rust-lang / rust

Empowering everyone to build reliable and efficient software.
https://www.rust-lang.org
Other
98.06k stars 12.69k forks source link

Tracking issue for RFC 2515, "Permit impl Trait in type aliases" #63063

Open Centril opened 5 years ago

Centril commented 5 years ago

This is a tracking issue for the RFC "Permit impl Trait in type aliases" (rust-lang/rfcs#2515) which is implemented under the following #![feature(..)] gates:

About tracking issues

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.

Steps

Unresolved questions

alexreg commented 5 years ago

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

varkor commented 5 years ago

@alexreg: thanks for the offer — I'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 :)

Nemo157 commented 5 years ago

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

crlf0710 commented 5 years ago

Got a tiny question: Is type Foo = impl Debug equivalent to type Foo = impl Debug + '_ or type Foo = impl Debug + 'static?

Centril commented 5 years ago

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.

djc commented 4 years ago

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

Centril commented 4 years ago

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

Aaron1011 commented 4 years ago

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

spunit262 commented 4 years ago
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.

CodeSandwich commented 4 years ago

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

dhardy commented 4 years ago

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

Aaron1011 commented 4 years ago

I believe that your last example should actaully be illegal: https://github.com/rust-lang/rust/issues/52843#issuecomment-457578915

dhardy commented 4 years ago

No, Aaron, because here MyType<T> is an alias for Result<T, T> which has no bounds on its generics.

Aaron1011 commented 4 years ago

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.

dhardy commented 4 years ago

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

Aaron1011 commented 4 years ago

@dhardy: Here's an example of code which works in the outer scope, but not the inner scope:

https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=8b3fd5bbb3b1e9ebb4d2e4b874b696d3

Move produce_it into the inner scope to break it.

dhardy commented 4 years ago

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.

Centril commented 4 years ago

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

dhardy commented 4 years ago

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 }

Done: https://github.com/rust-lang/rust/issues/66980

TimDiekmann commented 4 years ago

~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
varkor commented 4 years ago

@TimDiekmann: thanks, this is any one of these (seemingly duplicate) issues.

Aaron1011 commented 4 years ago

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.

eddyb commented 4 years ago

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.

Ekleog commented 4 years ago

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 {
        ()
    }
}

(playground link)

I'd guess this just hasn't been RFC'd yes, but does someone know if thought on the topic have already started?

Ekleog commented 4 years ago

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

alercah commented 4 years ago

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

CAD97 commented 4 years ago

@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 ```rust 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 ```rust type Factory { type Item = impl Debug; fn produce(&mut self) -> Self::Item { () } } ``` The two interpretations: - @Ekleog's: the TAIT is uniquely constrained by the default implementation(s) of functions on the trait. Implementors of the trait see it as an opaque type (unless per normal TAIT rules they see inside it (which I think they wouldn't because the scope is the trait declaration)), and thus cannot unify with it. For this to be useful, all methods returning the type must be defaulted and overriden together with the associated type. - @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 ```rust trait Factory { fn make() -> impl Debug; } ``` to ```rust 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

Dirbaio commented 4 years ago

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)

haroldm commented 4 years ago

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);
}

(playground)

When defining the type directly in the function prototype, it does compile (playground).

Arnavion commented 4 years ago

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

tomasgvivo commented 4 years ago

Any update on this topic?

mzohreva commented 3 years ago

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
ijackson commented 3 years ago

Can we stabilise this in its current form, and then add more defining uses later?

Judging by the "mentioned this issue" traffic this seems like an important missing feature.

sagebind commented 3 years ago

@ijackson Based on other open issues labeled with F-type_alias_impl_trait it looks like the implementation may be currently too buggy to be stabilized, regardless of how many people want it.

ijackson commented 3 years ago

@ijackson Based on other open issues labeled with F-type_alias_impl_trait it looks like the implementation may be currently too buggy to be stabilized, regardless of how many people want it.

Urgh. Thanks for pointing out what I should have looked for myself. Hmmmm.

oli-obk commented 3 years ago

I have created a min_type_alias_impl_trait feature gate (WIP PR: https://github.com/rust-lang/rust/pull/82898) that only permits type alias impl trait in function and method return positions. Modulo a few bugs that I'm also fixing right now, this should be easier to stabilize than the full feature.

joshtriplett commented 3 years ago

@oli-obk Awesome! Thank you for working on that.

WaffleLapkin commented 3 years ago

@oli-obk is that expected, that all code that was using #![feature(type_alias_impl_trait)] now broken, because it also requires #![feature(min_type_alias_impl_trait)] now?

#![feature(type_alias_impl_trait)]
pub type Empty<T> = impl Iterator<Item = T>;

pub fn empty<T>() -> Empty<T> {
    None.into_iter()
}
error[E0658]: `impl Trait` in type aliases is unstable
 --> src/lib.rs:3:21
  |
3 | pub type Empty<T> = impl Iterator<Item = T>;
  |                     ^^^^^^^^^^^^^^^^^^^^^^^
  |
  = note: see issue #63063 <https://github.com/rust-lang/rust/issues/63063> for more information
  = help: add `#![feature(min_type_alias_impl_trait)]` to the crate attributes to enable

warning: the feature `type_alias_impl_trait` is incomplete and may not be safe to use and/or cause compiler crashes
 --> src/lib.rs:1:12
  |
1 | #![feature(type_alias_impl_trait)]
  |            ^^^^^^^^^^^^^^^^^^^^^
  |
  = note: `#[warn(incomplete_features)]` on by default
  = note: see issue #63063 <https://github.com/rust-lang/rust/issues/63063> for more information

(playground)

I would expect #![feature(type_alias_impl_trait)] to include #![feature(min_type_alias_impl_trait)].

oli-obk commented 3 years ago

I would expect #![feature(type_alias_impl_trait)] to include #![feature(min_type_alias_impl_trait)].

While that could be done, it is a bit annoying to get such a scheme right. As it's an unstable feature, I didn't think about it more. If it's really a pain point we could implement this, but historically the diagnostics about getting the feature gate wrong in that case have been quite suboptimal (with const_fn vs min_const_fn we had the situation where you did not get a const_fn suggestion if you had min_const_fn active).

phaylon commented 3 years ago

I've been experimenting a bit with using min_type_alias_impl_trait to achieve (sort of) ad-hoc newtyping in Rust.

Example:

#![feature(min_type_alias_impl_trait)]

trait AddSelf: std::ops::Add<Output = Self> + Sized {}
impl<T> AddSelf for T where T: std::ops::Add<Output = Self> {}

type MyI64 =
    impl std::fmt::Debug
        + std::fmt::Display
        + AddSelf
        + Copy
        + From<i64>
        + Into<i64>;

fn my_i64(value: i64) -> MyI64 { value }

allowing things like

// compiles
let a = my_i64(23);
let b = my_i64(42);
println!("{} + {} = {}", a, b, a+b);

let x = MyI64::from(2i64 * a.into());
println!("{}", x);

// fail to compile
let x = a + 3i64;
let x = a * a;

Now, it's possible that I got a bit caught up in my excitement and there are serious downsides to this that I'm missing, but if that use-case is intended/supported that would be great. It might have been discussed more in-depth somewhere else and I've missed it.

Why I'm writing this: I'm wondering if it were possible for type errors regarding MyI64 to mention the type alias in the messages, or in a separate note, or simply have the source span declaring the impl Trait include the alias part?

On a positive note, after experimenting with min_type_alias_impl_trait for a bit this minor diagnostics inconvenience is the only thing I ran into.

I understand this might be complicated and out of scope for the min_* stabilization, of course.

petar-dambovaliev commented 3 years ago

It would be really sweet, if i can do this.

pub trait MyTrait<T>: Sized {
    type Type = impl Future<Output = T> + Send;
}

Then i don't need to box my futures.

Arnavion commented 3 years ago

@petar-dambovaliev The trait definition needs to specify a bound. The syntax you used goes in the impls.

https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=5e81b89ae9cd0e686b2dfaed90a51418

petar-dambovaliev commented 3 years ago

@petar-dambovaliev The trait definition needs to specify a bound. The syntax you used goes in the impls.

https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=5e81b89ae9cd0e686b2dfaed90a51418

I want to set a default for the associated type.

CAD97 commented 3 years ago

One random worry I thought of:

There's currently two ways to effectively seal a trait:

trait A {
    // requires naming an unnameable type
    fn sealed(_: Voldemort);
}

// requires implementing an unnameable trait
trait B: Voldemort {
}

The latter still works in the face of type = impl Trait;, but the former no longer prevents implementation of the trait once you can use TAIT to name the parameter type.

This seems like the correct behavior for TAIT, but we should make sure to communicate that the former method for pseudo-sealing the trait won't work once full TAIT is available.

Nemo157 commented 3 years ago

@CAD97 do you have an example of how your trait A will be affected by TAIT? The type alias is treated as a different type to the concrete type it aliases, so even with extra public API to allow creating a TAIT of the Voldemort type it's not possible to implement the trait: playground.

CAD97 commented 3 years ago

Ah, it seems there are two things in the way of this causing a problem.

  1. I assumed that use as a trait method argument would be enough to be a defining use (as it does limit it to be one type), and
  2. I assumed that a TAIT constrained in this fashion would be allowed to be used as the function argument.

It's a question of to whom exactly the TAIT is opaque. It's obviously not opaque to the defining usage(s). So long as trait method argument use isn't considered a defining use (and you can make an argument that it could be), and the TAIT's use in this position is always in the opaque region rather than the defining/transparent region, then TAIT won't break this method of pseudo-sealing traits.

ibraheemdev commented 3 years ago

Is there a list of known bugs, blocking min_type_alias_impl_trait from being stabilized? This feature means that literally hundreds of Box::pins can be removed from the async ecosystem, and writing low-level async code becomes waay more ergonomic as this also eliminates the need for many custom future types. Of course, without GATs they can't be completely eliminated, but this still represents a huge step forward towards real async fns in traits and I know that many of us are eagerly .awaiting it. The only unresolved question listed here is regarding defining uses, but I don't think that applies to the minimal feature gate .... @oli-obk is there a release that can be a reasonable target for min_type_alias_impl_trait landing on stable?

WaffleLapkin commented 3 years ago

@CAD97 note that with full TAIT you can name the type (it still doesn't allow you to implement the trait though):

type V = impl Any;

fn setv<T: A>() {
    fn teq<T>(_: &T, _: &T) {}

    let x = todo!();
    let y = todo!();
    teq(&x, &y);

    T::sealed(x);

    let _f = move || -> V { y };
}

(playground)

oli-obk commented 3 years ago

A bit unorganized: https://hackmd.io/tB3V8MP5S66zHIWTUdRvQw but it holds all current info I think. I also created a github label for Tait, but idk how complete the list of tagged things is. I won't work on any of this until July though

ibraheemdev commented 3 years ago

Thanks for the link, and thanks for all of your amazing work on this! I look forward to seeing it stabilize soon.