Heh, what an interesting one 
The idea is that there is so much inference and lifetime adjusting going on with your call that the error ends up being a bit silly.
Without further ado, let's debug this one, shall we?
The trick is thus to try and get to annotate the types involved as much as possible; let's focus on the HashMap::get()
signature:
impl<Key, Value> HashMap<Key, Value>
where
Key : Hash + Eq,
{
fn get (self: &'_ HashMap<Key, Value>, key: &'_ KeyView)
-> Option<&'_ Value>
where
Key : /* can be */ Borrow/*-ed as a */ <KeyView>,
KeyView : Hash + Eq,
- (from the std lib, but renamed for clarity)
So let's try and perform an explicit call with as little elision as possible:
- m.get(&k)
+ HashMap::<&'static str, String>::get::<&'k str>(m, &k)
This doesn't change the error message, so I suspect the two-time generics are making it so the second-batch of generics (the ::<&'k str>
) is actually performing a subtyping "coercion" on the Key
and Value
types of the HashMap
; and the only kind of subtyping relationships that Rust has involve lifetimes, that means the &'static str
may be subtyped to become a &'k str
, for instance, since that may accomodate for some constraint we have around, and if that happens then indeed the yielded value will only be valid for the 'k
lifetime, which is a lifetime which originates from k
, hence the error message.
To confirm this theory, let's make sure no such subtyping takes place, by using only one layer of generics / turbofish:
fn hashmap_get<'map, Key, Value, KeyView : ?Sized>(
map: &'map HashMap<Key, Value>,
key: &'_ KeyView,
) -> Option<&'map Value>
where
Key : Hash + Eq,
Key : Borrow<KeyView>,
KeyView : Hash + Eq,
{
map.get(key)
}
and now:
- HashMap::<&'static str, String>::get::<&'k str>(m, &k)
+ hashmap_get::<&'static str, String, &'k str>(m, &k)
this yields:
error[E0308]: mismatched types
--> src/lib.rs:20:5
|
20 | hashmap_get::<&'static str, String, &'k str>(m, &k)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ lifetime mismatch
|
= note: expected trait `Borrow<&'k str>`
found trait `Borrow<&'static str>`
note: the lifetime `'k` as defined on the function body at 18:17...
--> src/lib.rs:18:17
|
18 | fn do_stuff<'m, 'k>(m: &'m HashMap<&'static str, String>, k: &'k str) -> Option<&'m str> {
| ^^
= note: ...does not necessarily outlive the static lifetime
which is already hinting at a specially interesting thing, now: an unmet Borrow
bound!
Indeed, remember, the get
function requires that it be given a
key: &KeyView,
where
Key : /* can be */ Borrow /* -ed as a */ <KeyView>,
In our case, we do have Key = &'static str
, and depending on how we call the .get()
function, we are free to choose which KeyView
type to use.
When writing &k
, we have:
Thus, KeyView = &'k str
. Do we uphold Key : Borrow<Keyview>
?
i.e., do we have:
&'static str : Borrow<&'k str>
? No, we don't. Hence the error. The only impl that may apply here is the "reflexivity of Borrow
" rule, i.e., something may be borrowed as itself, which for Self = &'static str
, yields
&'static str : Borrow<&'static str>
.
There is also another rule, which states that a reference can be borrowed as its referee. When the referee is str
, this gives:
for<'any>
&'any str : Borrow<str>
,
Thus, if you had given k
rather than &k
, you'd have had KeyView = str
rather than KeyView = &'k str
and you'd have had no trait error [and thus no weird subtyping "coercion"] and thus no lifetime error!
Hence the fix:
- m.get(&k)…
+ m.get( k)…
The really interesting thing here, regarding Rust, is that when the language should have encountered an unresolved trait bound, instead, it still tried very hard to make compilation pass, by "abusing a legal loophole", we could say
, regarding subtyping of the Key
type. That is, when Rust encountered a:
// Inferred; can still be subtyped / the lifetime can shrink
// vvvvvvvvvvvv
&'static str :? Borrow<&'k str>
Rust decided that rather than bailing out directly, if it were to shrink that 'static
down to 'k
, it could make the bound pass. It thus dodged that compilation error (
) but in doing so, it just threw itself into the lion borrow checker's mouth, which yielded a way more obtuse error message 
Rust may be an incredibly smart automaton, but it's not able to solve the trolley problem (yet) 
