struct LocalStruct;
// OK:
impl another_crate::ForeginTrait for LocalStruct {}
// OK:
impl another_crate::ForeginTrait for Box<LocalStruct> {}
// error[E0117]: only traits defined in the current crate
// can be implemented for types defined outside of the
// crate:
impl another_crate::ForeginTrait for Vec<LocalStruct> {}
I'm trying to understand why the last case (impl Foreign for Vec<Local>) is forbidden.
I see why technically it violates the orphan rules: "At least one of the types T0..=Tn must be a local type", which in my case doesn't happen.
But is it fundamentally wrong?
Like, does it risk creating incoherence? I don't see why it would. IIUC the only other implementation that could conflict with it is impl<T> Foregin for Vec<T> which can only come from another_crate.
another_crate could implement impl<T> another_crate::ForeignTrait for Vec<T> where T: another_crate::ForeignTrait {}, which would be in conflict with your implementation. The orphan rules are there to prevent random breakage of your crate because such an implementation is added in a future, non-breaking release of another_create.
I believe we do eventually need a mechanism in Rust for another_crate to be able to explicitly declare something like “I won’t ever implement ForeignTrait for Vec<T> myself, any downstream crate is allowed to do so for their own local type(s) T”.
Coherence & orphan rules in Rust serve multiple purposes. On one hand it’s about preventing overlaps (or breakage) when combining unrelated crates. On the other hand, it’s explicitly also related to semver, i.e. allowing a crate to add (non-blanket-) trait impls later is explicitly a goal.
I’ve recently come across this listing of some fundamental principles that I liked:
Axiom 1: crates upstream to you should be able to implement their own traits for their own types
Axiom 2: crates downstream from you should be able to implement your traits
Property: Upstream crates must assume that downstream crates will add any impls that compile. Downstream crates are allowed to assume that upstream crates will not add any semver incompatible impls.
Thank you @jofas and @quinedot. I didn't know that it's considered non-breaking change.
My understanding of SemVer compatibility in traits was based on this sentence from from Rust for Rustaceans: (Ch 3: Designing Interfaces > Trait Implementations p.51)
Since we do not know what implementations downstream code may have added, adding a blanket implementation of an existing trait is generally a breaking change. The same holds true for implementing a foreign trait for an existing type, or an existing trait for a foreign type—in both cases, the owner of the foreign trait or type may simultaneously add a conflicting implementation, so this must be a breaking change.
But reading this again (and taking what @steffahn said into account), I see that impl<T> Foregin for Vec<T> isn't a blanket implementation so this doesn't apply.
The definition of what is or isn’t a (breaking) “blanket implementation” in the context of semver is definitely subtle and not obvious. Of course clear cases like impl<T> LocalTrait for T [where …] are rather obvious;
but something like impl<T> From<T> for Mutex<T>[1] for instance is a blanket implementation because the T parameter in From<T> is “uncovered” [see also “fundamental types”].[2] This is because without this implementation in place, orphan rules would allow downstream crates impls such as impl From<Local> for Mutex<Local>. Another interesting aspect about this is that a blanket impl of this form can be part of crates that don’t contain the trait itself, i.e. impl<T> ForeignTrait<T> for Local<T>. The only non-breaking way to add an impl<T> ForeignTrait<T> for Local<T> to a crate’s API is to add this impl at the same time as when the type Local itself is added.
I wrote a bit about the motivations and negative reasoning recently here. The ability to reason about what implementations won't be added (negative reasoning) is also based on the orphan rules, basically.
As far as I know the tenative plan is still to allow negative implementations which act as such a promise (and then add them to coherence).
Sometimes there's also talk of relaxing the orphan rules for bin/leaf crates (but I'm unaware of an actual initiative).
Negative implementations for coherence would solve the problem that’s of the form “my blanket implementation conflicts with my concrete implementation, because one of my dependencies could implement another trait making the blanket implementation’s bounds apply”, by allowing the dependency to promise that implementation won’t ever exist, but they do not solve the problem in the OP, which is where another_crate would give up the option of writing a particular implementation so that my_crate can write that implementation instead. Negative implementations would be saying that nobody gets to write that implementation, which is no better.