Blog post: Extended Enums and Thin Traits

I think that proposal has a lot going for it from a technical perspective, but the ergonomics are quite lacking, and it seems likely to lead to interoperability problems. Details to follow.

From an ergonomics point of view, pushing information onto the reference is also pushing complexity to the consumers of a library. I find it generally makes things feel more complex, since you are confronted with fat-ness and thin-ness at every use of a trait. The names in that proposal were also somewhat consuming: for example, &Fat<Trait> actually gives you a thin pointer. Finally, there is no precedent in Rust for a distinction like Fat<T,U> and Fat<U>, where omitting the type argument corresponds to something quite different (i.e., not a default), nor for using a “pseudo-type” like Fat that seems to really be a kind of keyword.

It also seems likely to me that having some references to traits be thin and some references be fat will lead to composability concerns, where one library is requesting a &Trait and another has a &Fat<Trait> (or vice versa). It is certainly not possible to convert from fat-to-thin, though the other direction problem does work.

It’s not clear to me that mixing fat and thin is a big use case to begin with. For the use cases I am aware of, one usually wants all objects from a trait to be thin or not thin, not some ad-hoc mixture. In general, I consider thin pointers to be somewhat niche, particularly across crates like this – typically in the downstream crate, you know the actual type concretely, and you only employ the trait to kind of thread information upstream into a more generic context. Alternatively, as might be the case for Servo, you are spreading information across crates as much for convenience as anything else, but you still expect all of the trait objects (in this case, dom nodes) to be interoperable within the same data structures.

Finally, the thin trait proposal still allows for one to take an existing fat trait and make thin references from it. You can do it by making a thin trait that extends the fat trait, but adds no methods:

trait Foo { ... }

#[repr(thin)]
trait ThinFoo: Foo { }

Now one can have &ThinFoo for thin pointers and &Foo for fat pointers. I believe there is no technical obstacle to this working. (Of course, it assumes we get upcasting working for trait objects, but there are many reasons that we would like that to work.)

1 Like