In Rust Box<dyn FnOnce>
seems to have a superpower: you can call the function and consume the box. But traits with methods with Box<Self>
receivers are not object-safe. I know there's a way around this: you can redesign your API to accept &mut self
instead, wrap the fields you want to consume in Option
, and take
them out. This is a general technique that weakens FnOnce
to FnMut
. But is there a way to make Box<dyn Trait>
objects with a method that consumes self
?
But they are!
trait Trait {
fn method(self: Box<Self>);
}
impl Trait for Struct {
fn method(self: Box<Self>) {
let inner: String = self.inner;
println!("owning, and dropping {inner:?}");
drop(inner);
}
}
struct Struct {
inner: String,
}
fn main() {
let x = Box::new(Struct {
inner: String::from("Hello World"),
});
let x: Box<dyn Trait> = x;
println!("before method call");
x.method();
println!("after method call");
}
before method call
owning, and dropping "Hello World"
after method call
They are, and you can impl Trait for Box<dyn Trait + '_>
when there's a self
receiver, too.
Thanks for the explanation. Is it necessary to name the receiver self
?
If you want the method to be dyn
dispatchable, yes.
It’s still true of course, that there is a superpower at play here. I’m not sure if there’s any real reason to doing it the magic way beyond the convenience – as well as possibly the shorter vtable (and the box deallocation not being behind the function call indirection could be of relevance to performance, too[1]).
I’d be curious though if anyone ever properly compared the pros&cons here ↩︎
Yes: in general, if there is no self
, then the parameter is not a receiver and the function is not a method; and dyn
dispatch only works on methods.
It seems very weird that the blanket impl of BoxedBye
is allowed; it seems circular. Was this an unintended consequence of the implementation of rustc
?
How so? Do you mean because it's a supertrait of Trait
, but the where
clause says T: Trait
? If so, that's fine so long as you don't create a cycle for the trait solver.