I need to have a struct gain ownership of a dyn Trait
field. How can I store any struct that implements Trait
in my struct without creating any references? I really don't want the overhead of a Box
.
you can only have single one unsized type as the last field of a struct, and this would make the struct unsized as well.
trait Trait {}
struct Foo {
// other fields must be `Sized`
x: i32,
b: bool,
// this must be the last field
obj: dyn Trait,
}
note: working with a unsized type is very inconvinient, e.g. you cannot define a new()
function that returns Self
because the bound Self: Sized
is not satisfied.
EDIT:
I should clarify that, if you define a DST directly like the example above, it's almost useless. you can define methods with &self
and &mut self
receivers, but you cannot create values of such types directly. (maybe there exists use cases with #[repr(C)]
or ffi, but that'll be very rare I imagine).
a better way to define DST
s is to use generics, and the compiler will derive CoerceUnsized
automatically for you so you can actually create (heap-allocated) values of the type. example:
trait Trait {}
impl Trait for () {}
struct Foo<T: ?Sized> {
header: i32,
payload: T,
}
impl<T> Foo<T> {
fn new(header: i32, payload: T) -> Self {
Foo { header, payload }
}
}
fn main() {
let foo = Foo::new(42, ());
// coerce to reference
let ref_dyn_foo: &Foo<dyn Trait> = &foo;
// coerce to boxed value
let box_dyn_foo: Box<Foo<dyn Trait>> = Box::new(foo);
}
you can define a constructor for boxed value for convinience
impl<T> Foo<T> {
fn new_boxed_dyn(header: i32, payload: T) -> Box<Foo<dyn Trait>>
where
T: Trait + 'static,
{
Box::new(Self::new(header, payload))
}
}
The dyn Trait
mechanism requires that you have a pointer-indirection somewhere for technical reasons. That doesn’t necessarily need to be inside your struct, though:
use std::fmt::Display;
struct MyStruct<T:?Sized> {
count:u32,
extra:T
}
fn f(s:&MyStruct<dyn Display>) {
let MyStruct { count, extra } = s;
for _ in 0..*count {
println!("{extra}");
}
}
fn main() {
f(&MyStruct { count:3, extra: "Hello" });
}
Note that the custom DSTs above don't have ownership. There's probably some max-size-based inline "box" approaches in the embedded world. But generally you would use something on the heap (Box
, Arc
, etc).
If you want to pass the object by value, you're going to have to use an enum
of all possible implementations instead of dyn Trait
.
In addition to the other suggestions here, you might consider a crate like stack_dst.
I am confused by this claim. They certainly are responsible for dropping, and can potentially mutate, the T
. What else are you defining ownership as?
I suppose that was a terse and imprecise. For example:
let foo = Foo::new(42, ()); // `Foo<()>` owns `()`
let ref_dyn_foo: &Foo<dyn Trait> = &foo; // `&Foo<dyn Trait>`, no ownership
// Creates temporary `MyStruct<&str>` that owns `&str`
// Coerces to `&MyStruct<dyn Display>` with no ownership
f(&MyStruct { count:3, extra: "Hello" });
In both cases the coercion from the owning, statically sized type to the dynamically sized type happens behind a non-owning reference. I don't think that's what the OP wanted.
But in the next statement in @nerditation’s version, you also have
let box_dyn_foo: Box<Foo<dyn Trait>> = Box::new(foo);
There is no sense in which the dyn Trait
is not owned now.
My comment was aimed at the OP.
Ah, I see. Sorry for the long tangent.
My own perspective here is that boxing is probably necessary (if you don't go for the “inline box” approach which requires picking a maximum size), but custom DSTs may let you avoid having an additional indirection by using the same Box
(or Arc
or any other owning pointer) for another purpose in the application, so you're at least paying for only 1 indirection instead of 2.
I have personally found it useful to set up things like Arc<MyStruct<dyn Fn()>>
, where MyStruct
contains other fields that also need to be shared along with the caller-supplied Fn
. So, there’s an allocation but it was an allocation I needed anyway.
That is a good point (and a nice pattern).