Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why do the coherence rules raise the error "the type parameter must be used as the type parameter for some local type"?

Tags:

rust

Why does code example 1 compile but example 2 gives a compilation error?

Example 1:

use std::ops::Index;

struct Bounded {
    idx: usize,
}

impl Index<Bounded> for [i32; 4] {
    type Output = i32;

    fn index(&self, b: Bounded) -> &i32 {
        unsafe { self.get_unchecked(b.idx) }
    }
}

Example 2:

use std::ops::Index;

struct Bounded {
    idx: usize,
}

impl<T> Index<Bounded> for [T; 4] {
    type Output = T;

    fn index(&self, b: Bounded) -> &T {
        unsafe { self.get_unchecked(b.idx) }
    }
}
error[E0210]: type parameter `T` must be used as the type parameter for some local type (e.g. `MyStruct<T>`)
 --> src/main.rs:7:1
  |
7 | impl<T> Index<Bounded> for [T; 4] {
  | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type parameter `T` must be used as the type parameter for some local type
  |
  = note: only traits defined in the current crate can be implemented for a type parameter
like image 428
kmky Avatar asked Jun 29 '16 17:06

kmky


1 Answers

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.

like image 132
2 revs, 2 users 97% Avatar answered Oct 03 '22 13:10

2 revs, 2 users 97%