rfcs icon indicating copy to clipboard operation
rfcs copied to clipboard

Named opt-in trait impls

Open crlf0710 opened this issue 1 year ago • 2 comments

We all know that from inference perspective, all impls are "global" if not excluded with coherence. So this is actually part of the "inference API" of a crate. (But not yet well documented by tools like rustdoc to this day, sadly)

I've seen many tragedies that are straightforward outcome of this.

But i feel we should have patching tools that move ourselves forward, if this is not solvable problem in the near future.

What if we could opt-out an impl from the implicit parts of inference?

Strawman proposal: Named opt-in trait impls.

#[optin_impl(u8_idx)]
impl Index<u8> for [T] {
      // impl
}

fn foo1() {
     dbg!([1,2,3][3u8]);    // Doesn't compile, because no impls is selectable
     dbg!([1,2,3][3usize]);    // Works fine as always, with `usize` as idx,
     dbg!([1,2,3][3]);    // Works fine as always, with `usize` as idx,
}

#[use_optin_impls(u8_idx, ...)]
fn foo2() {
     dbg!([1,2,3][3u8]);    // Works fine, with `u8` as idx,
     dbg!([1,2,3][3usize]);    // Works fine, with `usize` as idx,
     dbg!([1,2,3][3]);    // Doesn't compile, because two impls are both in effect     
}

crlf0710 avatar Apr 12 '24 07:04 crlf0710

#3634 is probably the same as this with a different syntax.

mod a {
    pub use impl<T> Index<u8> for [T] {
        ...
    }
}

mod b {
    fn foo2() {
        use ::a::{impl<T> Index<u8> for [T]};
        dbg!([1,2,3][3u8]); // ok
        dbg!([1,2,3][3usize]); // ok
        dbg!([1,2,3][3]); // error[E0277]: the type `[{integer}]` cannot be indexed by `i32`
    }
}

kennytm avatar May 25 '24 12:05 kennytm

It covers this one in terms of use-cases and the core idea seems pretty similar, yes. (I think you'd have to write use super::a::… to address the parallel module there.)

More specifically, the example here is roughly Using Scoped Implementations to Implement External Traits on External Types. Additionally, and while not part of the main RFC, Negative scoped implementations bikesheds some syntax to shadow an implementation without replacement to avoid the ambiguity above.

There are essentially two reasons I didn't attach names to implementations:

  • clear imports
  • the subsetting means broadening such an implementation doesn't have non-local effects

It has a few other benefits though, like less complicated semantics and that it's more natural to still apply all other coherence rules (besides the "orphan rule") even where these implementations are defined, which makes constructing an API a bit less error-prone.

Tamschi avatar May 25 '24 14:05 Tamschi