Почему HashMap :: get_mut более требователен, чем HashMap :: get в отношении времени жизни? - PullRequest
0 голосов
/ 07 мая 2018

У меня есть struct Foo<'a>, который является оберткой вокруг &'a str ссылок. И я хочу заполнить HashMap ключами Foo. Вот фрагмент кода ( откройте его на детской площадке ):

use std::collections::HashMap;

#[derive(PartialEq, Eq, Hash)]
struct Foo<'a> {
    txt: &'a str,
}

fn main() {
    let a = "hello".to_string();
    let a2 = Foo { txt: &a };
    let b = "hello".to_string();
    let b2 = Foo { txt: &b };

    let mut hm = HashMap::<Foo, u32>::new();

    hm.insert(a2, 42);
    println!("=== {:?}", hm.get(&b2));     // prints Some(42)
    println!("=== {:?}", hm.get_mut(&b2)); // prints Some(42)

    {
        let c = "hello".to_string();
        let c2 = Foo { txt: &c };
        println!("=== {:?}", hm.get(&c2));         // prints Some(42)
        // println!("=== {:?}", hm.get_mut(&c2));  // does not compile. Why?
        // hm.insert(c2, 101);                     // does not compile, but I understand why.
    }
}

Этот код отлично компилируется и работает, но компилятор жалуется, если я раскомментирую две последние строки кода. Точнее говоря, он жалуется на то, что заимствованная стоимость в c2 не живет достаточно долго.

Для последнего (insert) это совершенно понятно: я не могу переместить c2 в HashMap, который живет дольше, чем данные, заимствованные c2 у c.

Однако я не понимаю, почему у второй до последней строки (get_mut) такая же проблема: в этом случае заимствованные данные должны быть необходимы только при вызове get_mut, это не так. переехал в HashMap.

Это тем более удивительно, что get выше работает отлично (как я и ожидал), и что get и get_mut имеют одинаковые сигнатуры, когда дело доходит до параметра k ...


После копания еще немного, Я воспроизвел проблему с простыми ссылками (вместо структуры, включающей ссылку).

use std::collections::HashMap;

fn main() {
    let a = 42;
    let b = 42;

    let mut hm = HashMap::<&u32,u32>::new();

    hm.insert(&a, 13);
    println!("=== {:?}", hm.get(&&b));     // prints Some(13)
    println!("=== {:?}", hm.get_mut(&&b)); // prints Some(13)

    {
        let c = 42;
        println!("=== {:?}", hm.get(&&c));        // prints Some(13)
        //println!("=== {:?}", hm.get_mut(&&c));  // does not compile. Why?
    } 
}

( открыт на детской площадке )

Опять же, раскомментирование последней строки вызывает жалобу компилятора (то же сообщение, что и выше).

Однако я нашел интересный обходной путь для этого конкретного примера: замена &&c на &c в последней строке решает проблему - на самом деле, можно заменить && на & во всех вызовах get и get_mut. Я думаю, это связано с &T реализацией Borrow<T>.

Я не совсем понимаю, что в этом обходном пути убеждает компилятор сделать то, что я хочу. И я не могу применить его непосредственно к моему исходному коду, потому что я не использую ссылки в качестве ключей, но объекты встраивают ссылки, поэтому я не могу заменить && на & ...

1 Ответ

0 голосов
/ 08 мая 2018

Проблема возникает из-за того, что неизменяемая ссылка является вариативной по отношению к своему (ссылочному) типу, тогда как изменяемая ссылка инвариантна относительно своего типа.

Отличное чтение для понимания концепции: Nomicon .

HashMap: get против get_mut

Сокращение ниже, это более простой код, воспроизводящий проблему:

#![allow(unused_variables)]
use std::collections::HashMap;

fn main() {
    let mut hm = HashMap::<&u32, u32>::new();    // --+ 'a
    let c = 42;                                    // |  --+ 'b
                                                   // |    |
    HashMap::<&u32, u32>::get(&mut hm, &&c);       // |    |
    // HashMap::<&u32, u32>::get_mut(&mut hm, &&c);// |    |
}                                                  // +    +

Неизменный корпус

Рассмотрим подпись HashMap::get:

fn get<Q: ?Sized>(&self, k: &Q) -> Option<&V>
    where K: Borrow<Q>, Q: Hash + Eq

В этом случае &Q равен &&'b u32, а приемник get равен &Self.

Вариантность неизменяемых ссылок подразумевает, что &HashMap<&'a u32, u32> может использоваться там, где требуется &HashMap<'b u32, u32>.

Благодаря этому правилу компилятор учитывает исходный вызов:

HashMap::<&'a u32, u32>::get(&hm, &&'b c);

эквивалентно:

HashMap::<&'b u32, u32>::get(&hm, &&'b c);

Компилятор выводит из интерфейса, а только из интерфейса , что реализация метода не может привести к утечкам: компиляция выполнена успешно.

Изменчивый корпус

Рассмотрим подпись HashMap::get_mut:

fn get_mut<Q: ?Sized>(&mut self, k: &Q) -> Option<&mut V>
    where K: Borrow<Q>, Q: Hash + Eq

Также в этом случае &Q равно &&'b u32, но приемник get_mut равен &mut Self.

Инвариантный характер изменчивых ссылок подразумевает, что &mut HashMap<&'a u32, u32> не может использоваться там, где ожидается &mut HashMap<&'b u32, u32>.

Благодаря этому правилу компилятор выдает ошибку, потому что, анализируя только интерфейс:

HashMap::<&'a 32, u32>::get_mut(&mut hm, &&'b c);

компилятор не может исключить, например, что get_mut может хранить ключ с временем жизни 'b.

Такой ключ не может пережить hm HashMap: компиляция не удалась.

...