Cannot infer closure type with higher-ranked lifetimes inside Box::new
In the code below, type inference works in the first line of main(), but fails in the second one. Note also that the "kind" of the closure has to be given with explicit syntax when inside Box::new().
fn do_async<F>(_cb: Box<F>) where F: FnOnce(&i32) {
}
fn do_async_unboxed<F>(cb: F) where F: FnOnce(&i32) {
do_async(Box::new(cb))
}
fn main() {
do_async_unboxed(|x| { println!("{}", *x); });
do_async(Box::new(|: x| { println!("{}", *x); }));
}
Triage: updated code
fn do_async<F>(_cb: Box<F>) where F: FnOnce(&i32) { } fn do_async_unboxed<F>(cb: F) where F: FnOnce(&i32) { do_async(Box::new(cb)) } fn main() { do_async_unboxed(|x| { println!("{}", *x); }); // Ok do_async(Box::new(|x| { println!("{}", *x); })); // ERROR the type of this value must be known in this context }Steve Klabnik at 2015-11-04 16:39:16
From my investigation, the problem is that we don't relate the return type of
Wrapper/Box::newto the argument type ofapi, because we are afraid there might be a coercion involved. This means the bound information is not passed to the closure. We could try to be better here - cc @nikomatsakis @eddyb.Ariel Ben-Yehuda at 2015-11-22 12:26:29
Nominating as this is makes some API patterns horribly non-ergonomic.
Ariel Ben-Yehuda at 2015-11-22 12:27:23
triage: P-medium
Niko Matsakis at 2015-12-03 21:37:14
I agree we could probably do better here. I'm not sure what precisely is going on or the current state of this coercion code though.
Niko Matsakis at 2015-12-03 21:37:31
triage: P-medium
Niko Matsakis at 2015-12-17 21:34:37
// Works: fn foo<F: Fn(f32) -> f32>(_: F) {} foo(|x| x.sin()) // Why it works: the inference variables look like this: foo::<$F>((|x: $A| x.sin()): $F) // Because $F: Fn(f32) -> f32, we can deduce $A: f32.// Doesn't work: fn bar<F: Fn(f32) -> f32>(_: Option<F>) {} bar(Some(|x| x.sin())) // Why it doesn't work: the inference variables look like this: bar::<$F>(Some::<$T>((|x: $A| x.sin()): $T): Option<$F>) // Because Some: T -> Option<T>, we know that $T = $F. // But they are both inference variables, so we don't replace // one with the other, and $T: Fn(f32) -> f32 doesn't exist.If my analysis is correct, it should be possible to fix this bug by computing inference variable equivalence via union-find instead of using simple equality.
Eduard-Mihai Burtescu at 2016-01-08 14:58:11
@eddyb
Except that the variables are only related via a coercion.
Ariel Ben-Yehuda at 2016-01-10 13:42:18
That looks like a case of things being so loosely coupled they fall apart.
Ariel Ben-Yehuda at 2016-01-10 13:42:54
@arielb1 There's no coercion magic going on, this is the "expected" type being propagated down. Coercions are applied after the type is known. ~~Try replacing the strict equality with equivalence and it should just start working.~~
Eduard-Mihai Burtescu at 2016-01-10 13:44:23
I mean, we don't equate
$Tand$F, but rather we will coerce them after the call is type-checked.Ariel Ben-Yehuda at 2016-01-10 13:49:13
@arielb1 We do to obtain the expected type for the arguments of a call.
Eduard-Mihai Burtescu at 2016-01-10 13:49:39
@eddyb
That would be a subtype relation, which is destroyed by the
commit_regions_if_okcall.Ariel Ben-Yehuda at 2016-01-10 13:53:32
@arielb1 Let's try to be clearer using pseudosyntax:
// Initial expression. bar(Some((|x| x.sin()))) // Add type variables for bar. bar::<$F>(Some((|x| x.sin()))) // Propagate expected type from signature of bar. bar::<$F>(Some((|x| x.sin())) expects Option<$F>) // Add type variables for Some. bar::<$F>(Some::<$T>((|x| x.sin()))) // Propagate expected type from signature of Some. bar::<$F>(Some::<$T>((|x| x.sin()) expects $T) expects Option<$F>)Well, that's embarrassing, turns out I was being unreasonable. A relationship between
$Tand$Fcannot exist before assigning the actual types.Presumably it might be possible to end up with:
bar::<$F>(Some::<$T>((|x| x.sin()) expects $F) expects Option<$F>)What confused me was that the following snippet ~~works~~ seemed to work when I tried it initially:
fn foo<F: Fn(f32) -> f32>(_: F) {} fn id<T>(x: T) -> T {x} foo(id(|x| x.sin()))Given that it doesn't actually work, it's a better testcase. If we end up with:
foo::<$F>(id::<$T>((|x| x.sin()) expects $F) expects $F)Then that would work, but consider:
fn foo<F>(_: F) {} fn id<T: Fn(f32) -> f32>(x: T) -> T {x} foo(id(|x| x.sin()))Where
$Thas theFnbound but$Fdoesn't.@arielb1 @nikomatsakis Would it make sense to have a set of inference variable "assignments" used for the expected type? So that
$T = $Fis known but doesn't affect code which doesn't explicitly deal with expected types, as we only need to know thatX: Fn(f32) -> f32applies to the closure whetherXis$For$T.Eduard-Mihai Burtescu at 2016-01-10 14:18:04
@eddyb
That might fix this, but it may cause confusing issues if a coercion were actually to occur. Maybe have hints solely for closure inference? That looks like a hack.
Ariel Ben-Yehuda at 2016-01-11 11:07:47
@arielb1 Nothing other than closure inference looks at bounds which refer to the expected type, AFAICT.
Eduard-Mihai Burtescu at 2016-01-11 16:32:38
Triage: not aware of any movement on this issue
Steve Klabnik at 2017-09-30 16:13:42
Triage: still reproduces on 2021 edition, tested using
rustc 1.59.0 (9d1b2106e 2022-02-23)Maayan Hanin at 2022-03-21 12:04:05