5

I’m trying to use a callback function on a trait object. I reduced my problem to the following code (playpen):

trait Caller {
    fn call(&self, call: fn(&Caller)) where Self: Sized {
        call(self)
    }
}

struct Type;
impl Caller for Type {}

fn callme(_: &Caller) {}

fn main() {
    let caller: Box<Caller> = Box::new(Type);
    caller.call(callme); // does not work
    //callme(&*caller);  // works
}

which results in

<anon>:14:12: 14:24 error: the trait `core::marker::Sized` is not implemented for the type `Caller` [E0277]
<anon>:14     caller.call(callme); // does not work
                     ^~~~~~~~~~~~

Adding a Sized bound to Caller results in:

<anon>:3:14: 3:18 error: cannot convert to a trait object because trait `Caller` is not object-safe [E0038]

I really don’t understand why I need the Sized bound on the trait. Funnily it works if I use the callback directly. How do I get this to work?

Edit: Thanks to the answer I now came up with a nice solution

trait Caller {
    fn borrow(&self) -> &Caller;
    fn call(&self, call: fn(&Caller)) {
        call(self.borrow())
    }
}

struct Type;
impl Caller for Type {
    fn borrow(&self) -> &Caller { self }
}

fn callme(_: &Caller) {}

fn main() {
    let caller: Box<Caller> = Box::new(Type);
    caller.call(callme);
}

1 Answer 1

3

The argument call in fn call takes a trait object &Caller, so calling it requires coercing the self reference (of type &Self) to a &Caller trait object. The coercion is only possible when &Self is a thin pointer rather than a fat pointer like a trait object or a &[T] slice. &Self is a thin pointer exactly when Self: Sized. The compiler defaults to Self in traits not being Sized, and so the extra restriction is required. The Sized trait represents that the type has a size that is known at compile time, there is no need to store extra info (next to the pointer, making it "fat") to compute it at runtime.

Unfortunately, this leaves a hole: AFAIK, it's actually not possible to have such a method be a default method and still be able to call it on trait objects, since a trait object &Caller has Self = Caller which isn't Sized. However, it should work if the method is implemented manually for each type:

trait Caller {
    fn call(&self, call: fn(&Caller));
}

struct Type;
impl Caller for Type {
    fn call(&self, call: fn(&Caller)) {
        call(self)
    }
}

fn callme(_: &Caller) {}

fn main() {
    let caller: Box<Caller> = Box::new(Type);
    caller.call(callme);
}

The call method declaration in the trait no longer needs the where Self: Sized since it isn't trying to do the trait object coercion itself, and the concrete implementations have much more control over how the &Caller trait object is obtained. For Sized types, it works directly, like the original where Self: Sized code.

Sign up to request clarification or add additional context in comments.

5 Comments

One thing I'm still unclear on after reading this - is there a way to make the OP's code work?
Err, yeah, forgot to actually answer the question. :)
Thank you, but what I don’t understand is why it is not possible to transform &Self to &Caller. Shouldn’t that be just an identity transform as &Self is already a &Caller? Edit: The answer is probably that &Self is not &Caller but might be something else but sounds like it should be fixable in rustc.
If Self isn't Sized, it could be SomeOtherTrait, or [SomeType], i.e. &Self is some "random" fat pointer that isn't &Caller. We would need a bound like Self: SizedOrCaller to ensure that either &Self is a thin pointer or &Self is &Caller.
Thanks. I actually found a nicer solution of my problem. I now require the implementation of borrow_mut(&mut self) -> &mut Caller. Unfortunately it is not possible to require BorrowMut<Caller> as a trait bound on Caller since that creates some cycle.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.