Rust: Inconsistency in type checking where clauses in trait definition

Created on 28 Aug 2015  路  4Comments  路  Source: rust-lang/rust

In this example, function f compiles, but g does not. To make g compiles it's necessary to repeat the C definition bound. For function f this is not necessary. This seems to be an inconsistency.

trait A {
    fn a(&self);
}

trait B where Self: A {
    fn b(&self);
}

trait C where for<'a> &'a Self: A {
    fn c(&self);
}

fn f<T>(x: &T) where T: B {
    x.b();
    x.a();
}

fn g<T>(x: &T) where T: C, /*for<'a> &'a T: A*/ { // uncomment to work
    x.c();
    x.a();
}

fn main() {}
<anon>:19:7: 19:10 error: the trait `for<'a> A` is not implemented for the type `&'a T` [E0277]
<anon>:19     x.c();
                ^~~
<anon>:20:7: 20:10 error: no method named `a` found for type `&T` in the current scope
<anon>:20     x.a();
                ^~~
<anon>:20:7: 20:10 help: items from traits can only be used if the trait is implemented and in scope; the following trait defines an item `a`, perhaps you need to implement it:
<anon>:20:7: 20:10 help: candidate #1: `A`
error: aborting due to 2 previous errors

Most helpful comment

While the description of how bounds are added to a function makes sense, and is very useful, I'm still left with the question "why?"

If I have some trait C: B where <Self as B>::A: A {}, it seems that for any case where T: C could possibly be true, the other bounds would have to be true as well. Could that proof be added to the trait bounds resolver?

All 4 comments

This is how supertraits work. not a bug.

Could you give me some references to understanding this behavior?

Supertraits: from the reference:

Trait bounds on Self are considered "supertraits". These are
required to be acyclic. Supertraits are somewhat different from other
constraints in that they affect what methods are available in the
vtable when the trait is used as a trait object.

The issue (elaboration) - this is part of the trait-system and mostly documented by the code comments. The thing is that we don't want too many bounds to be implicitly available for functions, as this can lead to fragility with distant changes causing functions to stop compiling. There are basically 3 kinds of bounds available to a function:

  • bounds from explicit where-clauses - e.g. T: B when you have that clause. This includes the "semi-explicit" Sized bound.
  • bounds from supertraits of explicit where-clauses - a where-clause adds bounds for its supertraits (as trait B: A, the T: B bound adds a T: A bound).
  • bounds from the lifetime properties of arguments (outlives/implicator/implied bounds). These are only lifetime bounds, and irrelevant for the current problem. rust-lang/rfcs#1214 involved them a great deal.

If your bound isn't in the list, you will have to add it explicitly if you want to use it. I guess this should be a FAQ entry.

While the description of how bounds are added to a function makes sense, and is very useful, I'm still left with the question "why?"

If I have some trait C: B where <Self as B>::A: A {}, it seems that for any case where T: C could possibly be true, the other bounds would have to be true as well. Could that proof be added to the trait bounds resolver?

Was this page helpful?
0 / 5 - 0 ratings