Marker traits in dyn trait bounds

Currently, trait objects cannot use more than one non-auto trait in the trait bounds. This includes marker traits which have no methods. Why is this restriction in place? It seems to be redundant: the main feature of auto traits is that they are automatically derived for complex types, and that is unlikely to have any relevance for trait objects. Since the marker traits have no methods, they also shouldn't affect the vtable layout in any way.

The only reason for the exclusion of marker traits that I could think of (besides no one writing an RFC for that) is backwards compatibility: auto traits are guaranteed to have no methods and not affect the vtable, while marker traits could potentially add methods, making dyn Trait + Marker invalid and breaking backwards compatibility. However, adding required methods is already backwards incompatible, and it is hard to imagine a defaulted method on a marker trait. In the rare case that such a method makes sense, it could be implemented on an extension trait Extension: Marker.

The place where I was specifically bitten by that restriction is rand. It provides a marker trait CryptoRng which adds an additional security guarantee for RNG. However, the crate doesn't provide a trait Csrng: RngCore + CryptoRng, which means that I can't accept something like &mut dyn RngCore + CryptoRng in my functions. I would want to keep the type safety, but use the trait objects to avoiding generic code bloat and overcomplicated API.

Of course, I could define the specific trait myself, but there is no good place to put it, so I would need to duplicate it my every crate that uses rand. Besides code duplication, this could cause API issues if such types are used publicly. In principle, it could be PRed into rand itself, but the general problem remains: there are many marker traits, and it's unreasonable to expect that all their possible combinations would have separate traits just to make trait objects possible.

TL;DR: would it be possible to make dyn Trait + Marker syntax work with marker trait Marker and arbitrary Trait?

FYI, on an unreleased/future version of the crate, such a trait exists: https://rust-random.github.io/rand/rand_core/trait.CryptoRngCore.html

Edit: Don’t ask me why it doesn’t actually have a CryptoRng supertrait bound…

3 Likes

I thought for sure I'd seen this mentioned before, but I couldn't find it anywhere obvious.

But I agree, we should do this. IIRC there was even straw poll happiness with it when it was brought up in passing in a previous lang team backlog bonanza.

(And initially it should probably be that this works with both auto traits and marker traits, but I think ideally we'd eventually just make Send and Sync and whatever else currently works into #[marker] traits to simplify the rule.)

Not really a fan of magic attributes. Is there a reason why it couldn't work with arbitrary marker traits (no methods and only marker supertraits possible)?

A new attribute also wouldn't solve the issue for anyone using old versions of crates, or crates which aren't up to date on latest lang changes.

Same reason that there's repr(transparent) rather than guaranteeing it for structs that happen to just be one field -- it's better to say what you want directly and have the compiler help ensure you respect it. It would be a major footgun for adding a method with a provided body to a trait to suddenly be a breaking change. (Kinda like how it's really easy to accidentally stop being dyn-safe -- example BuildHasher is no longer object safe in 1.55 · Issue #87991 · rust-lang/rust · GitHub -- and I wish the trait owner had to allow that, perhaps like Add a new syntax to declare that a trait must always be object-safe by RDambrosio016 · Pull Request #3022 · rust-lang/rfcs · GitHub )

See Tracking issue for allowing overlapping implementations for marker trait · Issue #29864 · rust-lang/rust · GitHub for more about why the design was changed from "any trait with no associated items counts as marker" to "you need to put an attribute".

1 Like

Hmm, it seems like the #[marker] annotation suggested in that feature is in direct contradiction with what I propose, thus can't be used here. That feature issue discusses overlapping impls, and there is indeed a problem with allowing unconstrained impls. Quoting Ralf Jung, "Likely none of the existing auto traits can be made #[marker] , but certainly not Unpin". It seems that permitting overlapping impls would indeed mean that in many cases safety could be violated, since there would be no way to prevent unsound or buggy impls.

However, the feature that I'm talking about doesn't seem to suffer from such problems, and thus I would expect all auto traits to be markers (as well as many userspace marker traits). Indeed, allowing dyn Trait + Marker doesn't really affect the expressivity of the language, since I can always do

trait Merged: Trait + Marker {}
impl<T: Trait + Marker> Merged for T {}

// now use `dyn Merged`

even if I don't own either of the traits Trait and Merge. The safety or backwards compatibility doesn't seem to be affected, rather it's a usability feature which guarantees that you can easily use trait object with markers even if the crate author didn't provide all possible combinations as separate traits. Indeed, they may have no such possibility, since Trait and Marker may be defined in separate crates.

While being explicit is indeed a virtue, in this case I worry that many crates will never be updated to use that feature. For example, using a new attribute would require a MSRV bump, which may be undesirable for a number of reasons. And that's without even considering that many crates may be almost abandoned, at best receiving a minor update once a couple of years.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.