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);
}
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With