Sam Estep
Sam Estep

Reputation: 13304

Why doesn't my generic From implementation accept Box<Fn()> values?

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

Answers (1)

Shepmaster
Shepmaster

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 the Foo 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:

  1. From Box<closure> to Box<Fn()>. This is provided by the compiler / the as keyword.
  2. From 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

Related Questions