0

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, i.e. 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, i.e.

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??

A. Kriegman
  • 510
  • 1
  • 4
  • 18
  • "for every combination of `T` and `U`" - this is not something a language will likely be happy to provide, but I think you can emulate that (but not generically). – Chayim Friedman Jul 25 '22 at 20:52

1 Answers1

0

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:

kmdreko
  • 42,554
  • 6
  • 57
  • 106