简体   繁体   中英

Rust struct with reference to a type with a dynamic parameter

Here's what I want to do:

struct Foo<T: Trait> {
  inner: T,
}

struct Bar<'a> {
  foo: &'a Foo<dyn Trait>, // This is pseudo code, I don't think Rust has this feature.
}

I want an instance of Bar to hold a reference to a Foo where the type parameter of Foo is dynamic, ie over the lifetime of a single Bar it may hold different instances of Foo with different types for T . Since Bar only holds a reference, it would still be Sized . As far as I can tell this is impossible right now. If this were possible, then I could do it multiple times in the same type, ie

struct Foo<T: Trait, U: Trait> {
  a: T,
  b: U,
}

struct Bar<'a> {
  foo: &'a Foo<dyn Trait, dyn Trait>,
}

Now foo needs to hold at least three pieces of information: a pointer, a pointer to a vtable for T, and a pointer to a vtable for U. This is unlike references to DSTs as they currently exist in the language, which hold two pieces of information: &[T] holds a pointer and a length, and &dyn Trait which holds a pointer and a pointer to a vtable. I think this could be feasible, but as far as I'm aware nothing like this exists in Rust at the moment. So, what's the closest thing to what I want to do?

Actually, on second thought, foo in the above example could just hold one vtable pointer, and we could have a separate vtable for every combination of T and U . So maybe this is possible right now??

This is possible actually!

If you compile the first example you get this error:

error[E0277]: the size for values of type `(dyn Trait + 'static)` cannot be known at compilation time
 --> src/lib.rs:8:10
  |
8 |     foo: &'a Foo<dyn Trait>,
  |          ^^^^^^^^^^^^^^^^^^ doesn't have a size known at compile-time
  |
  = help: the trait `Sized` is not implemented for `(dyn Trait + 'static)`
note: required by a bound in `Foo`
 --> src/lib.rs:3:12
  |
3 | struct Foo<T: Trait> {
  |            ^ required by this bound in `Foo`
help: you could relax the implicit `Sized` bound on `T` if it were used through indirection like `&T` or `Box<T>`
 --> src/lib.rs:3:12
  |
3 | struct Foo<T: Trait> {
  |            ^ this could be changed to `T: ?Sized`...
4 |     inner: T,
  |            - ...if indirection were used here: `Box<T>`

It somewhat hints that you can relax the implicit Sized bound by using ?Sized , and doing so will make the Foo<dyn Trait> declaration compile!

struct Foo<T: Trait + ?Sized> {
    inner: T,
}

However, your intuition is correct that this cannot extend beyond a single dynamically-sized field. If you were to try the same trick on the second example, you'd get an error:

struct Foo<T: Trait + ?Sized, U: Trait + ?Sized> {
  a: T,
  b: U,
}
error[E0277]: the size for values of type `T` cannot be known at compilation time
 --> src/lib.rs:4:6
  |
3 | struct Foo<T: Trait + ?Sized, U: Trait + ?Sized> {
  |            - this type parameter needs to be `std::marker::Sized`
4 |   a: T,
  |      ^ doesn't have a size known at compile-time
  |
  = note: only the last field of a struct may have a dynamically sized type
  = help: change the field's type to have a statically known size
help: consider removing the `?Sized` bound to make the type parameter `Sized`
  |
3 - struct Foo<T: Trait + ?Sized, U: Trait + ?Sized> {
3 + struct Foo<T: Trait, U: Trait + ?Sized> {
  | 
help: borrowed types always have a statically known size
  |
4 |   a: &T,
  |      +
help: the `Box` type always has a statically known size and allocates its contents in the heap
  |
4 |   a: Box<T>,
  |      ++++ +

Emphasis on the "only the last field of a struct may have a dynamically sized type" which gives an indication of how dynamically-sized types are implemented by the compiler.

Actually, on second thought, foo in the above example could just hold one vtable pointer, and we could have a separate vtable for every combination of T and U . So maybe this is possible right now??

Unfortunately no, this is not supported.

Dynamically-sized types aren't particularly well supported and thus their current implementation is simplistic. For example, you can't actually construct a Foo<dyn Trait> directly, you can only create one by first creating a Foo<T> and coercing it into a Foo<dyn Trait> . And of course, in that scenario Foo itself becomes a DST and thus can only be used behind some kind of indirection (reference, Box , Rc , etc.).

See also:

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM