Reputation: 13304
Consider the following simple struct, implementation of From
for that struct, and function that takes instances of the struct containing boxed functions:
struct Foo<T>(T);
impl<T> From<T> for Foo<T> {
fn from(x: T) -> Self {
Foo(x)
}
}
fn call(x: Foo<Box<Fn()>>) {
let Foo(f) = x;
f()
}
This prints "Hello, world!":
call(Foo(Box::new(|| println!("Hello, world!"))))
This fails to compile:
call(Box::new(|| println!("Hello, world!")).into())
The following error message is given:
error[E0277]: the trait bound `Foo<Box<std::ops::Fn()>>: std::convert::From<Box<[closure@src/main.rs:15:19: 15:47]>>` is not satisfied
--> src/main.rs:15:49
|
15 | call(Box::new(|| println!("Hello, world!")).into())
| ^^^^ the trait `std::convert::From<Box<[closure@src/main.rs:15:19: 15:47]>>` is not implemented for `Foo<Box<std::ops::Fn()>>`
|
= help: the following implementations were found:
= help: <Foo<T> as std::convert::From<T>>
= note: required because of the requirements on the impl of `std::convert::Into<Foo<Box<std::ops::Fn()>>>` for `Box<[closure@src/main.rs:15:19: 15:47]>`
I can't see any way in which my From
implementation is any stricter than the Foo
constructor. Why does into
fail where Foo
succeeds here?
Upvotes: 1
Views: 208
Reputation: 430711
I don't know for sure, but I suspect that this won't happen because there are two conversions required, and that's too many jumps to take. Check out the error message again:
From<Box<[closure@src/main.rs:15:19: 15:47]>>
Note that the error mentions a closure. In Rust, each closure is a unique, un-namable type (sometimes called a Voldemort type). A closure is not a Fn
, but it does implement Fn
.
For the From
conversion to work, the starting type would need to be a Box<Fn()>
. We can see that an explicit cast to Box<Fn()>
allows it to compile:
call((Box::new(|| println!("Hello, world!")) as Box<Fn()>).into());
why isn't the
as Box<Fn()>
needed in my first example using theFoo
function?
Again, I suspect that this works because there's only one conversion taking place. The complier knows how to convert a Box<closure>
to a Box<Fn()>
- it's just creating a boxed trait object.
I look at it like a little graph. There are two edges:
Box<closure>
to Box<Fn()>
. This is provided by the compiler / the as
keyword.T
to Foo<T>
. This is provided by the From
implementation.You can perform the first step (or have it done implicitly), or you can perform the second step via .into()
. However, there's no step that goes from the very beginning to the very end.
It would probably be a bad idea for the compiler to attempt to traverse the conversion graph to find a conversion path that has arbitrary number of steps. There's also the possibility for there to be many paths, leading to ambiguity.
I'd probably write it so that the function takes a generic and does the boxing and conversion itself. This way, the client of call
doesn't need to deal with those details:
fn call<F>(f: F)
where F: Fn(),
{
let x: Foo<_> = Box::new(f).into();
// I'm assuming something interesting happens
// here before we unpack the variable again
let Foo(f) = x;
f()
}
fn main() {
call(|| println!("Hello, world!"));
}
Chris Emerson points out that you can also accept a generic type:
fn callg<T:Fn()>(x: Foo<Box<T>>) {
let Foo(f) = x;
f()
}
fn main() {
callg(Box::new(|| println!("Hello, world!")).into());
}
This works because we are no longer converting to Box<Fn()>
, and thus only need one conversion step.
Upvotes: 4