It does boil down to "there's a good reason", but the good reason isn't all that complicated.
Here's the problem. Imagine I've got a library crate:
// library.rs
pub struct Dog;
pub trait Speak {
fn speak(&self);
}
And two crates that use that library crate.
// bark.rs
extern crate library;
impl library::Speak for library::Dog {
fn speak(&self) {
println!("woof");
}
}
// woof.rs
extern crate library;
impl library::Speak for library::Dog {
fn speak(&self) {
println!("bark");
}
}
Now, for some reason, I want to use both of these libraries:
// main.rs
extern crate library;
extern crate woof;
extern crate bark;
fn main() {
let rex = library::Dog;
rex.speak();
}
What should this program output? There are two equally valid, indistinguishable implementations of library::Speak
for library::Dog
; there isn't a right answer. What's worse, if I depended on woof
originally, and added bark
later, my code would stop compiling, or - worse - start transparently doing the wrong thing. Conflicting trait impls are a Bad Thing?.
It gets worse when you add generics. If you have:
// barkgeneric.rs
extern crate library;
impl<T> library::Speak for T {
fn speak(&self) {
println!("woof");
}
}
// woofgeneric.rs
extern crate library;
impl<T> library::Speak for T {
fn speak(&self) {
println!("bark");
}
}
You now have an infinite number of conflicting trait impls. Whoops.
To avoid this problem, we have the orphan rules. The idea of the orphan rules is to make sure that any impl Trait for Type
has one, and only one, place it can be put. That way, we don't have to worry about impl conflicts; they should be straight-up impossible, if the orphan rules are set up correctly.
The rules boil down to: when you impl
a trait for a type, either the trait or the type has to come from the current crate. This makes all of my conflicting examples not work. woof.rs
can't implement library::speak
for library::Dog
, because it neither of them come from its crate.
Similarly, you can't impl<T> Index<Bounded> for [T; 4];
, because [T; 4]
doesn't come from your crate, and rustc
has decided that Index<Bounded>
doesn't count as coming from your crate either.
It does, however, let your impl Index<Bounded> for [i32; 4]
through, because in this case Index<Bounded>
does come from you. It's possible that's a bug, but it's also possible that it's just intended behavior; the orphan rules are slightly more complex than what I've stated here, and they might be interacting in weird ways.
For more specifics, see rustc --explain E0117
, rustc --explain E0210
.