(NOT A CONTRIBUTION)
I read @nikomatsakis's post about "dyn* Trait" and I wanted to share some problems with this idea. I feel like we discussed something very similar several years ago, so I was surprised to see this post without trying to address these problems, which I remember were brought up then.
&dyn Trait
, for example, would becomedyn* Trait + ‘_
This is the big problem with this proposal. Given a trait object &dyn Trait
or &mut dyn Trait
there are two operant lifetimes, which have different implications for the moves and borrows that are allowed for this type. In particular, if you have &'a mut dyn Trait + 'b
, you are covariant in 'a
and invariant in 'b
. It is also important because you may need to constrain other lifetimes in your signature to be the same as either 'a
or 'b
, but obviously not both (those other lifetimes could be invariant also, for example: fn(&'b mut Type<'a>, &'c dyn Trait + 'a)
.
This means that given the signature of a trait object which is a reference, you need to be able to specify the reference lifetime separately from the lifetime of the trait object, and that reference needs to specify whether its mutable or not. This muddling with "trait views" (which seems like just more complexity to patch over the weaknesses of this proposal) doesn't solve the problem.
Let's say the final syntax does somehow have all of these knobs, but instead of composing reference types with an unsized trait object type (what we have now) you've got 3 (ref, mut ref, by value) new types with syntax for separately specifying these lifetimes (the by value one doesn't need it). How do they implement deref? What is the target type? And if they don't implement Deref, how do you Pin them, or use any other abstraction built on top of Deref?
As far as I can tell they would have to deref targeting the old dyn Trait types. So you aren't even getting rid of that type. You'll have to have both systems forever. You can't just shift over at an edition boundary.
The new types would have these two advantages, as far as I can tell:
- You can pack objects that are less than a word in size into the pointer type.
- You can pass self by-value as an argument and return type, thereby making more traits dyn safe.
I'm not very hyped about the first point, but I think the second of these is a very intriguing goal. I want to note that people already do create "trait objects" that implement dyn unsafe traits and so forth - they do so using Any downcasting or a special clone method they make implementers provide or the erased_serde bridge trait trick. People also do all kinds of representational tricks with their custom trait objects (anyhow using a thin pointer for example), and certainly can do small object optimisations like this if they really want to.
The problem that I see is that these things have to be done bespoke each time you want to do them, and that requires advanced understanding of unsafe code and the trait system and possibly exploitation of currently underspecified behavior. So rather than bake some aspects of them into a new type, I would ask: why aren't the tricks people do to create custom trait objects libraries that anyone can pull down and compose with their own trait? What abstractions would be needed to make that possible? I'm not sure there's an answer, but to me this seems like a more compelling avenue to find parsimonious additions to the language to support these use cases.