The following example:
trait Foo<T> {
fn foo(&self) -> &T;
}
trait Bar<A> where A: Foo<Self> {}
fn foobar<A, B: Bar<A>>(a: &A) -> &B {
a.foo()
}
fails with "error: type &A does not implement any method in scope named foo".
This UFCS variant
trait Foo<T> {
fn foo(&self) -> &T;
}
trait Bar<A> where A: Foo<Self> {}
fn foobar<A, B: Bar<A>>(a: &A) -> &B {
Foo::foo(a)
}
fails with "error: the trait Foo<_> is not implemented for the type A".
cc @nikomatsakis @jroesch
Possibly related to https://github.com/rust-lang/rust/issues/20469 but it is not fixed by where clauses. I'm seeing this on nightly, but was led there from a problem on master.
Note, this blocks the transition from BorrowFrom to Borrow. Not a blocker for alpha, though.
Sorry, but I don't see why this should work _without_ an explicit A: Foo<B> bound in the foobar function. trait Bar<A> where A: Foo<Self> is the same as trait Bar<A: Foo<Self>>, so this seems consistent with how bounds have always worked. To show an example with bounds on structs:
#![crate_type = "lib"]
// these two are equivalent
struct Rev1<I> where I: DoubleEndedIterator {
it: I,
}
struct Rev2<I: DoubleEndedIterator> {
it: I,
}
// these two don't work because `Rev<I>` doesn't imply that `I: DoubleEndedIterator`
fn foo1<I>(mut rev: Rev1<I>) {
rev.it.next_back();
}
fn foo2<I>(mut rev: Rev2<I>) {
rev.it.next_back();
}
// even these won't work unless you explicitly add `I: DoubleEndedIterator` as a bound
fn bar1<I>(mut rev: Rev1<I>) {}
fn bar2<I>(mut rev: Rev2<I>) {}
I skimmed over the where clause RFC, and I didn't see anything about propagating bounds just for traits. Am I missing something?
@japaric My assumption that this would work was based partly on conversations with @nikomatsakis after the initial RFC. In particular, we've discussed that trait Foo: Bar should be equivalent to trait Foo where Self: Bar in most respects, and from that one would further expect other bounds to apply. But currently these are not equivalent:
trait Foo<T> {
fn foo(&self) -> &T;
}
//trait Bar<A> where Self: Foo<A> {} <--- this produces an error
trait Bar<A>: Foo<A> {}
fn foobar<A, B: Bar<A>>(b: &B) -> &A {
b.foo()
}
That seems inconsistent and I don't know of any good reason for it. (And of course it's a pain to have to rewrite bounds everywhere when those bounds _must_ already hold.)
I do take your point that in general we do not propagate bounds, but I think we want to head toward more propagation, as in http://smallcultfollowing.com/babysteps/blog/2014/07/06/implied-bounds/ -- and I thought we'd already started down this road.
@japaric It is also my understanding (from conversations with @nikomatsakis) that this should work. It seems related to the generalization of bounds checking.
One of the annoyances I started purging from the compiler is the ParamBounds struct which is still used to store the super traits bounds declared on a trait definition. This is one of (if not the last) place where we still have special treatment of bounds (modulo my open PR). It is my hunch that the bounds checking code introduces all super trait bounds but doesn't do anything with the ones declared in the where clause.
I think this is an important question to resolve generally because like @aturon it was my understanding that any super trait bound should be identical to bounding the Self type with the same bound. I believe Niko and I had talked about needing to make some more reaching changes to support this, and if I recall correctly it was around the computing of super trait bounds.
In my imagined scheme we should probably collect any Self type constraints and introduce them like we have been with super trait bounds. It seems this would be the simplest strategy, and avoids introducing a breaking change for anyone relying on the current behavior (esp. if this isn't resolved pre-1.0).
I think this is a grey area :) At present we only "elaborate" supertraits (meaning Self : Foo). This case is not a "supertrait". However, limiting ourselves to elaborating supertraits is fairly arbitrary (and, in fact, easily lifted). I can do an experiment in this area if it will speed along the BorrowFrom to Borrow transition. I agree it's not exactly _blocking_ alpha but I'd love to kill the old_orphan_check and old_impl_check "features" (maybe that's unrealistic).
@aturon
In particular, we've discussed that trait Foo: Bar should be equivalent to trait Foo where Self: Bar
So I _was_ missing a piece of information.
That seems inconsistent and I don't know of any good reason for it.
I agree with this. But want to point out that this second example is a "supertrait", whereas the first one is not. So having to explicitly add the B: Foo<A> bound in the first case is consistent with _today's_ rules.
I do take your point that in general we do not propagate bounds, but I think we want to head toward more propagation
I'm not opposed to having implied bounds, but I would like it to be implemented in a general way (such that my struct bounds example would also compile), instead of making it more ad hoc ("we only propagate bounds on traits with where-clause bounds that have a Self parameter on either side" or something like that)
@nikomatsakis
I think this is a grey area :) At present we only "elaborate" supertraits (meaning Self : Foo). This case is not a "supertrait".
I agree!
However, limiting ourselves to elaborating supertraits is fairly arbitrary (and, in fact, easily lifted)
I think that at this point in time supertraits are ingrained in rustaceans' minds as something natural even if they are arbitrary. In fact, I just realized that it's a (very) special case of implied bounds.
Thanks everyone for their explanations!
Not sure if it is related, but this doesn't work either:
use std::ops::*;
trait Vector<S> where
for<'a, 'b> &'a Self: Add<&'b Self>,
for<'a> &'a Self: Mul<S>,
{
fn test(&self) {}
}
fn test<S, V: Vector<S>>(v: V) { v.test() }
<anon>:10:36: 10:42 error: the trait `for<'b, 'a> core::ops::Add<&'b V>` is not implemented for the type `&'a V` [E0277]
<anon>:10 fn test<S, V: Vector<S>>(v: V) { v.test() }
^~~~~~
<anon>:10:36: 10:42 help: see the detailed explanation for E0277
<anon>:10:36: 10:42 error: the trait `for<'a> core::ops::Mul<S>` is not implemented for the type `&'a V` [E0277]
<anon>:10 fn test<S, V: Vector<S>>(v: V) { v.test() }
^~~~~~
This is needed for cgmath to move full to operators, and remove our ugly mul_s-style methods :(
@bjz
Same "issue"
I ran into this issue as well, and was really surprised by it. One situation where it would be very useful is refining traits that are used in higher-kinded trait bounds like in this example: https://gist.github.com/anonymous/e3a8fe834bb383504bb4
If we don't propagate associated type bounds for where clause traits, I don't know how to actually put the bound I need in the linked example on the function: the bound itself is polymorphic over the lifetime, which seems to not be expressible in the grammar. So, it seems that without this propagation there are useful bounds that we can't express.
@russellmcc what bound are you unable to express precisely?
In that example, I want to say that T: for<'a> Iterable<'a, Item=u32>, and in addition to that, for all 'a, <T as Iterable<'a>>::Iter implements the ExactSizeIterable<Item=&'a u32> trait. Unless I'm missing something, that can't currently be expressed. If we weren't talking about higher-kind bounds, I could add another free type variable E: ExactSizeIterable<&'a u32>, and then bound T::Iter=E, but that doesn't work because in this situation E would have to be higher kind, and there's no way to bound equality for higher kinded types that I know of.
Making the trait bounds propagate for associated types would allow me to express the exact same bound in a convenient way, as in my previous example.
I ran into something like this trying to create an AddAny supertrait, for all the variations of x + y, x + &y, &x + y, and &x + &y. The bounds I put on &Self as the left side of an Add won't propagate. It's pretty surprising to me that T: AddAny doesn't imply _all_ of its transitive constraints, instead of just those on Self.
https://users.rust-lang.org/t/traits-with-self-bounds/4033
There's at least one category of non-supertrait where clauses that should be elaborated according to an accepted RFC but are not.
In RFC-0195 there's mention of where clauses on associated types (non-supertrait projections from Self?) being assumptions for user code (which translates to me as 'elaborated').
While the exact syntax of the RFC is unimplemented, current behavior is that the placement of trait bounds on an associated type in both the trait where clause and on the associated type are equivalent. e.g.
trait A { type B: C; }
and
trait A where Self::B: C { type B; }
Are the same _today_ with respect to trait bound elaboration.
Thus where clauses on associated types _should_ be elaborated according to the RFC's specification when considered alongside today's behavior (卤syntax), but they're currently not. For example, by my understanding, this code on the Rust playground should work, as the From bound could just as well have appeared in a production of RFC-0195's WHERE_CLAUSE for associated types.
Beyond that it's further arbitrary decisions (e.g. I'd like to see the RFC's note on where clauses extended to where clauses on supertrait's associated types), but at the very very least defining a new associated type should be allowed the comforts of elaboration.
@soltanmm Your code is placing a restriction on the associated type of a supertrait (where <Self::B as Q>::U: From<usize>), which is not elaborated.
@jonas-schievink Please read RFC-0195 and re-read my note to see why I believe that's the wrong behavior.
In particular, this line:
The BOUNDS and WHERE_CLAUSE on associated types are obligations for the implementor of the trait, and assumptions for users of the trait
The WHERE_CLAUSE is among the assumptions user code ought to make. The relevant predicate in the example I gave is a bound on an associated type defined within the trait (or, rather, an associated type within an associated type defined within the trait), which by analogy as given in my previous post should be allowed in the where clause for the trait itself.
The current supertrait-only behavior _seems_ like a cultural momentum phenomenon (or oversight, or what-have-you) rather than something following from the accepted RFCs.
Has there been any more discussion on the lang team about this lately? Is this something that is just missing a proper RFC?
@sgrif There hasn't been any discussion, no. This is also (loosely) related to the implied bounds proposal.
Rather than starting immediately with an RFC, I'd suggest perhaps opening up a discuss thread with some motivating examples/rationale for any change -- or some other way of drawing a bit more visibility. If we can build some consensus there, then we could proceed to an RFC.
@aturon @sgrif I opened a thread about a motivating example some time ago.
I ran into the same issue as well: https://users.rust-lang.org/t/creating-alias-for-bounds/7145. As someone relatively new to Rust, It was a bit surprising to see.
Hi, if anyone ran into this problem and was interested in a working solution (or rather hack), I'd like to share mine. The solution is quite complicated, but the high-level code does not get polluted by the boilerplate where clauses, but instead only by annotations. My approach is generation of the where clause by use of a proc_macro, which leads to a slightly better-looking code (and raises another problem described -- and partially solved -- in the gist). https://gist.github.com/p4l1ly/6dbcb40fb3bbb450598876b068762939
As proc_macros are not hygienic, you'll also lose some guarantees...
any progress?
It seems worth noting that in some cases with associated types you can work around this by duplicating them to trade off verbosity at the definition+impl instead. Taking some examples from this thread:
-trait ExactSizeIterable<'a> : Iterable<'a> where Self::Iter : ExactSizeIterator {}
+trait ExactSizeIterable<'a> : Iterable<'a, Iter=<Self as ExactSizeIterable<'a>>::Iter> {
+ type Iter: ExactSizeIterator<'a>;
+}
-trait A where Self::B: Q, <Self::B as Q>::U: From<usize> {
+trait A {
+ type U: From<usize>;
+ type B: Q<U=Self::U>;
fn b() -> Self::B;
}
Interestingly this issue doesn't affect trait aliases. I don't know if it's possible but couldn't the compiler use the same logic to elaborate the where clauses in normal traits? I would expect them to work the same.
#![feature(trait_alias)]
trait Foo<T: ?Sized> {
fn foo(&self) -> &T;
}
trait Bar<A> where A: Foo<Self> {}
// This doens't compile
// fn foobar_original<A, B: Bar<A>>(a: &A) -> &B {
// a.foo()
// }
trait BarAlias<A> = Bar<A> where A: Foo<Self>;
// This does compile
fn foobar_alias<A, B: BarAlias<A>>(a: &A) -> &B {
a.foo()
}
trait BarAliasExt<A>: BarAlias<A> {}
// This also compiles
fn foobar_super<A, B: BarAliasExt<A>>(a: &A) -> &B {
a.foo()
}
Most helpful comment
Not sure if it is related, but this doesn't work either:
http://is.gd/FgFAWo
This is needed for cgmath to move full to operators, and remove our ugly
mul_s-style methods :(