Collective bikeshed of `#[marker] trait Foo { .. }`

What about #[empty] or something similar because it is a guarantee the trait contains zero items? Could such a trait also be used as an additional constraint like dyn Trait + Empty?

I like the double meaning of trivial.

  • Mathematically: The impls are all identical because they’re trivial.
  • Colloquially: What about overlap? It’s trivial, who cares?!
4 Likes

This one seems pretty nice. Not too long and to the point. The only missing bit is impl, but that can be inferred?

That would work as a description of the trait, but it is redundant because the user can see that the trait contains zero items from the definition. It also leads to questions about why some trait Foo {} is not #[empty] and why the annotation is necessary at all. If we also allow items that can't be overridden in the trait, then #[empty] is no longer an apt description.

I like where this is heading I think, #[trivial] is in the same vein but I think I prefer #[trivial] out of these choices.

It does seem nice and I think it extends well to traits with non-overridable defaulted items.

I guess the question is if users will understand the implication wrt. overlap? I think unless we pick something like overlapping_impls_allowed then some users will have to look up the meaning of the attribute anyways (even if we have #[overlapping] so this may not be a problem in practice?

I think the innate quality that matters is that all implementations of the trait are known to be equivalent. The fact that this allows overlap falls out of this. The #[trivial]-ness of a trait is the property of the trait, and the fact that overlapping is possible is a result of that.

Annotating the result seems backwards. Annotating the property seems more right.

For the purpose of discussion, how (does?) this property interact with auto traits? I don’t think it influences the name, but it might influence the choice of attribute versus keyword.

I guess it’s a matter of how intrinsic the property is.

Of the other attributes implemented, which are breaking changes to add/remove from a type? The only one I know (excl #[derive]) is that #[non_exhaustive] is fine to remove and breaking to add. I assume #[trivial] would be fine to add and breaking to remove.

5 Likes

I defer to @scottmcm on this since they last touched the implementation.

Yes, I think that's accurate.

I'd definitely prefer one of the names with "overlap" in it.

  • all the alternative names like marker/empty/trivial/simple/basic all fail to convey to the reader what is actually being enabled by the attribute

  • as @Centril pointed out, those names create an awkward "not all empty traits are #[empty] traits" situation

  • the concept of "overlapping" impls is something you already need to understand for the orphan rule and specialization and so on.

This argument doesn't really work for me because overlapping is an opt-in feature. If allowing overlap was automatic and this attribute was about, say, making a public API commitment to never make the trait non-trivial so that client crates could also write overlapping impls, then this would be more persuasive.


As for which overlap-using name, I have no strong opinions, but I do want to throw out the additional paint color of #[impls_may_overlap] since I think there's some precedent for using may like this.


I always assumed the intended metaphor is that the type and trait are the "parents" of the impl, because an orphan impl is (very roughly) an impl where both "parents" are far away/in some other crate.

Then again, it's not like the type and trait created the impl then went to another crate to abandon it there before going back to their home crate(s). It's the other crate author that created the impl out of thin air without either parent's involvement, so I guess that's more like a "test tube baby impl"? Oh well.

2 Likes

auto traits are not implicitly #[marker], but can be. Positive and negative impls still conflict:

error[E0119]: conflicting implementations of trait `Foo` for type `u32`:
 --> src/lib.rs:7:1
  |
6 | impl<T:Copy> Foo for T {} 
  | ---------------------- first implementation here
7 | impl !Foo for u32 {}
  | ^^^^^^^^^^^^^^^^^ conflicting implementation for `u32`

#[impls_may_overlap] is very appropriate, since in English "may" historically has been used to convey permission.

1 Like

Rust itself also provides precedent for may in the form of #[must_use] in the sense that both may and must are modal verbs.

Do we think that impls_ is necessary or could we leave that inferred and shorten the name to #[may_overlap] perhaps? Here, the name in #[may_overlap] is 11 characters long and shorter than in #[overlappable] which is 12 characters long.

I have a very slight preference for including impl(s) because

  • this is a pretty small feature in the grand scheme of things and not likely to be written dozens of times in a row, so a long descriptive name wins by default

  • I feel like a user who’s brand new to all this trait system machinery would have a much easier time guessing what “impls may overlap” means on a trait than what “overlappable” means on a trait (there’s probably some plausible but wrong meaning you could invent for “overlapping traits”; just look at how many people thought “inherent trait impls” were a thing because of bare trait syntax)

1 Like

I am thinking that that is exactly what this attribute represents. "I as the API author recognize the trivialness of this Trait and am making a commitment to keep it Trivial so you as a client are free to create 3rd party implementations of this trait".

1 Like

I like this one a lot. If we're not comfortable with "trivial" or "simple", I like this one the best of anything suggested so far. Could possibly be shorted to just "may_overlap" as well.

2 Likes

I too like #[impls_may_overlap] best so far

The shorter #[may_overlap] sounds a little like it might be referring to memory overlapping somehow, which is of course nonsense for a trait, let alone an empty trait. This potential confusion is (small) cognitive load.

Longer still, but perhaps:

  • #[other_crates_may_impl]
  • #[any_crate_may_impl]

Or shorter by a couple of chars:

  • #[anyone_may_impl]
1 Like

This sounds REALLY good. I'd shorten even a bit more to:

  • #[any_may_impl]
1 Like

I'm not a huge fan of trivial because the meaning of an unsafe trait can be quite complex and nuanced, even if it has no overridable behaviour.

:+1: I think this is particularly true since it goes on the trait, of which there's only one, not on the impls, of which there are many.

2 Likes

third_party_impls?

1 Like

#[mere_proposition]

(Mostly joking.)

I think marker is a fine name, since we already call these marker traits.

I think if a trait has items that can’t be overridden, that trait is still a marker trait, because you can’t define any items in impls. I am also dubious that we would ever add this feature, since it adds complexity & irregularity and is semantically equivalent to just writing free functions.

I’m more interested in the possibility that there are other things we could do with marker traits (nothing in particular comes to mind), and I think limiting the name of this attribute to the particular behavioral change we’re introducing today would be shortsighted.

1 Like

Yes, but, what about traits that aren't "Marker" traits (i.e. have some default, non-overridable methods) that this should work with as well? I think that was the OP's point that "Marker" is misleading because it will (potentially) need to be used with non-marker traits; hence, the commentary around "simple/trivial" traits and a hierarchy of "Trait Complexity Kinds".

I specifically addressed this in my comment: