I had a function that called map_or(None, …), which clippy suggests one should change to an .and_then() (because of the None case (it will go on to suggest another change, but that’s not important for my question)). Just copy’n’paste:ing clippy’s suggestion will cause an compilation error, because it detects a mismatched return type:
error[E0308]: mismatched types
--> src/main.rs:25:9
|
14 | impl<K, V> MyMap<K, V>
| - expected this type parameter
...
18 | fn get<'a>(&'a self, k: &'a K) -> Option<(&'a K, &'a V)> {
| ---------------------- expected `std::option::Option<(&'a K, &'a V)>` because of return type
...
25 | / self.inner.get(k).and_then(|v| {
26 | | let v = &v.v;
27 | | Some((k, v))
28 | | })
| |__________^ expected `Option<(&K, &V)>`, found `Option<(&K, &ManuallyDrop<V>)>`
|
= note: expected enum `std::option::Option<(&'a _, &'a V)>`
found enum `std::option::Option<(&_, &std::mem::ManuallyDrop<V>)>`
What’s causing the map_or() case to allow the compiler to detect that it can shoehorn a &ManuallyDrop<V> into a &V, while this isn’t allowed for the and_then() case?
(The fix for the and_then() case is trivial, I’m just curious where the difference in magic matching ability comes from).
My guess is that it is because the None::<_> shows up before the closure, and then inference does some work on that which ends up supplying more "outside influence" on the closure signature (as opposed to inferring the return type from the closure body). One reason for my guess is that map_or_default (a nightly method) also fails to compile.
I don’t have any deep insights, but I am able to minimize it to be a difference like
fn and_then<T, U, F>(o: Option<T>, f: F) -> Option<U>
where
F: FnOnce(T) -> Option<U>,
vs
fn and_then_2<T, U, F>(o: Option<T>, f: F, _: [U; 0]) -> Option<U>
where
F: FnOnce(T) -> Option<U>,
so what matters apparently is that there is some function parameter somewhere that also uses the U parameter, and that influences how type inference works here.
Because… it’s definitely a phenomenon of type inference. It’s the typical phenomenon with implicit casts, that the order/way in which type inference operates propagating type information can influence where in the code the type mismatch ends up happening; and different locations in code with a mismatch involving the same type parameter may result in different outcomes, as is the case here: A mismatch between Option<(&K, &V)> and Option<(&K, &ManuallyDrop<V>)> is a compilation error; but a mismatch between &V and &ManuallyDrop<V> merely results in an implicit cast [the location it happens then would be the second argument of the tuple constructor in the return expression inside of the closure].
As usual with these things, a well-placed explicit cast can solve the issue, e.g. in this case writing Some((k, v as _)) does it.