Pre-pre-RFC: Anti-trait-object auto trait

Object safety is a (necessary) thorn in my side, because I work with unsized types regularly (so using where Self: Sized to make a trait object-safe means these unsized types do not have these trait methods). These types are not trait objects. They are unsized types like str, [T], and most importantly, extern types.

My understanding of object safety is that where Self: Sized is really just a blunt hammer for checking whether or not Self is a trait object. If some trait existed (say, IsNotTraitObject) that was (auto) implemented for all non-trait objects, this could be relaxed to where Self: IsNotTraitObject. This would allow unsized types (that aren’t trait objects, like extern types) to implement trait methods, and would really simplify my life (and hopefully the lives of my fellow FFI developers).

I don’t want to get hung up on the bikeshed-able details (e.g., naming, whether this should be an IsTraitObject or IsNotTraitObject trait (with a negative or positive where clause), etc.).

Have there been any past attempts at pursuing something like this? Are there any pitfalls I’m overlooking? I’d like to draft an RFC for this, but I’d like to test the waters first…

3 Likes

Self: Sized is needed whenever you try to manipulate an instance of Self by-value. You might be looking for the unsized rvalues RFC, which would make it possible to lift this restriction in some cases.

Actually, that’s separate from object safety. A trait method can take self by value and still be considered object safe (RFC 255 explicitly mentions this).

There are other situations that require where Self: Sized that don’t involve taking Self-by-value:

trait ThisIsNotObjectSafe {
  fn foo();
  fn bar<T>(&self);
}

Both of those methods above make the trait non-object-safe, and both require using where Self: Sized as an escape hatch. I’m interested in solving this situation, because Sized is an overly broad requirement.

I just realized an alternative solution is possible if RFC 1834 (“Type inequality constraints in where clauses”) is accepted and implemented. Object safety could be satisfied for trait T by using where Self != dyn T. I actually find this preferable to introducing a new (arguably hacky) trait.

I agree, this is something I've thought of before. Self: Sized is overly restrictive, and disallows unsized types that aren't the trait object.

Interestingly, that would allow other trait object types besides dyn Trait to implement the method.

One question: would the method be callable on dyn Subtrait, where Subtrait requires Self: Trait?

That's a good point, and one I overlooked. Perhaps RFC 2580 ("Pointer metadata & VTable") could help (assuming it's accepted and implemented). Instead of checking where Self != dyn Trait, how about where <Self as Pointee>::Metadata != &'static VTable? Downsides:

  1. It's a bit verbose, ugly, and unintuitive.
  2. Is it unnecessarily broad? It disallows other trait object types from implementing Trait.

Personally, I'm okay with these downsides. Downside 1 is kinda moot, because if RFCs 1834 and 2580 are accepted, this syntax should be valid (and adding a prettier veneer is a bikeshed-able thing that can always be done later). Downside 2 isn't a big deal, since this is less broad than where Self: Sized and isn't any more broad than my original proposal.

I guess the only types that we definitely have to exclude from having the method are the dyn Trait, dyn SubTrait, dyn Trait + OtherTrait. Essentially, any trait object type with that method. So dyn SuperTrait and dyn OtherTrait are fine.

My first idea would be a marker trait that takes dyn Trait as a type parameter. It would be implemented by everything except dyn Trait and any other trait object type that has access to its methods. Working name: IsNotDynWithMethodsOf<T>.

Hopefully we can come up with a name no longer than DispatchFromDyn, because otherwise we’d have to refactor lang_items.rs again :smile:

Are there any complications with using dyn Trait as a type parameter before its object safety has been fully established? Trait is only object safe if its where clauses establish that, but its where clauses require Trait to be object safe because dyn Trait is used. It seems like a chicken-and-egg problem.

Oh yeah, good point. It looks like anytime you use the dyn Trait type as a type argument, the compiler requires that Trait be object safe. Example: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=f3d716560d6302a88571aa5f2ab54e93

The approach I suggested wouldn’t work unless that changed. I think it would change when https://github.com/withoutboats/rfcs/blob/object-safe-for-dispatch/text/2027-object_safe_for_dispatch.md is implemented though.

Oh excellent, I wasn't aware of that RFC. That RFC alone will solve a ton of my problems.

I'll draft an RFC for IsNotDynWithMethodsOf<T>. Hopefully I'll finish it by the new year and post it here for review.

1 Like

Okay, my RFC draft is nearly done. Some differences in my draft from what’s been mentioned in this discussion:

  • I used the name ExcludesDynType instead of IsNotDynWithMethodsOf. I mentioned the latter as an alternative, but I went with the former because it’s not longer than DispatchFromDyn :slight_smile:.
  • I also mentioned that dyn SubTrait + ... should not implement the trait.

Edit: Pre-RFC draft has been posted.

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