vuilehaid
vuilehaid

Reputation: 352

Non-scalar cast: `Box<FnMut<&Any>>`

I am trying to return a closure that takes an &Any as argument. Following code returns a compiler error.

trait Selector {
    fn id(&self) -> i64;
    fn selector(&self) -> Box<FnMut(&Any, &mut Any)>;
}
struct TypedSelector<TSource, TTarget> {
    id: i64,
    select: Box<FnMut(&TSource, &mut TTarget)>,
}
impl<TSource, TTarget> Selector for TypedSelector<TSource, TTarget>
    where TSource: 'static,
          TTarget: 'static
{
    fn id(&self) -> i64 {
        self.id
    }
    fn selector(&self) -> Box<FnMut(&Any, &mut Any)> {
        self.select as Box<FnMut(&Any, &mut Any)>
    }
}

The compiler error is the following:

error: non-scalar cast: `Box<for<'r, 'r> std::ops::FnMut(&'r TSource, &'r mut TTarget) + 'static>` as `Box<for<'r, 'r> std::ops::FnMut(&'r std::any::Any + 'static, &'r mut std::any::Any + 'static)>`
   --> src\main.rs:190:9
    |
190 |         self.select as Box<FnMut(&Any, &mut Any)>
    |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Am I missing some type annotations?

Upvotes: 4

Views: 76

Answers (1)

Chris Emerson
Chris Emerson

Reputation: 14041

There are a few problems here.

First, what you're trying to do (cast from FnMut<&TSource, &mut TTarget> to FnMut<&Any, &mut Any>) isn't valid. If you had succeeded you would have been able to call a function expecting a &TSource with a different type - so you'd have broken type safety and caused undefined behaviour.

To fix this, you can wrap it in a closure which downcasts the Any and handles any errors (in this example it will panic as I use unwrap):

    Box::new(
        move |a, b| {
            let a = a.downcast_ref().expect("a was the wrong type.");
            let b = b.downcast_mut().expect("b was the wrong type.");
            (self.select)(a, b)
        }
    )

At this point the next problem becomes apparent: TypedSelector owns the original boxed closure (select), but this new closure needs access to it. There are three ways to pass values in Rust, and none work as is:

  • By value (move) won't work unless selector takes self by value (and hence destroys it in the process)
  • By immutable &reference wouldn't allow you to call the FnMut
  • By mutable &mut reference similarly can't be done from the immutable &self.

So something needs to change. I'm going to arbitrarily pick the most fully featured but heavy-weight option, and use Rc<RefCell<T>> to have shared reference-counted pointers to the internally-mutable FnMut; this might not be the option which is best for your situation:

fn selector(&self) -> Box<FnMut(&Any, &mut Any)+'static> {
    let wrapped = self.select.clone();
    Box::new(
        move |a, b| {
            let a = a.downcast_ref().expect("a was the wrong type.");
            let b = b.downcast_mut().expect("b was the wrong type.");

            // Mutably borrow the shared closure (checked at runtime)
            let mut f = wrapped.borrow_mut(); 

            (&mut *f)(a, b)
        }
    )
    //self.select as Box<FnMut(&Any, &mut Any)>
}

(Playground link)

Upvotes: 4

Related Questions