This is a tracking issue for the RFC "Permit impl Trait in type aliases" (rust-lang/rfcs#2515) under the feature gate #![feature(type_alias_impl_trait)]
.
Tracking issues are used to record the overall progress of implementation.
They are also uses as hubs connecting to other relevant issues, e.g., bugs or open design questions.
A tracking issue is however not meant for large scale discussion, questions, or bug reports about a feature.
Instead, open a dedicated issue for the specific matter and add the relevant feature gate label.
static
s and const
s are not counted, but rust-lang/rfcs#2071 specifies that they should be.@varkor See my comment about the PR for this in the other thread, and let me know if you'd like me to take up your existing PR and finish it off maybe. :-)
@alexreg: thanks for the offer 鈥斅營'll try to get a PR open this week, but if I turn out not to have enough time, I may pass it on to you :)
It appears that the current implementation does not trigger any "private type in public interface" errors, for the following code (playground):
#![feature(existential_type)]
#[derive(Debug)]
struct Bar(usize);
existential type Foo: std::fmt::Debug;
pub fn foo() -> Foo {
Bar(5)
}
I would expect it to give an error like
error[E0446]: private type alias `Foo` in public interface
--> src/lib.rs:8:1
|
5 | existential type Foo: std::fmt::Debug
| - `Foo` declared as private
...
8 | / pub fn foo() -> Foo {
9 | | Bar(5)
10 | | }
| |_^ can't leak private type alias
(although, because normal type aliases are transparent to the visibility checking, I'm not certain exactly what the error message should say; it just seems bad to introduce another way to accidentally forget to make part of your API publicly nameable)
EDIT: Opened #63169
Got a tiny question: Is type Foo = impl Debug
equivalent to type Foo = impl Debug + '_
or type Foo = impl Debug + 'static
?
In https://github.com/rust-lang/rust/pull/63092, landed on 2019-07-28, written by @Centril and reviewed by @varkor, the tracking issue for existential_type
was adjusted to this one.
In https://github.com/rust-lang/rust/pull/63096, landed on 2019-07-29, written by @Centril and reviewed by @varkor, 3 ICEs were closed with reproducer tests added.
In https://github.com/rust-lang/rust/pull/63158, landed on 2019-07-31, written by @JohnTitor and reviewed by @Centril, 1 ICE was closed with a reproducer test added.
In https://github.com/rust-lang/rust/pull/63180, landed on 2019-08-03, written by @varkor and reviewed by @Centril, the syntax was changed to type Foo = impl Bar;
through a temporary hack in the parser. @varkor will follow up to remove that hack. The new feature gate name is type_alias_impl_trait
and the old one existential_type
has been removed.
In https://github.com/rust-lang/rustc-guide/pull/402, landed on 2019-08-29, written by @varkor and reviewed by @mark-i-m, @Arnavion, and @spastorino, the rustc guide description was updated.
Given that async/await is now available on stable, not being able to name futures returned from async functions seems like one of the bigger factors limiting where async/await can be used (without boxing). What are the remaining blockers to stabilizing this?
From the comments, it seems it has been implemented and at least some documentation has been written (though this is not reflected in the initial description's task list yet).
@djc Please see the associated label F-type_alias_impl_trait
. As you can see, there are a lot of bugs and the implementation is partial. Beyond fixing all of those bugs, the set of tests will need to be audited, and moreover there are unresolved questions as the issue description notes. This is far from in a ready state to stabilize.
There's currently an unfortunate trade-off with usages of type-alias-impl-trait (TAIT from here on out). You can either:
1) Use a TAIT within the defining scope. This requires your function to be fully generic over the TAIT usage (cannot repeat type parameters, have extra bounds, on parametesr to the TAIT, etc).
2) Use a TAIT outside the defining scope. This allows you to use any type or type parameter for the generic parameters of the TAIT, but you cannot (by design) see the underlying type.
This means that moving code into a defining scope can actually make it stop compiling, as it might not be fully generic over the TAIT.
This is a significant limitation - it prevents a large amount of perfectly reasonable code from being written, and contradicts the general principle that moving code into a 'more private' scope (e.g. into a crate, module, or function) is always valid.
I see two ways of working around this:
1) Allow these kinds of not-fully-generic uses of TAITs. Under the current rules of the RFC, this means that function body can no longer see the underlying type (since every usage must either fully constrain the TAIT, or leave the TAIT fully unconstrained). This would allow more code, but would still prohibt reasonable things like:
type MyType<T, V> = impl Copy;
fn make_it<T>(val: T) -> MyType<T, T> { Ok(val) /* ERROR: we cannot see the underlying type here */ }
2) Amend the RFC to allow some kind of 'partial constraining' of the TAIT. This would need to work within the following constraints:
a) The underlying type may be unnameable (e.g. a closure). Thus, we cannot assume that it's always possible to specify the underlying type when the opaque type is defined (which would allow us to remove the concept of 'defining use').
b) Type-checking should not become insanely complicated. In particular, we probably want to avoid anything that would require function bodies to be type-checked in a specific order (e.g. to find the one containing the 'fully defining' use, to assist with 'partially defining' uses).
type MyType<T, V> = impl Copy; fn make_it<T>(val: T) -> MyType<T, T> { Ok(val) /* ERROR: we cannot see the underlying type here */ }
Function that don't qualify to see the underlying type can use private helper function, which won't be horrible uneconomic as long as turbofish isn't required (but turbofish is currently required, not sure if that's a dup of #66426 or not).
Also I think option 2 can be backward-compatibly added even after stabilization.
I've found an interesting use for impl trait in type alias: treating specific functions as regular types. I'm not even sure how legal it's supposed to be, but it's partially working, partially rejected by compiler and partially causes ICEs: https://play.rust-lang.org/?version=nightly&gist=f4e134cea703a8a8fbaf6aef687a56f2
I don't follow @Aaron1011's argument. The following is illegal, because it does not fully constrain MyType
:
type MyType<T, E> = impl Copy;
fn make_it<T: Copy>(val: T) -> MyType<T, T> { Result::<T, T>::Ok(val) }
The following is also illegal, in this case because a defining use of MyType
must exist within the same scope:
mod M {
pub type MyType<T, E> = impl Copy;
}
fn make_it<T: Copy>(val: T) -> M::MyType<T, T> { Result::<T, T>::Ok(val) }
Note however that this is legal:
type MyType<T> = impl Copy;
fn make_it<T: Copy>(val: T) -> MyType<T> { Result::<T, T>::Ok(val) }
Uses outside the defining scope cannot assume any more than the trait bounds (e.g. with the above, Copy
is available on values of MyType
but nothing else).
I believe that your last example should actaully be illegal: https://github.com/rust-lang/rust/issues/52843#issuecomment-457578915
No, Aaron, because here MyType<T>
is an alias for Result<T, T>
which has no bounds on its generics.
Oh, you're right. I missed the fact that MyType
didn't resolve to T
, like in the example in https://github.com/rust-lang/rust/issues/52843#issue-345582081.
However, my point about restrictions within the defining scope still stands. Usaages within the defining scope cannot repeat generic parameters, instantiate generic parameters with concrete types, or place bounds on the underlying type. I think we should try to come up with a solution that would allow this kind of code in defining scopes.
You are partially right it seems: partial defining uses within the scope are not allowed, although in theory in combination with a full defining usage (or possibly even multiple partial defining usages) it could be legal. Perhaps this should be an extension?
I have yet to find code which works in the outer scope but not the inner scope. Some examples:
https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=987d8f7823abbef1505de2fe0ad86ab3
@dhardy: Here's an example of code which works in the outer scope, but not the inner scope:
Move produce_it
into the inner scope to break it.
Concern: the defining implementation cannot currently be a struct field. (In part this overlaps with @Aaron1011's concern of being unable to use the type within the defining scope except in defining uses.)
#![feature(type_alias_impl_trait)]
type A = impl Drop;
struct S {
a: A,
}
fn foo() -> S {
S { a: Box::new(1) }
}
Concern: it is not possible to define type aliases without trait bounds using the current syntax (because type A;
is not an existential type). Although unbounded existentials (without even Drop
) are useless, it would be nice for them to be legal (in the same way that a spurious let
binding or unused struct field is legal). Unfortunately I see no solution to this using the RFC-proposed syntax.
Concern: scopes. I believe it was agreed that the details of an existential type should not cross API boundaries (i.e. escape from modules/crates). On the other hand, I believe it would be useful for the type details to be able to escape the defining scope (within the same module, the same as non-pub
types are accessible within the same module). This would allow struct literal macros to be significantly more useful.
Concern: the defining implementation cannot currently be a struct field.
Please refile this as an issue so that we can have targeted in-depth discussion about this aspect of the type inference / checking.
Concern: it is not possible to define type aliases without trait bounds using the current syntax
#![feature(type_alias_impl_trait)]
type A = impl Sized;
fn f1() -> A { 0 }
type B = impl ?Sized;
fn f2() -> &'static B { &[0] }
type C = impl ?Sized + 'static;
fn f3() -> &'static C { &[0] }
(This would be a good test for someone to add.)
On the other hand, I believe it _would_ be useful for the type details to be able to escape the defining scope
I believe you want type Foo = _;
(https://github.com/rust-lang/rfcs/pull/2524).
Thanks @Centril for reminding me of 2524. I'm still unsure whether type details should be available outside the defining scope (the RFC is not really clear).
?Sized
is an interesting type bound, but your test cases should really include the following (currently passes):
type B = impl ?Sized;
fn foo() -> &'static A { &1 }
~I haven't found this bug reported somewhere thus I don't know if it's known.~
Nevermind, found the issue #58011 after searching for "existential type"
, I'm sorry for the noise...
It's not possible to use rustdoc
as it's dropping the function bodies with. Using the latest example in this issue:
trait Bar {}
impl Bar for u8 {}
type Foo = impl Bar;
fn foo() -> Foo {
10
}
results in this error:
$ cargo doc
error[E0277]: the trait bound `(): Bar` is not satisfied
--> src/lib.rs:6:1
|
6 | type Foo = impl Bar;
| ^^^^^^^^^^^^^^^^^^^^ the trait `Bar` is not implemented for `()`
|
= note: the return type of a function must have a statically known size
@TimDiekmann: thanks, this is any one of these (seemingly duplicate) issues.
Another example (from https://github.com/rust-lang/rust/issues/68368) of trying to write a non-defining use from within a defining scope (note that this code currently ICEs due to an unrelated issue):
#![feature(type_alias_impl_trait)]
pub type Parser<'a, X, T, V> = impl Fn(X) -> Result<(T, V), String>;
pub fn take_cpredicate<'a>(
predicate: impl Fn(char) -> bool,
) -> Parser<'a, &'a str, &'a str, char,> {
move |s| {
if s.len() == 0 {
return Err(s.to_string());
}
let mut chars = s.chars();
let next = chars.next().unwrap();
if predicate(next) {
Ok((&s[1..], next))
} else {
Err(s.to_string())
}
}
}
take_cpredicate
will not compile, since it's a non-defining use (all of the Parser
generic parameters are substituted) in a defining scope.
Since a closure is being used as the underyling type, it's impossible to have more than one fully defining use (since each closure has a unique type). This means that there's really no way of making this code compile, even though it seems like it would be fine if it did.
I just came across an implementation oddity - this compiles (playground):
#![feature(type_alias_impl_trait)]
#![feature(const_generics)]
type Foo<const X: usize, const Y: usize> = impl Sized;
fn foo<const N: usize>(x: [u8; N]) -> Foo<N, N> {
x
}
However, the type parameter and lifetime parameter cases emit very different errors.
The type parameter error is from type_of
: https://github.com/rust-lang/rust/blob/5574b1df577f737373b42cbf364b6cab2dfa5960/src/librustc_typeck/collect/type_of.rs#L410-L420
While the lifetime parameter error is from wfcheck
:
https://github.com/rust-lang/rust/blob/5574b1df577f737373b42cbf364b6cab2dfa5960/src/librustc_typeck/check/wfcheck.rs#L925-L936
I think wfcheck
runs first, and checks for concrete types/consts, but ignores parameters.
But then, for the concrete type case, why does the error come from type_of
and not wfcheck
?!
At least concrete consts do error in wfcheck
.
I've left some comments on the PR that added the checking to type_of
(https://github.com/rust-lang/rust/pull/57896#discussion_r396064431) and I'll now attempt to untangle this, and I suppose add some const
parameter examples to the tests.
EDIT: opened #70272, which should address all of these inconsistencies.
A question about impl Trait in trait type aliases: has any thought been given about how they should handle defaults?
I'm trying to do something like this, without success:
#![feature(type_alias_impl_trait, associated_type_defaults)]
use std::fmt::Debug;
struct Foo {
}
trait Bar {
type T: Debug = impl Debug;
fn bar() -> Self::T {
()
}
}
impl Bar for Foo {
type T = impl Debug;
fn bar() -> Self::T {
()
}
}
I'd guess this just hasn't been RFC'd yes, but does someone know if thought on the topic have already started?
Also, just going to link as a related issue https://github.com/rust-lang/rust/issues/66551 ; as it almost immediately happens when returning Future
from trait functions, which I assume is an important intended usage of this feature: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=ebb2911238ff57bf988bc322474013d7
This is the first I've discovered this RFC, which is a little unfortunate but such is life. I do have some comments/observations, but I am inclined not to actually argue the RFC is wrong.
I'm going to go off of Centril's excellent comment here as reference to the current state of thinking on this.
In the case of type Foo = impl Bar;, it does not seem like there's noteworthy power that the simple syntax gives up. In fact, as shown before, the proposed syntax seems to compose better than other suggestions.
Associated type defaults are a case where this syntax does truly give something up. You cannot write this without true existentials (I've left out the bounds and function for clarity):
trait IntoIterator {
type Item;
type Iter: Iterator = impl Iterator<Item = <Self as IntoIterator>::Item>;
}
This is not permitted in the current implementation (note that the error message is bad and this should be a TODO), nor would it be particularly useful. This would require the trait author to constrain the type to a single concrete type, which all trait implementors would have to use. But on the other hand, if this were an existential, it would essentially say "trait implementors can provide a type, but it will be opaque if they do not". This is not merely a sugary nicety: it would allow the trait definer to add the type without breaking backwards-compatibility, which they may be unable to do otherwise.
I'm not sure that means that the design in the RFC is wrong, as there are good arguments for the current design being the more accessible one. But I wanted to raise the example anyway, even if it just creates future work to add an alternative syntax for true existentials, like exists<T>
to complement for<'a>
, is available for those rare cases where it's genuinely useful. (Plus I think an awkward syntax discourages use in favour of the nicer syntax proposed here, and that's possibly a good thing.)
@alercah the problem here would be that there's two possible meanings for impl trait in associated type position.
Digression on that topic
Case 0: Associated Type Impl Trait (just TAIT for an associated type), which currently works
trait Factory {
type Item;
fn produce(&mut self) -> Self::Item;
}
impl Factory for Foo {
type Item = impl Debug;
fn produce(&mut self) -> Self::Item {
()
}
}
Case 1: Default Associated Type Impl Trait (also requires feature(associated type defaults)
), currently forbidden
type Factory {
type Item = impl Debug;
fn produce(&mut self) -> Self::Item { () }
}
The two interpretations:
@alercah's: the TAIT default is equivalent to writing copy/pasting the associated type definition into each trait impl. As such, each trait impl gets a unique existential, and the interaction of defaulted methods returning the defaulted associated type is unclear. The intent is likely that default method implementations are "just" copy/pasted into the trait impl and participate in existential inference there. This is not enough by itself, as then trait definitions cannot be type checked until instantiation time. (Generics are not C++ templates; Rust does not have post-monomorphization errors.) The principled decision would be to just not allow default methods to return a DATIT, as the type is not known at that point (but this makes any argument for this under backwards compatibility of the type basically moot; the instantiation has to bound the type). The middle ground would be to... basically make it behave as in @Ekleog's version of DATIT. If the existential is concretized by default method impls, it behaves as a concrete opaque existential to impls, and they cannot unify types with it. At that point, the only difference between this and @Ekleog's version is the ability to have DATIT without a default method constraining it. E.g. being able to lift from
trait Factory {
fn make() -> impl Debug;
}
to
trait Factory {
type Item = impl Debug;
fn make() -> Self::Item;
}
but not to add a default method returning that associated type.
I suspect this version of DATIT is what would be needed to support async fn
(essentially just RPIT) in traits, so it is likely we will eventually see it. (Explicit DATIT with defaulted method bodies should probably be opaque to the implementors, and need to be overriden as a group.)
But given that this issue is about TAIT and not DATIT, and DATIT hasn't been RFCd (as far as I've seen), further discussion of the DATIT extension to TAIT (with associated type defaults) should probably move to irlo and/or the RFCs repo; this isn't really the place to
I've created a new issue regarding surprising behavior with async fns: #76039
(EDIT: previously the comment was here, moved to own issue since tracking issues are not for discussion)
I came across a case that may be a bug and/or may be linked to #76039.
This code doesn't compile with a "mismatched types" error:
type Callback = impl Fn();
fn callback() {
return;
}
fn register(_cb: Callback) {
return;
}
fn main() {
let cb = || callback();
register(cb);
}
When defining the type directly in the function prototype, it does compile (playground).
@haroldm The error is correct. The feature is for callee-defined types, whereas you're trying to use it as a type parameter, ie a caller-defined type.
Any update on this topic?
Why wouldn't this compile? playground
pub trait Trait<'a, 'b> {
type Assoc1: Debug + 'a;
type Assoc2: Debug + 'b;
fn func1(&'a self) -> Self::Assoc1;
fn func2(&'b self) -> Self::Assoc2;
}
#[derive(Debug)]
struct A<'a> {
a: &'a str,
}
#[derive(Debug)]
struct B<'b> {
b: &'b str,
}
struct Test(String);
impl<'a, 'b> Trait<'a, 'b> for Test {
type Assoc1 = impl Debug + 'a;
type Assoc2 = impl Debug + 'b;
fn func1(&'a self) -> Self::Assoc1 {
A { a: &self.0 }
}
fn func2(&'b self) -> Self::Assoc2 {
B { b: &self.0 }
}
}
The compile error is also confusing:
error[E0477]: the type `impl Debug` does not fulfill the required lifetime
--> src/main.rs:26:5
|
26 | type Assoc1 = impl Debug + 'a;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
note: type must outlive the lifetime `'a` as defined on the impl at 25:6
--> src/main.rs:25:6
|
25 | impl<'a, 'b> Trait<'a, 'b> for Test {
| ^^
error[E0477]: the type `impl Debug` does not fulfill the required lifetime
--> src/main.rs:27:5
|
27 | type Assoc2 = impl Debug + 'b;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
note: type must outlive the lifetime `'b` as defined on the impl at 25:10
--> src/main.rs:25:10
|
25 | impl<'a, 'b> Trait<'a, 'b> for Test {
| ^^
error: aborting due to 2 previous errors
Most helpful comment
In https://github.com/rust-lang/rust/pull/63092, landed on 2019-07-28, written by @Centril and reviewed by @varkor, the tracking issue for
existential_type
was adjusted to this one.In https://github.com/rust-lang/rust/pull/63096, landed on 2019-07-29, written by @Centril and reviewed by @varkor, 3 ICEs were closed with reproducer tests added.
In https://github.com/rust-lang/rust/pull/63158, landed on 2019-07-31, written by @JohnTitor and reviewed by @Centril, 1 ICE was closed with a reproducer test added.
In https://github.com/rust-lang/rust/pull/63180, landed on 2019-08-03, written by @varkor and reviewed by @Centril, the syntax was changed to
type Foo = impl Bar;
through a temporary hack in the parser. @varkor will follow up to remove that hack. The new feature gate name istype_alias_impl_trait
and the old oneexistential_type
has been removed.In https://github.com/rust-lang/rustc-guide/pull/402, landed on 2019-08-29, written by @varkor and reviewed by @mark-i-m, @Arnavion, and @spastorino, the rustc guide description was updated.