This is a tracking issue for the RFC "Associated type bounds of form MyTrait<AssociatedType: Bounds>
" (rust-lang/rfcs#2289). The feature gate is #![feature(associated_type_bounds)]
.
Steps:
Unresolved questions:
[ ] Does allowing this for dyn
trait objects introduce any unforeseen issues? This can be resolved during stabilization.
[ ] Resolve exact desugaring in the context of putting bounds on an associated type of a trait. See discussion in the RFC.
[ ] Consider nested lifetimes, https://gist.github.com/Centril/b0e62d31d4ae1849535c3e506e1fad64.
TODO:
[ ] Extend with tests in https://github.com/rust-lang/rust/pull/57428#issuecomment-489336637.
Does allowing this for
dyn
trait objects introduce any unforeseen issues?
馃槙 How does it work? For instance while dyn Iterator<Item = T>
is object-safe, dyn Iterator<Item: Debug>
should not since its .next()
returns... impl Debug
(but dynamic)?
trait Tr {
type Assoc;
fn input(&self, assoc: &Self::Assoc);
fn output(&self) -> Self::Assoc;
}
let a: &Tr<Assoc: Debug> = ...;
let o = a.output(); // ???
a.input(&o); // ???
let b: &Tr<Assoc: Debug> = ...;
b.input(&o); // ??????????
@kennytm I think that while dyn Iterator<Item: Debug>
make syntactic sense to parse (to keep the parser simpler), it doesn't make much sense semantically for the reasons you've noted. Nicely spotted. :)
So I suppose we could parse dyn Trait<Assoc: Bound>
into AST, but will reject it during HIR lowering or typeck.
@kennytm sounds reasonable. 馃憤
I haven't found any discussion about this: what about super trait bounds? In particular: whether other code can "rely" on it. Example (current Rust, Playground):
trait Color {
type Channel;
}
trait CloneColor: Color
where
Self::Channel: Clone,
{}
fn foo<T: CloneColor>() {
let f = T::Channel::clone;
}
This errors. While foo
can assume that T
also implements Color
(the direct super trait bound) and thus T::Channel
works, foo
cannot assume the where
bound of the trait to be true, and thus ::clone
doesn't work. The compiler says "the trait bound <T as Color>::Channel: std::clone::Clone
is not satisfied". I can't quite remember what this is called, but I once heard a term that describes what things other code can rely on and what not (maybe something fancy like "projection"?).
My question is: how would the following code behave?
trait CloneColor: Color<Channel: Clone> {}
Would it behave as the first code example or would other code now be able to deduce T::Channel: Clone
from T: CloneColor
?
(I hope I'm not missing something obvious here)
This errors.
https://github.com/rust-lang/rust/issues/44491 will likely change this :) (cc @scalexm)
My question is: how would the following code behave?
Same as:
trait CloneColor where Self: Color, <Self as Color>::Channel: Clone {}
Would it behave as the first code example or would other code now be able to deduce
T::Channel: Clone
fromT: CloneColor
?
Depends on whether you have implied bounds or not :)
@Centril Thanks for the quick explanation! I didn't know implied bounds would change this too -- that's great!
How does this interact with HRLB? I just discovered that trait aliases allow you to write the constraints required for working with async functions/closures, but could this be another way to do so?
You can currently write:
trait Foo<'a> = FnOnce<(&'a u8,)> where <Self as FnOnce<(&'a u8,)>>::Output: Future<Output = u8> + 'a;
fn foo<F: for<'a> Foo<'a>>(f: F)
and presumably once this is implemented it could be:
trait Foo<'a> = FnOnce<(&'a u8,), Output: Future<Output = u8> + 'a>;
fn foo<F: for<'a> Foo<'a>>(f: F)
but can you go all the way to:
fn foo<F: for<'a> FnOnce<(&'a u8,), Output: Future<Output = u8> + 'a>>(f: F)
@Nemo157 When you write:
where
Foo: for<'a> Bar<'a, Assoc: Bound>
it should desugar into:
where
Foo: for<'a> Bar<'a>,
for<'a> <Foo as Bar<'a>>::Assoc: Bound,
However, rustc is generally unhappy with for<'a> <Foo as Bar<'a>>::Assoc: Bound
today as I found out when writing tests for https://github.com/rust-lang/rust/pull/57428. Chalk will hopefully makes things better in the future and throw out some of the bugs.
https://github.com/rust-lang/rust/pull/63350 by @iluuu1994 & reviewed by @Centril fixed https://github.com/rust-lang/rust/issues/61738. The standard library now uses ATBs in some places. This is further enhanced in https://github.com/rust-lang/rust/pull/63584.
In https://github.com/rust-lang/rust/pull/63620, a bug was fixed where some incorrect spans were used in diagnostics. The PR also added a test src/test/ui/associated-type-bounds/assoc-type-eq-with-dyn-atb-fail.rs which checks that type Out = Box<dyn Bar<Assoc: Copy>>;
rejects unifying Assoc = T
where T: Copy
is not satisfied. The PR was written by @estebank and reviewed by @Centril.
This would be really helpful.
Currently one can't write a function that takes fn<T: FromStr<Err: Debug>>(a: T)
As such it's not possible to print the error.
@0xpr03
fn foo<T: FromStr>(a: T)
where T::Err: Debug {}
Oh thanks! Sorry for the noise but that will hopefully also help other people.
In the meantime, while this is unstable, the compiler when encountering
fn foo<T: FromStr<Err: Debug>>(a: T) {}
fn foo<T: FromStr>(a: T) where <T as FromStr>::Err: Debug {}
instead of the current
error[E0658]: associated type bounds are unstable
--> src/lib.rs:5:19
|
5 | fn foo<T: FromStr<Err: Debug>>(a: T) {}
| ^^^^^^^^^^
|
= note: for more information, see https://github.com/rust-lang/rust/issues/52662
= help: add `#![feature(associated_type_bounds)]` to the crate attributes to enable
I don't know if this "experience"-information is useful at this point, but I just used this feature in all places where it made sense in a fairly large project of mine: https://github.com/LukasKalbertodt/lox/commit/e2cc0c340b13428e26f01fa0923a969ecc6b6e1e. "48 additions and 92 deletions" sounds good to me. Everything worked just out of the box, I just needed to mechanically replace all occurences.
@lukas-code Nope, we appreciate hearing of such use cases! Will make me feel more comfortable about stabilising in the (hopefully) near future. :-)
I don't know if this is a bug or of topic for this issue. But I'm encountering the following error when compiling the following code.
#![feature(associated_type_bounds)]
trait A {
type T;
}
trait B: A<T: B> {}
error[E0391]: cycle detected when computing the supertraits of `B`
--> src/main.rs:6:15
|
6 | trait B: A<T: B> {}
| ^
|
= note: ...which again requires computing the supertraits of `B`, completing the cycle
note: cycle used when collecting item types in top-level module
--> src/main.rs:6:1
|
6 | trait B: A<T: B> {}
| ^^^^^^^^^^^^^^^^
But if I merge the traits A
and B
into the trait C
it works.
trait C {
type T: C;
}
I think the first example should work as good as the second since the trait C
is only split into two traits A
and B
with same functionality.
@tage64 Please re-file this as an issue.
Another use case for this is constraints for associated types of associated types. Since apparently you can't put where clauses on associated types without enabling GAT (??), the only other way I could think of was using this feature.
trait MyTrait {
type Assoc: Iterator<Item: Debug>;
}
That's sugar for:
trait MyTrait
where Self::Assoc: Iterator<Item: Debug>,
{
type Assoc;
}
And by further desugaring this feature:
trait MyTrait
where Self::Assoc: Iterator,
<Self::Assoc as Iterator>::Item: Debug,
{
type Assoc;
}
@eddyb: Thanks! It didn't occur to me that I could put constraints on the associated type in the trait's where clauses. I'd say it's more intuitive with associated type bounds, but having a way of doing the same thing on stable today is really helpful!
After trying to use this I noticed that the desugaring inside return position impl Trait<Assoc: Bound>
doesn't feel right. As an example (playground) I would expect code like this to just _add_ an additional bound, while retaining the existing bounds from the trait:
pub fn foo() -> impl IntoIterator<IntoIter: Debug> {
vec![5]
}
Instead it completely overwrites the bounds, so while you can return an impl IntoIterator<Item = impl Debug>
easily without mentioning IntoIter
, to add a Debug
bound to the iterator you need to both split off an extra type alias and write a lot of duplicate information to return a
type Item = impl Debug;
fn foo() -> impl IntoIterator<Item = Item, IntoIter: Iterator<Item = Item> + Debug> {...}
Interesting. There are similar limitations in cases like this:
fn foo<T>(x: impl IntoIterator<IntoIter=T>)
where you must repeat that T: Iterator
and so forth, though I guess we don't desugar to a fresh type parameter so it doesn't come up here. In any case, I had some thoughts to fix this as part of the work we're doing on chalk and implied bounds, but (a) I'm not sure if the latest formulations actually change anything here and (b) I'm not sure if they would address the impl Trait
case.
Most helpful comment
I don't know if this "experience"-information is useful at this point, but I just used this feature in all places where it made sense in a fairly large project of mine: https://github.com/LukasKalbertodt/lox/commit/e2cc0c340b13428e26f01fa0923a969ecc6b6e1e. "48 additions and 92 deletions" sounds good to me. Everything worked just out of the box, I just needed to mechanically replace all occurences.