One potential alternative option would be to allow users to tag the implementation of the trait with a name, and then require users to explicitly import the implementation of the trait in the module where they want to use the implementation. Using the string addition example in the RFC, libstd would add impl blocks to str like so:
// In `libstd`
impl Add<core::str> as StringAddition for core::str {
// ^^ ^^^^^^^^^^^^^^
// This implementation of `Add` is given the name `StringAddition`, which must be explicitly
// imported by the user in order to actually work. `as` is being used here to associate a name
// with an implementation.
type Output = String;
fn add(&self, other: &Self) -> String {
// ...
}
}
impl core::str as StringToOwned {
// ^^ ^^^^^^^^^^^^^
// Here, the `impl` block itself is being namespaced as `StringToOwned`. Again, `as` is being
// used to associate the name and the user would need to explicitly import this implementation.
fn to_owned(&self) -> String {
// ...
}
}
// In user code
// Without this import, the implementations of `Add` and `to_owned` for `str` in `libstd`
// effectively don't exist. Attempting to use them would result in an error.
use std::{StringAddition, StringToOwned};
Doing that would let the user sidestep the orphan rules, without risking code breaking because another, seemingly unrelated crate was included.