Why the [u8] inside Box<[u8]> does not implement AsRef<[u8]> + AsMut<[u8]>?

The code below creates a Box<[u8]>, which I though had a fixed size, because Box has a fixed size. Also, I though [u8] implemented AsRef<[u8]> + AsMut<[u8]>

pub trait MemAs<T>: AsMut<[T]> + AsRef<[T]> {}
fn allocate(size: usize) -> Box<dyn MemAs<u8>> {
    vec![0; size].into_boxed_slice()
}

Errors:

error[E0277]: the trait bound `[{integer}]: MemAs<u8>` is not satisfied
 --> src/lib.rs:3:5
  |
3 |     vec![0; size].into_boxed_slice()
  |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `MemAs<u8>` is not implemented for `[{integer}]`
  |
  = note: required for the cast to the object type `dyn MemAs<u8>`

error[E0277]: the size for values of type `[{integer}]` cannot be known at compilation time
 --> src/lib.rs:3:5
  |
3 |     vec![0; size].into_boxed_slice()
  |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ doesn't have a size known at compile-time
  |
  = help: the trait `Sized` is not implemented for `[{integer}]`
  = note: required for the cast to the object type `dyn MemAs<u8>`

https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=a348e149773b8145284fac308666b220

What is wrong?

The problem is that you can't cast non-Sized things to dyn Trait. Example:

    // No problem
    let _: &dyn Debug = &();
    
    // And `str` is Debug too
    fn is_debug<T: ?Sized + Debug>(_: &T) {}
    is_debug::<str>("");
    
    // But this is an error
    let _: &dyn Debug = "";

Why not? To put it very briefly, the metadata for wide pointers is either a vtable or a count, but for a &([T] as dyn Trait) you would need both.

          +--------------+----------------+
&dyn is a | data_pointer | vtable pointer |
          +--------------+----------------+

          +--------------+----------------+
&[T] is a | data_pointer | count as usize |
          +--------------+----------------+
2 Likes

indeed. It would work for Box<&[T]> I guess but it would be useless. Do you know a good way to fix this? I basically needed to make the Box<&[T]> "borrowable" but maybe also I want to Box<Vec<Box<[T]>>> borrowable, that's why I use a dyn MemAs<u8>, and the Box is of course to make it Sized.

I think you're going to want to have a trait that Box<U: ?Sized> satisfies (in contrast to a trait that U: ?Sized, including [T], satisfies).

impl<U: ?Sized> BehaviorIWant for Box<U> where U ...

Then you could cast Box<Box<[T]>> to Box<dyn BehaviorIWant>. The indirection is a bit sad though.

Presumably you want this to be type-erased for a reason, so these are probably no-gos, but if you had such a bound you could also

  • Just return the Box<[T]>
    • Consumers rely on generics: fn f<X: BehaviorIWant>(...)
    • If they need it type-erased, like to put different ones in a Vec, they'll have to box it themselves
  • Return impl BehaviorIWant
    • Similar, but you're even hiding the type now
    • Even less convenient for consumers until TAIT makes opaque types nameable

I'm batting around other ideas in my head but haven't come up with anything worth suggesting yet.

All I had to do was hit Reply for an idea worth floating to pop into my head.

fn allocate<const SIZE: usize>() -> Box<dyn MemAs<u8>> {
    Box::new([0u8; SIZE])
}

Edit: Let's think about the tradeoffs for a moment. The problem with &([T] as dyn) is more detail is...

  • &dyn and &[T] both need to be able to calculate their size dynamically
  • It's obvious how to do this for [T] with a known T and a length
  • For &dyn, the size is stored in the vtable
  • For &([T] as dyn)...
    • We've erased T, so we need the size and not just the length
    • We can't store it "inline" without making &dyn an extra usize larger
    • We can't store it in the vtable because that's static memory and shared
      • But if there was some way to do this, that would be a vtable per unique length N. Horrors!

We can have a &([T; N] as dyn) though, as suggested. How does that work for the same T but different N?

Well, every unique length N gets its own vtable storing the size size_of::<T>() * N...

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.