About trait generic method bounds propagation to impls

Let's get straightforward, here's the definition for std::iter::Extend:

/// code-listing-1

pub trait Extend<A> {
    fn extend<T>(&mut self, iter: T)
    where
        T: IntoIterator<Item = A>;

    fn extend_one(&mut self, item: A) { ... }
    fn extend_reserve(&mut self, additional: usize) { ... }
}

With this definition, I went to implement it for a custom type as follow:

/// code-listing-2

   1   │ struct Foo;
   2   │ 
   3   │ impl std::ops::Deref for Foo {
   4   │     type Target = str;
   5   │     fn deref(&self) -> &Self::Target {
   6   │         "foo"
   7   │     }
   8   │ }
   9   │ 
  10   │ impl std::iter::Extend<Foo> for String {
  11   │     fn extend<I>(&mut self, iter: I) {
  12   │         //iter.into_iter().for_each(|f| self.push_str(&f));
  13   │     }
  14   │ }

Case 1.

The following code compiles, so it seems that the trait bound T: IntoIterator<Item = A> from std::iter::Extend::extend propagates to the impl.

/// code-listing-3

let once = std::iter::once<Foo>(Foo);
let string = String::new();
string.extend(once);
// Error: Foo is not an Iterator
// string.extend(Foo); 

Case 2.

Uncommenting line 12 in code-listing-2 caused compilation failure of code-listing-3. So it seems that the trait bound T: IntoIterator<Item = A> from std::iter::Extend::extend does not propagates to the impl, which is contradictive to the observation from case 1.

error[E0599]: the method `into_iter` exists for type parameter `I`, but its trait bounds were not satisfied
  --> test.rs:12:14
   |
12 |         iter.into_iter().for_each(|f| self.push_str(&f));
   |              ^^^^^^^^^ method cannot be called on `I` due to unsatisfied trait bounds

Case 3.

Adding the trait bound I: std::iter::IntoIterator in the method impl:

  10   │ impl std::iter::Extend<Foo> for String {
  11   │     fn extend<I: std::iter::IntoIterator>(&mut self, iter: I) {
  12   │         iter.into_iter().for_each(|f| self.push_str(&f));
  13   │     }
  14   │ }

the code still fails to compile

error[E0308]: mismatched types
  --> test.rs:12:53
   |
12 |         iter.into_iter().for_each(|f| self.push_str(&f));
   |                                                     ^^ expected `str`, found associated type
   |
   = note: expected reference `&str`
              found reference `&<I as IntoIterator>::Item`

Seems that the bound on the associated type Item = A was not inferred, and indeed adding the bound on the associated type make the code compile.

  10   │ impl std::iter::Extend<Foo> for String {
  11   │     fn extend<I: std::iter::IntoIterator<Item = Foo>>(&mut self, iter: I) {
  12   │         iter.into_iter().for_each(|f| self.push_str(&f));
  13   │     }
  14   │ }

Questions

So my questions are:

  1. Do bounds on trait method generic type parameter propagate to implementors?
  2. Do bounds on associated type of trait method generic type parameter propagate to implementors?
  3. Are the observations from the cases limitations of the current compiler or of deliberate design ?

Thank you for reaching here !

I've googled around for some bit, but did not find something related to my questions. It would be of great help if someone could point me to related documentations. Thank you!

Why would that imply "propagation"? That simply compiles because the impl Extend<Foo> for String exists.

That is correct. You can only use bounds you declare. There's no "propagation" going on at all.

That is wrong. There's no contradiction.

I think your problem is that you are allowed to impl a trait method with weaker requirements than what's specified by the trait. If there's a bound on the method on the trait declaration, but you omit it from the impl, that simply means you don't need the requirements represented by that bound for providing an implementation. It doesn't mean that the bound magically appears there.

@H2CO3 in case 1, if I try the following code, the compilation fails, which seems the bound T: IntoIterator<Item = A> is required although I did not specify it.

string.extend(Foo)
error[E0277]: `Foo` is not an iterator
  --> test.rs:19:14
   |
19 |     s.extend(Foo);
   |              ^^^ `Foo` is not an iterator
   |
   = help: the trait `Iterator` is not implemented for `Foo`
   = note: required because of the requirements on the impl of `IntoIterator` for `Foo`

No. Look at the signature of Extend. impl Extend<Foo> for T means that Extend::extend takes an iterator over Foo, not a single Foo. That's a simple type mismatch. If you impl Extend<Foo> without the IntoIterator bound, then it will still compile when you invoke it with e.g. iter::once(Foo) or vec![Foo] or the like. You are confusing extend() with extend_one().

@H2CO3 No, I am not confusing extend() with extend_one(). As you said, Extend::extend takes an iterator, but as I don't specify the bound in the impl, the compiler shouldn't find a type mismatch.

// does NOT compile
  10   │ impl std::iter::Extend<Foo> for String {
  11   │     fn extend<I>(&mut self, iter: I) { // no type bound on `I`
  12   │         //iter.into_iter().for_each(|f| self.push_str(&f));
  13   │     }
  14   │ }
  15   │ 
  16   │ fn main() {
  17   │     let mut s = String::new();
  18   │     s.extend(Foo); // deliberate, I know Foo is not an Iterator
  19   │     println!("{}", s);
  20   │ }

The compiler doesn't check the impl, it checks calls against the trait/interface.

This makes sense to me. But if I miss the bound in the impl, as shown in case 1, code-listing-2 still compiles. So it seems the compiler does not enforce one to specify the same bound in the impl block?.

That's only partially correct. You don't have to specify the same bounds; you are allowed to specify weaker bounds on the impl than what is declared on the trait.

The reason for this is logical. If your implementation doesn't need all the bounds, but the caller provides them, that's fine. You can't specify stronger or additional bounds, though, because if your implementation needed those stronger guarantees, but the caller weren't able to provide them, then the code wouldn't work.

This is exactly what I was saying in my earlier response:

3 Likes

You can leave trait-required bounds off of methods in your impl, if that's what you're asking. Or add bounds that are provable in the impl (even if they're stricter than the trait-required bound). But you can't add unprovable bounds that are stricter than the trait-required bounds.

However, the trait-required bounds are required for anything to actually make use of your impl.

This probably makes the most sense in a generic implementation, wherein some of the types that use the implementation meet the trait-required bounds but others do not.

Examples.

2 Likes

@quinedot In your example, why are the bounds vacous ? I know Clone is a super trait of Copy, but wouldn't adding Borrow<str> make it stricter ?

pub trait Lop { fn method() where Self: Copy; }

impl Lop for String {
    // OK as these are vacuous bounds within the context of this `impl`
    fn method() where Self: Clone + Borrow<str> {
        println!("No complaints");
    }
}

Because the impl is for String and String: Clone + Borrow<str>, so the method bounds aren't doing anything that the parameters of the impl haven't already guaranteed. E.g. this works too, even though no Borrow bound is declared anywhere:

impl Lop for String {
    fn method() where Self: Clone {
        let s: Self = "No complaints".to_string();
        let s: &str = s.borrow();
        println!("{}", s);
    }
}
1 Like

@H2CO3 @quinedot Thank you guys ! I understand the problem better now. :coffee:

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.