Проблема возникает из-за того, что неизменяемая ссылка является вариативной по отношению к своему (ссылочному) типу, тогда как изменяемая ссылка инвариантна относительно своего типа.
Отличное чтение для понимания концепции: 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: компиляция не удалась.