Proposal: Change terminology from "trait object" to "dynamic trait"

Given the pending stabilization of the new dyn Trait syntax, I think it’s time that we shift our terminology. I think we should start calling dyn Trait types dynamic trait types. Moreover, we should deprecate the term “object safe trait” and say “dyn-capable trait”. These changes have a few advantages:

  • It is relatively obvious from the syntax and helps explain what dyn stands for.
  • The existing “trait object” term feels suboptimal to me; it may confuse people into expecting something more OOP-like than they are getting. And anyway the “object”, in some sense, is the box more than the dyn Trait itself (that is, the Box<dyn Trait> type in its entirety is really the object).
  • “dyn-capable” just seems a lot more obvious to me than “object safe” – it expresses the idea that traits which accept some restrictions can be used in additional ways. It doesn’t confuse with memory safety or other forms of safety.
28 Likes

Going further: I would like to have an attribute that declares a trait to be dyn-capable. Something like:

#[dyn_capable]
trait Foo { ... }

It would then be a compilation error if that is not the case. We would lint (warn) if you do dyn Foo and Foo is not declared as dyn_capable.

The purpose of dyn_capable is then to declare your intention that Foo should be usable as an object, which implies accepting limitations on its design.

8 Likes

This feels like a good change :+1:

We need some way to refer to Box<dyn Foo> tho -- personally, I'd call it a "boxed dynamic existential Foo" but "boxed dynamic Foo" works and is shorter.

On #[dyn_capable]

This also sounds like a great idea :+1:

For reference, (which I'm sure you're aware of), the current way to do:

#[dyn_capable]
trait Foo { ... }

is:

trait Foo { ... }
fn assert_object_safe_foo(_: &Foo) {}

But the current method has the drawback of not being in the documentation, which #[dyn_capable] could / would be.

A different way to do #[dyn_capable] would be via "ConstraintKinds" and auto meta traits:

#[lang_item = "dyn"]
auto meta trait dyn {}

trait Foo {}
// This will be inferred automatically, but we can explicitly state it:
meta impl dyn for Foo {}
// ^-- This will be shown in documentation, and we can lint as well.

// General assertion that a trait is dyn capable:
fn assert_dyn_capable<trait C: dyn>();

#[test]
fn foo_dyn_capable() {
    assert_dyn_capable::<Foo>();
}

This however requires ConstraintKinds which we are far away from having. We can also desugar #[dyn_capable] into meta impl dyn for Foo {} at a later stage if need be, so the #[dyn_capable] proposal is forward compatible with this idea.

1 Like

Yeah, I'm looking for a better alternative. =) I wanted also to have the lint, so that if clients of your library are using your traits in dyn Form, they get warnings, which might lead them to open issues on your repo, might might in turn lead you to be aware that they are doing that. =)

Maybe we should actually have:

#[dyn_capable(no)]

as well, which artificially forbids the use of dyn Trait.

This way, for backwards compat reasons, trait Foo is sort of ambiguous without such a declaration (presuming it uses only dyn-capable features). Eventually, we can slowly migrate the lint to deny-by-default and so forth to "tighten the noose" there.

A worthwhile goal :+1:

On:

#[dyn_capable(no)]

I think that is even better.

#[dyn_capable(no)]

// As well as:

#[dyn_capable(yes)]
// or equivalently:
#[dyn_capable]

I really like the documenting + executable nature of this.

Might as well use true/false rather than yes/no? Or:

#[dyn_capable = true]
#[dyn_capable = false]
#[dyn_capable]

So you get the syntax highlighting at the same time (once attr literals are stable).

2 Likes

I like how "trait object" sounds like something you make from a trait, while "dynamic trait" sounds like a property of the trait, but that's confusing since the property is being dyn-capable. How about "trait dyn type" instead? It avoids putting an adjective before "trait" and the abbreviation evokes the syntax.

3 Likes

I don't like dyn capable (seems supe jargony to me) but I like ""dynamic trait," maybe its more of a "dynamic type" though, or a "dynamic trait type."

If we're going to require this on every trait, or even every public trait - and I don't think we should - I wouldn't want the syntax to be an attribute.

2 Likes

"Dyn capable" just sounds like a shorthand for "Can be used as a dynamic trait type" -- not sure why it sounds like jargon to you..?

Well... we could do something like:

// Using as Box<dyn Foo> is legal
dyn trait Foo {}

// Using as Box<dyn Foo> is illegal
trait Foo {}

but it feels too heavyweight (and will break a ton of code), especially if (totally not given) we're going to introduce meta traits in the future. Therefore, to me, attributes seem like the cheaper solution right now.

If we go with this dyn trait Foo {} syntax, it would also be a breaking change, so we would need to sneak it into edition 2018.

2 Likes

"Dyn" is short-hand and "X capable" is not really an adjective phrase that I can think of another example of. With "object-safe" at least I have "type-safe" and "memory-safe" to compare it to, which makes it feel less like a technical term I need to memorize. "Capable" is also less of a casual word than "safe."

3 Likes

Brainstorming… How about:

  • “dyn-safe” instead? #[dyn_trait_safe] or #[dyn_safe]
  • #[erasable] (type erasure)

If we stick with “object safe” we could do #[object_safe].

More brainstorming:

#[dynamic_trait]
trait Foo {}
#[dynamic]
trait Foo {}
#[dyn]
trait Foo {}

An argument in favor of attributes is that you don’t have to think about the order between dyn and unsafe (and possibly const) when defining the trait.

I hear you -- open to alternatives.

I believe this is what I wrote:

I think we should start calling dyn Trait types dynamic trait types.

I did not mean to imply it would be required on every trait. But what I was thinking is that any trait that you use with dyn would want to be marked #[dyn_capable], so that you know you are using it in the expected way.

I'm not sure how important it is though. I'd be happy to just start with having a way to "opt-in" to those checks. (And not necessarily connect it to uses of dyn.)

(Why wouldn't you want it to be an attribute? Attribute feels right to me.)

To elaborate: it doesn't affect the meaning of the trait items. It just imposes some restrictions on the trait's future trajectory.

I am not keen on dyn trait Foo because it makes it feel (to me) like the trait is "inherently dynamic" or something, or limited to dynamic trait types.

3 Likes

See, unsafe trait feels different -- it is something that implementors of the trait must be aware of (since they need an unsafe impl).

Similarly, const trait -- if we supported that -- would presumably mean that implementors must have a const impl (or all const fn, etc).

But dyn trait doesn't have that character. It is just about the trait definition (implementors can do whatever).

1 Like

That sonunds fine (though I think it should be very optional, analogous to an allow by default lint for example). But when you have dyn_capable(no) it sounds like dividing the world into two sets, one which can be used as objects and one which can't. That would mean it'd be idiomatic to put this on every trait you define ("to avoid the back compat hazard!"), at which point it shouldn't be an attribute.

My goal with #[dyn_capable(no)] was to allow you to opt-out from being dyn-capable -- even if you otherwise would be -- because you expect to maybe change the trait in incompatible ways in the future, e.g. by adding default fns that are generic or something. But maybe that's not necessary.

1 Like

+1 for name change.

dyn trait Foo {} seems OK. Or impl dyn for Foo; impl !dyn for Foo;

I’m not a fan of #[] syntax, which seems to be reserved for things that are complex or “oops, we forgot to reserve a keyword for this” escape hatch.

This is a compelling argument; unsafe and const have the character of being effects (or restrictions thereof) which dyn is not. Instead, dyn has an entirely different "category" -- traits of traits (meta traits).

It can often be that reason, yes; but in this case, I think dyn trait Foo {} is misleading for the reason @nikomatsakis voiced: that it makes the trait feel inherently dynamic (or limited to being dynamic).

I also dislike “dynamic trait” because that makes it sound like it’s a property of the trait, not a runtime polymorphic fat pointer to some type implementing that trait. In fact the whole point of adding dyn in front of the trait is to make it clear when we’re talking about the trait itself and when we’re talking about some thing that implements the interface defined by the trait. Calling the thing an “[adjective] trait” feels like re-encouraging the exact confusion that we just made this high-churn change to combat.

Unfortunately, I really have no idea what to call the thing other than a “trait object”. Part of me wants to say &dyn Trait is a “polymorphic reference”, but that doesn’t provide a name for the unsized dyn Trait type, and I don’t know how many people would assume “polymorphic” implies runtime polymorphic the way I do. “dynamic reference” sounds like the reference might sporadically change what it refers to, which is not at all what we want. “dynamic type” is… maybe okay? I have no idea how people would interpret that phrase. And any other term that doesn’t contain the word “dynamic” will probably be no better than “trait object”.

5 Likes