DST Receivers/Idempotent unsize


#1

When trying to make something object safe, I ran into the following problem:

trait Trait {}

struct View<'a>(&'a mut Wrapper<Trait + 'a>);

struct Wrapper<A: ?Sized + Trait>(A);

impl<T: ?Sized + Trait> Wrapper<T> {
    // I want this to work on both `Wrapper<SomeType>` and `Wrapper<Trait>`.
    fn view<'a>(&'a mut self) -> View<'a> {
        // FIXME: Not sized so we can't do this.
        View(self as &mut Wrapper<Trait + 'a>)
    }
}

fn main() {}

However, I fell there should be some way to do this. For now, I can implement this method on Wrapper<Trait> and implement DerefMut<Target=Wrapper<Trait>> for Wrapper<T> where T: Trait. However, this feels dirty. I’d like to be able to write the following:

trait Trait {}

struct View<'a>(&'a mut Wrapper<Trait + 'a>);

struct Wrapper<A: ?Sized + Trait>(A);

impl<T: ?Sized + Trait> Wrapper<T> {
    fn view<'a>(self: &'a mut Wrapper<Trait + 'a> /* Unsize the receiver */) -> View<'a> {
        View(self)
    }
}

fn main() {}

Before, I submit an RFC issue, is there a fundamental reason rust can’t (efficiently) support this?


How can I cast `&self` to a trait object in default method?
#2

While converting from T: Trait to Trait might be obvious to you in a specific circumstance, generically speaking, converting some Struct<T:Trait> to Struct<Trait> is highly non-obvious. Consider Box<Trait>. Box<T: ?Sized> is defined to wrap Unique, which is defined to hold a raw pointer. How would the compiler know how to do the conversion without understanding the intimate semantics of the unsafe code?

I don’t quite understand how you intend to implement DerefMut<Target=Wrapper<Trait>>. The return type of deref_mut() would be &'a mut Wrapper<Trait>. But Wrapper<T> does not contain a Wrapper<Trait> and you would need to return a reference to the latter with the right lifetime. As far as I can see a conversion function, not a dereferencing one, needs to be implemented that unwraps T, casts it to Trait and returns the wrapped version of it; i.e. &'a Wrapper<T> --> Wrapper<Trait + 'a>.


#3

To further clarify: a trait object is a type-erased reference type. It consists of a pair: pointer to the data, and a pointer to the vtable for the original type. Wrapper<T>, as you defined, contains neither — it contains the data directly inline.

I also refer you to Huon’s writeup on the representation of trait objects.


#4

And yet Rust can do exactly that today - if T is Sized. The only missing operation is trait-object-to-trait-object.

One potential problem with the latter I can think of is that if Rust eventually supports &(Trait1+Trait2+Trait3) properly (it currently only allows it if all but the first trait are builtin marker traits with no methods), going from that to, say, &(Trait1+Trait3) would definitely require a separate vtable, so if such casting were allowed in general, turning a concrete reference to a trait object with N traits would require generating a number of vtables exponential in N. And such a compound trait object would match your generic impl. The special case of casting to a single-trait object as you’re trying to do - say, going from &(Trait1+Trait2) to &Trait2 - would be possible if vtables for compound objects embedded full copies of the vtables for their component traits, such that the conversion would just be a pointer addition, but even that might be problematic when combined with trait inheritance…


#5

I think the issue is more about the special status of ‘?Sized’, because if you could write:

struct View<'a>(&'a mut Wrapper<?Sized + Trait + 'a>);

It should work. However ?Sized is only valid in the generic parameter declaration part like here:

struct Wrapper<A: ?Sized + Trait>(A);

Is that a bug, or is it done deliberately because it would be unsound to have a trait like:

trait Trait : ?Sized {}

Edit: Also note, I think this:

struct Wrapper<A: ?Sized + Trait>(A);

Is an anti-pattern, because all it does is mean that every function that is passed a Wrapper must have ?Sized + Trait bounds, even if that function does not use any methods from those traits. Why would you ever want to do this? It does not store the dictionaries with the object or provide those traits to the functions that use Wrapper in any way. I cannot think of a single useful reason to have trait bounds on a struct.


#6

Could you provide a compiling example to show what you mean? Specifically, converting Struct<T:Trait> to Struct<Trait> when T is sized? A trait object is not binary compatible with the object itself, so I’m not sure what you mean. I’d love to know what Wrapper<Trait> even means for stebalien’s struct definition.

Trait objects are not sized, so I am not sure what precisely you are claiming would work. Can’t convert to Wrapper<Trait> as the OP wanted, for example, since it’s not a valid type with the constraint. (Though, as I pointed out, I’m not sure that makes sense to begin with.)


#7

Adding the Sized bound to a trait object would make it implement the ‘size’ method. So you can validly ask for its size even though you have erased the type. Adding ?Sized means it does not have to implement ‘Sized’.

Converting from Wrapper<?Sized + Trait + 'a> to Wrapper<Trait + 'a> obviously is not possible, as “?Sized” removed the Sized constraint. If we make sizing explicit we are asking to cast: Wrapper<Trait> to Wrapper<Trait + Sized> so where is it supposed to get the ‘Sized’ dictionary from?


#8

I can already do this. See my other thread.

I thought this was an inherent problem in supporting &(Trait1+Trait2+Trait3). That is, to support some form of &(Trait1+Trait2+Trait3), we’d either need super-fat pointers, dynamic indirect vtables (i.e., dynamically allocated tables mapping traits to vtables), or “thin” traits. Unless I’m mistaken, all of these would allow this cast.


#9

The problem is ?Sized is the ‘absence of sized trait’ so its more like casting from &(Trait1) to &(Trait1 + Trait2), I don’t know how you can magic the extra dictionary out of thin air :slight_smile:


#10

What exactly are you referring to? I’m not trying to go from unsized to sized, I’m going from sized to unsized or unsized to unsized. That is, Struct<T: Trait + Sized> as Struct<Trait> or Struct<Trait+Markers> as Struct<Trait> which are are already possible. Unfortunately, there are currently two unsized types: trait objects and slices. This is the actual problem:

trait Trait {}

impl Trait for [u8] {}

fn main() {
    let a = b"abc";
    let b = a as &[u8];
    // b needs to be sized.
    let c = b as &Trait;
}

So, ?Sized is the wrong bound. However, I’ve found a solution (that requires the unstable unsized feature):

#![feature(unsize)]
use std::marker::Unsize;

trait Trait {}

impl Trait for i32 {}

struct View<'a>(&'a mut Wrapper<Trait + 'a>);

struct Wrapper<A: ?Sized + Trait>(A);

impl<T: ?Sized + Trait> Wrapper<T> {
    // I want this to work on both `Wrapper<SomeType>` and `Wrapper<Trait>`.
    fn view<'b>(&'b mut self) -> View<'b>
        where Self: Unsize<Wrapper<Trait + 'b>>
    {
        View(self as &mut Wrapper<Trait + 'b>)
    }
}

fn main() {
    let mut a = Wrapper(0i32);
    {
        a.view();
    }
    {
        let obj = &mut a as &mut Wrapper<Trait + Send>;
        obj.view();
    }
}

#11

What does ‘Unsize’ do?


#12

When compiling the original version at the top of the thread, I get:

13 |>         View(self as &mut Wrapper)
   |>              ^^^^
help: consider adding a `where T: std::marker::Sized` bound
note: required for the cast to the object type `Trait + 'a`

This suggests that the object must implement ‘Sized’ in order to be cast into a trait-object.

struct Wrapper<A: ?Sized + Trait>(A);

This tells us whatever is Wrapped does not need to have a size.

So this looks like trying to convert a possibly unsized “?Sized + Trait” into a sized “Trait + 'a”?


#13

A: Unsize<B> means that A can be unsized to B.

Triat + 'a isn’t sized and ?Sized + Trait isn’t a type (it’s a bound). To unsize (go from &T where T: Trait to &Trait), T either needs to implement Sized + Trait or be Trait + Markers; it can’t be some other unsized non-trait object (e.g., [u8]) and implement Trait.


#14

I thought thats what I said? (minus the bit about Trait + Markers):

In other words ‘T’ needs to be Sized, not ?Sized

This is the bit I don’t get, surely we need T to be Sized to be converted into a trait-object, so isn’t unsized doing the opposite of what we want?


#15

Not if it’s already a trait object. &Trait + Markers (or even just &Trait) can be “re-unsized” to &Trait. The problematic case was &slice as &Trait.


#16

It the reason you can convert a trait object ‘Trait + Sync’ to ‘Trait’ is because the built-in marker traits have no dictionary?


#17

Yes (assuming that “dictionary” means vtable). IIRC, this could technically work with custom marker traits (as long as they don’t implement any methods) but rust doesn’t currently allow you to do this).


#18

Wow, yes, you can! Can someone explain what that means? Specifically, given

struct Wrapper<A: ?Sized + Trait>(A);

what’s the binary representation of Wrapper<Trait>? It doesn’t seem to be a wrapped trait object. What exactly does, e.g. Wrapper(1u32) as Wrapper<Trait> create/do?


List of things to be explained in the documentation?
#19

It is a trait object, of sorts. &Wrapper<Trait> is a fat pointer where the vtable points to i32's vtable for Debug.

#![feature(raw)]
use std::fmt::Debug;
use std::raw::TraitObject;

impl Trait for i32 {}
trait Trait {}
struct Struct<T: ?Sized>(T);

fn main() {
    unsafe {
        let value1 = Struct(0i32);
        let value2 = 0i32;
        let obj = &value1 as &Struct<Debug>;
        let obj2 = &value2 as &Debug;
        let raw1: TraitObject = std::mem::transmute(obj);
        let raw2: TraitObject = std::mem::transmute(obj2);
        assert_eq!(raw1.vtable, raw2.vtable);
    }
}

#20

Wow, I love this! It’s like conceptually fmapping trait-object-referencing into the struct. But how general is this? Does this work for any struct parameterized by T that allows T:?Sized? Presumably not: what if the struct contains two instances of T? Is this (and its constraints) documented somewhere?