We used the term "double pointer" rather than "fat pointer", as it's more descriptive. The latest draft doesn't use the term at all, as we cut back on the details a bit.
Spitballing here (and it may totally be a bad idea), but how about:
- "annotated pointer"
I’d like to voice my opinion in favor of renaming object-safe traits to “dyn-compatible traits”, as suggested by @bluss. It explicitly names the feature that it is enabling (so it’s also googlable) and “compatible”, while being longer than “capable”, definitely feels like a more common word to me as a non-native speaker.
I can see the advantage of annotating dyn-compatible traits and would like an (opt-in) lint on it. But, maybe due to lack of experience, I’m not seeing the problem that this annotation solves as so widespread that it justifies new syntax.
@kornel said they don’t like the attribute syntax because it feels like it’s used for complex or after-thought things, but I interpret most attributes as friendly advice to the compiler, but nothing that fundamentally changes the semantics of the annotated thing. (derive being the mandatory exception from the rule) As such, I’d prefer an attribute #[dyn_compatible] over any new syntax like dyn trait Trait {}.
+1 to "dynamic trait" sounds like a subset of traits, not of types.
I think this is a great observation. Right now this is an important property that's only noted with an arcane incantation. (And to emphasize how arcane, I think the above isn't quite right, as it should be fn _assert_object_safe_foo to avoid "unused function" warnings.)
I quite like the notion of a warning that says "you used dyn on a trait that hasn't promised to stay dyn-capable". The logistics of making that not annoying sound difficult, but hopefully there are options. Especially given that there's no (stable) code using dyn yet, so whatever it is could be tied to the dyn syntax rather than to the "trait object" concept.
(Sketch of one possibility: Rust 2018 implicitly adds #[dyn(never)] to traits that don't have a dyn attribute, and supports that explicitly as well as #[dyn] for "dyn-safe". Then when using dyn, you get the warning if the trait is #[dyn(never)] -- which isn't the case for unannotated traits in Rust 2015. And this extends naturally to potentially one day offering #[dyn(always)] that makes it usable only as a "trait object", not as a bound.)
I’m much in favor of “dynamic type (of trait A)” and “polymorphic type (of trait A)” for dyn A. If Box is a mapping between types, then dyn A must be a type. One may see a type as a set/class of values (disjoint from all other types) and a set of operations supported on these values. Furthermore, dyn is a mapping that turns a type class A into the type dyn A. This type dyn A belongs to the static type system, and thus is statically typed, but its method access operator performs runtime dispatch. In this regard one may see dyn A as an interface between the static type system and a dynamic type system.
Yes, roughly the following mapping, as noted above (using Haskell notation and modulo memory representation, sizedness):
data Dyn (trait :: * -> Constraint) =
forall (ty :: *). (trait ty :: Constraint) => Dyn ty
But there are no dynamic type systems (unless this refers to some dependently typed scheme where values refine types, but that is wholly different) as type systems are syntactic methods. The word static in static type system is therefore redundant.
As long as we qualify things as dynamic trait type, it feels OK to me, but dynamic type alone becomes misleading.
Doesn't for denote universal quantification? (cf. for<'a> fn (&'a T) -> &'a U) There doesn't seem to be a keyword for existential quantification yet.
An interesting question; and it does, as you can put any T inside of that Box so long as T: Trait.
However, the binder for<T: Trait> is not something you can apply any type to from outside the Box<T>, so operationally, if you were to pattern match on Box<for<T: Trait> T> (or use the methods of the type), you wouldn't know which specific T it is. Instead, you only know that there is some T such that T: Trait.
So in this sense, Box::new is indeed universally quantified, but once that is done, and you have a Box<for<T: Trait> T>, there is no way to know what T it was anymore.
This is exactly what happens with Box<dyn Trait>, you can put any T: Trait you like in there, but once you've done that, you don't know which T it was.
For more details, and probably better explanations, see:
I know what you meant. I was just pointing out your choice of keyword was rather poor.
for<'a> fn (&'a u32) -> &'a u32 is a universally-quantified type: a holder of a value of that type can choose any concrete lifetime 'lt, obtain a fn (&'lt u32) -> &'lt u32 and call it.
By a similar token, 'I can put any T inside the Box’ can be understood as Box::new having type for<T> fn (T) -> Box<T> (although this is not a valid type in current Rust); whoever can access Box::new can choose a concrete type U and obtain a fn (U) -> Box<U>. (Inside the body of Box::new, however, T behaves like an existential type.)
One of the operations that can be done with such a Box<U> is moving out, which returns a U (assuming U: Sized). In particular, if U = for<T: Trait> T (which by some miracle is Sized), the owner of a Box<for <T: Trait> T> would get hold of a for<T: Trait> T, and should be able to choose any type T meeting Trait to obtain a value of that very type.
As for the names themselves… given that the defining feature of dyn is dynamic dispatch, not so much ‘dynamic typing’ (however it is understood), why not refer to that? Call dyn Trait a 'dynamically-dispatched type, bound by Trait', while Trait itself can be called ‘dynamically dispatchable’ or ‘dynamizable’ whenever dyn Trait makes sense.
If you think ‘dynamically-dispatched type’ is too long, you need not use that name all the time. It can be shortened to just ’dyn type’ and everyone should still understand it.
OK; that was not clear tho from your comment. I did a direct translation from Haskell; we could use exists<T> for clarity, but I wanted to be more faithful to the actual Haskell encoding of dyn. We do have for<'a> today, so it seemed a more natural and smaller delta ![]()
Indeed it can, as generic functions in both Rust and Haskell (both being based in various ways on System F) have explicit or implicit type lambdas which correspond to the for<T> quantifiers.
My goal was not to set down a semantics of what Box<for<T: Trait> T> exactly means here... Only to illustrate how dyn is existential quantification and what it would correspond to in Haskell.
Your comments about being able to get out a for<T: Trait> T sounds to me as more related to RankNTypes than ExistentialQuantification. The compiler would of course need to ensure that you are not able to get out a universally quantified for<T: Trait> T, from which the user can pick any T from, to preserve soundness (which is what dyn Trait does). In retrospect, perhaps Box<exists<T: Trait> T> would have been clearer to distinguish between these concepts.
That seems OK.
Elaborating more on why I think "dynamically dispatched trait type" is the best proposal so far, let's start with a maximally descriptive and unambiguous phrasing:
Box<dyn Foo>is a boxed, dynamically dispatched, existential type supporting the operations of the traitFoo.
This is quite packed with theory, so we try to eliminate extraneous details. The language informs us on what is important by having the dyn keyword as a syntactic cue.
Box<dyn Foo>is a (boxed), dynamically dispatched, (existential) type (supporting the operations of the) trait (Foo).
And so we get:
Box<dyn Foo>is a dynamically dispatched type [..] trait.
We reorder to get a proper sentence:
Box<dyn Foo>is a dynamically dispatched (trait) type.
The term is somewhat long, but I believe it preserves the most important details whilst removing the, in the context of teaching dyn Trait, contextually less important aspects. Removing more details such as "trait" or "dispatched" becomes too ambiguous. As @felix.s suggested, but slightly modified here, we can shorten this to dyn trait type when among a more expert audience.
As opposed to adding a directive #[dyn_capable], could we not just have a marker trait like Send or Sync?
trait DynTrait: Dynamic {}
This could be automatically inferred as well, but the warning would appear if it’s not specifically said to require Dynamic.
I’m not a fan of new syntax as it can get confusing quickly when you have too much of it.
How about “trait-constrained reference”, “trait-constrained box”? “trait-constrained reference” emphasizes the fact that the reference must implement the trait.
With RFC 1909 we will be able to take some dyn types by value. The "trait-constrained X" terminology starts to get really confusing with values because traditional generics are already "trait-constrained". Additionally, I think its important the the phrase we use at least have the word "dynamic" in it to match the keyword.
The “trait-constrained X” terminology starts to get really confusing with values because traditional generics are already “trait-constrained”. Additionally, I think its important the the phrase we use at least have the word “dynamic” in it to match the keyword.
How about "dynamic generics" as opposed to "static generics"? I personally find "dynamic types" to be too easily confused with dynamic typing.
Edit: as in "dynamic generic type" for dyn Foo, "dynamic generic reference" for & Foo, etc. where trait Foo . Further, for<T: Foo> T would be a "static generic type" and for<T: Foo> & Foo a "static generic reference" and both imply monomorphisation.
We already have lifetime generics and type generics, and will soon have const generics, all of which are static, so personally I'd like to avoid using "generics" for anything that isn't static.
This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.