Sealed traits

I don't think type 3 wants to be a trait, especially if it wants the duck typed behavior you demonstrate. Instead, it wants some sort of static type set, where you accept any member of the type set and can use any functionality provided by all types in the set. Basically, a shortcut for monomorphizing over a known set of types. Naturally, this would be incompatible with dynamic dispatch. I'd suggest maybe static trait Q { u32, i32 } as a working name for the feature idea (or maybe enum trait is better?), but while related to sealed traits, due to the implications on type checking / method lookup, it's a much different feature.

But I have a fourth kind/usage of sealed traits: type three, but adding new implementations for non-downstream types is fine. Basically, going back to my assertion that the coherence implications is just impl<T in ::downstream> !Q for T.

An example use case for this would be FnPtr in std. This would allow std to have

#[sealed(for coherence)]
#[unstable = "implementation detail"]
trait FnPtr {
    fn as_addr(this: Self) -> usize;
}

impl_fn_ptr! {
    extern
        "Rust", "C", "system",
        "C-unwind", "system-unwind",
        // etc
    fn(A, B, C, D, E, F, G, H, I, J, K, L) -> Ret
}

impl<F: FnPtr> Debug for F { … }
impl<F: FnPtr> fmt::Function for F { … }
impl<F: FnPtr> PartialEq for F { … }
impl<F: FnPtr> Eq for F { … }
// etc

and macro copy/paste a single sealed trait implementation over the function pointer types which is used to blanket implement the traits available for function pointers. Without the coherence guarantee that downstream types won't be covered by this blanket implementation, they cannot implement any of the blanket implemented traits. However, adding new implementers which are not downstream of std is perfectly fine.

I'd argue that treating type 2 as "type 4" is also fine... but what type 2 actually wants imho is unsafe trait, where the unsafe requirement for implementing is don't.

I'll also argue that the case of "#[non_exhaustive] trait is similar to structs in that it could be served by a private item, but having a way to state the intent directly is also a good idea. (#[non_exhaustive] is an interesting way to spell it, since the first thing I think of is the enum meaning and not the struct one, and trait already is an open-world set compares to enum's closed-world set.)

5 Likes