Min min specialization: replaceable impls?

To hopefully bypass syntax bikeshed, I'm using an attribute #[replaceable] to mark replaceable impls here. The hope is that #[replaceable] should be able to straightforwardly evolve forwards-compatibly to be expressed in terms of a proc macro attribute on top of today's feature(min_specialization) but be easier to show sound and stabilize sooner.

In short: allow writing #[replaceable] impl<T, ⋯> Trait for T { ⋯ } where the blanket impl is "replaceable" by a specific implementation.

I will use the following definitions from RFC#2451:

Local Trait

A trait which was defined in the current crate. Whether a trait is local or not has nothing to do with type parameters. Given trait Foo<T, U>, Foo is always local, regardless of the types used for T or U.

Local Type

A struct, enum, or union which was defined in the current crate. This is not affected by type parameters. struct Foo is considered local, but Vec<Foo> is not. LocalType<ForeignType> is local. Type aliases and trait aliases do not affect locality.

Covered Type

A type which appears as a parameter to another type. For example, T is uncovered, but the T in Vec<T> is covered. This is only relevant for type parameters.

In some more detail:

  • #[replaceable] impl Trait
    • Trait must be a local trait
    • Self must be an uncovered generic
    • Trait must only contain functions, and these functions cannot have defaulted bodies[1]
    • Must not overlap another #[replaceable] impl for the same Trait
  • Other impl Trait
    • May overlap a #[replaceable] impl but only if for a local type
      • Conflicts with #[replaceable] impl otherwise (for uncovered nonlocal types)
    • Must be complete as if the replaceable implementation did not exist
  • Trait lookup
    • When asking if a type implements Trait, we can now get two providing impls: take the one not marked #[replaceable]
    • Auto(de)ref lookup order happens normally, so a replaceable impl can still be chosen before a normal impl if it is for an earlier type in the lookup chain

If I understand default correctly, this should behave just as a highly restricted form of making an implementation where all members are marked default.

The point is that this is a even more minimal subset of specialization which is even more obviously sound and easier to understand, mainly as the "always applicable" rule is replaced with the simpler "local type" rule. In a similar vein, the restriction of default method bodies in the trait is so that there is no question about which function gets used if a replacement implementation relies on the default implementation (since it could potentially come from the one in the trait or the #[replaceable] impl).


I have two specific questions for the irlo crowd:

  • Is this a proper forwards-compatible subset of min_specialization, such that #[replaceable] could be replaced with a simple token transformation using default in the future?
  • Is this a meaningful enough subset of min_specialization that a) it could be stabilized sooner and b) it's worth stabilizing sooner?

I think it's possible to incrementally relax #[replaceable] to the always applicable rule, allow partial replacement, etc all the way to default, but I'm not certain.


  1. This restriction exists solely to make #[replaceable] relaxable to a procedural macro attribute which only sees the TokenStream, as the attribute cannot mark functions not present in the impl it sees as default. Additionally, this makes it so that the replacement implementation also specifies every function, so there is no question ↩ī¸Ž

6 Likes