Non_exhaustive on trait impls

Currently, rust has the "only one impl" rule, which can cause breakages in type inference if a crate adds a second implementation of a trait.

There are often talks of disabling this via an attribute, and I think putting #[non_exhaustive] on a trait impl block wouldn't be the worst idea of how to do this.

18 Likes

That's...remarkable simple. It might just work.

6 Likes

I'm fairly certain this would work as long as

  • the impl is visible only¹ in the current crate (because otherwise impls from parallel dependencies could cause ambiguity) and
  • the impl is "weak" vs. any non-#[non_exhaustive] impl (because of unsafe assumptions that are possible using multiple bounds or TypeId)².

¹ An explicit crate-wide import would be possible though.

² so it could result in an implicit behaviour change, which unfortunately seems to make this unsuitable for serialisation traits.

I think this is interesting because crate private impls would also allow at the least some (unsafe) convenience implementations for crates that would normally be allowed to have the respective global ones.

1 Like

I forgot, there's one caveat where something that is currently sound would become unsound:

// lib.rs
pub struct Type;
pub trait Trait { }
pub fn assumption<T: Trait>() {
    use std::any::TypeId;

    unsafe {
        //SAFETY: `Type` doesn't implement `Trait`.
        if TypeId::of::<T>() == TypeId::of::<Type>() {
            std::hint::unreachable_unchecked();
        }
    }
}

This becomes unsound if a downstream crate can implement Trait for Type and call assumption::<Type> that way. (Either Trait or Type could also be from a third crate further upstream in this example.)

This is one of the reasons (that iirc someone else found) that my proposal a while back "bakes" a distinct type identity whenever a non-global implementation is captured.
(The other two reasons are distinct cases in which keyed containers would misbehave. Note that that RFC is unsound too though, as I haven't gotten around to revising it to account for those multi-trait cross-impl assumptions possible through e.g. multiple bounds on one type parameter.)