Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

where clauses are only elaborated for supertraits, and not other things #20671

Open
aturon opened this Issue Jan 7, 2015 · 21 comments

Comments

Projects
None yet
@aturon
Copy link
Member

aturon commented Jan 7, 2015

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".

@aturon

This comment has been minimized.

Copy link
Member Author

aturon commented Jan 7, 2015

cc @nikomatsakis @jroesch

Possibly related to #20469 but it is not fixed by where clauses. I'm seeing this on nightly, but was led there from a problem on master.

@aturon

This comment has been minimized.

Copy link
Member Author

aturon commented Jan 7, 2015

Note, this blocks the transition from BorrowFrom to Borrow. Not a blocker for alpha, though.

@japaric

This comment has been minimized.

Copy link
Member

japaric commented Jan 7, 2015

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?

@aturon

This comment has been minimized.

Copy link
Member Author

aturon commented Jan 7, 2015

@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.

@jroesch

This comment has been minimized.

Copy link
Member

jroesch commented Jan 7, 2015

@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).

@nikomatsakis

This comment has been minimized.

Copy link
Contributor

nikomatsakis commented Jan 7, 2015

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).

@japaric

This comment has been minimized.

Copy link
Member

japaric commented Jan 7, 2015

@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!

nikomatsakis added a commit to nikomatsakis/rust that referenced this issue Mar 6, 2015

Distinguish elaboration vs supertrait expansion. Elaboration considers
all where-clauses, but supertrait considers a more limited
set. Supertrait expansion is suitable for virtual tables and associated
type inclusion, elaboration for more general reasoning.

Fixes rust-lang#20671.
@brendanzab

This comment has been minimized.

Copy link
Member

brendanzab commented Sep 30, 2015

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() }
                                             ^~~~~~

http://is.gd/FgFAWo

This is needed for cgmath to move full to operators, and remove our ugly mul_s-style methods :(

brendanzab added a commit to rustgd/cgmath that referenced this issue Sep 30, 2015

Implement by-ref operators for vectors, and remove by-value implement…
…ations

We can't yet remove the operator methods, due to rust-lang/rust#20671

This also removes the implementations of `Zero` and `One` for vectors.

brendanzab added a commit to rustgd/cgmath that referenced this issue Sep 30, 2015

Implement binary operators for points
We can't yet remove the operator methods, due to rust-lang/rust#20671

brendanzab added a commit to rustgd/cgmath that referenced this issue Sep 30, 2015

Implement binary operators for matrices
We can't yet remove the operator methods, due to rust-lang/rust#20671
@arielb1

This comment has been minimized.

Copy link
Contributor

arielb1 commented Sep 30, 2015

@bjz

Same "issue"

@nikomatsakis nikomatsakis changed the title where clauses for super traits do not fully propagate where clauses are only elaborated for supertraits, and not other things Oct 20, 2015

@russellmcc

This comment has been minimized.

Copy link
Contributor

russellmcc commented Oct 27, 2015

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.

@nikomatsakis

This comment has been minimized.

Copy link
Contributor

nikomatsakis commented Oct 28, 2015

@russellmcc what bound are you unable to express precisely?

@russellmcc

This comment has been minimized.

Copy link
Contributor

russellmcc commented Oct 28, 2015

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.

@cuviper

This comment has been minimized.

Copy link
Member

cuviper commented Dec 22, 2015

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

@soltanmm

This comment has been minimized.

Copy link

soltanmm commented Dec 24, 2015

@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.

@sgrif

This comment has been minimized.

Copy link
Contributor

sgrif commented Jun 13, 2016

Has there been any more discussion on the lang team about this lately? Is this something that is just missing a proper RFC?

@aturon

This comment has been minimized.

Copy link
Member Author

aturon commented Jun 21, 2016

@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.

@vks

This comment has been minimized.

Copy link
Contributor

vks commented Jun 22, 2016

@chaaz

This comment has been minimized.

Copy link

chaaz commented Sep 3, 2016

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.

cuviper added a commit to cuviper/num that referenced this issue Apr 24, 2017

Add new traits for reference and assignment operators
There are two new "utility" traits covering the basic operators:
`Add`, `Sub`, `Mul`, `Div`, and `Rem`.

- `NumOps<Rhs, Output>`: operators with an arbitrary operand and output.
- `NumAssignOps<Rhs>`: assignment operators with an arbitrary operand.

Then the new collection of numeric traits are:

- `Num`: effectively unchanged, just taking operands by value.
- `NumRef`: `Num` adding reference operands on the right side.
- `RefNum`: `&T` operators, with either `T` or `&T` on the right side.
  - This does not specify `T: Num`, as rust-lang/rust#20671 means that
    could only add a constraint, without implying its presence for use.
- `NumAssign`: `Num` adding assignment operators by value.
- `NumAssignRef`: `NumAssign` adding reference assignment operators.
  - Nothing actually implements this yet!

Acknowledgement: this is roughly based on [@andersk's suggestion][1].

[1] rust-num#94 (comment)

cuviper added a commit to cuviper/num that referenced this issue May 7, 2017

Add new traits for reference and assignment operators
There are two new "utility" traits covering the basic operators:
`Add`, `Sub`, `Mul`, `Div`, and `Rem`.

- `NumOps<Rhs, Output>`: operators with an arbitrary operand and output.
- `NumAssignOps<Rhs>`: assignment operators with an arbitrary operand.

Then the new collection of numeric traits are:

- `Num`: effectively unchanged, just taking operands by value.
- `NumRef`: `Num` adding reference operands on the right side.
- `RefNum`: `&T` operators, with either `T` or `&T` on the right side.
  - This does not specify `T: Num`, as rust-lang/rust#20671 means that
    could only add a constraint, without implying its presence for use.
- `NumAssign`: `Num` adding assignment operators by value.
- `NumAssignRef`: `NumAssign` adding reference assignment operators.
  - Nothing actually implements this yet!

Acknowledgement: this is roughly based on [@andersk's suggestion][1].

[1] rust-num#94 (comment)

homu added a commit to rust-num/num that referenced this issue May 7, 2017

Auto merge of #283 - cuviper:more-num, r=cuviper
Add new traits for reference and assignment operators

There are two new "utility" traits covering the basic operators:
`Add`, `Sub`, `Mul`, `Div`, and `Rem`.

- `NumOps<Rhs, Output>`: operators with an arbitrary operand and output.
- `NumAssignOps<Rhs>`: assignment operators with an arbitrary operand.

Then the new collection of numeric traits are:

- `Num`: effectively unchanged, just taking operands by value.
- `NumRef`: `Num` adding reference operands on the right side.
- `RefNum`: `&T` operators, with either `T` or `&T` on the right side.
  - This does not specify `T: Num`, as rust-lang/rust#20671 means that
    could only add a constraint, without implying its presence for use.
- `NumAssign`: `Num` adding assignment operators by value.
- `NumAssignRef`: `NumAssign` adding reference assignment operators.
  - Nothing actually implements this yet!

Acknowledgement: this is roughly based on [@andersk's suggestion](#94 (comment)).
@p4l1ly

This comment has been minimized.

Copy link

p4l1ly commented Oct 23, 2018

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...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.