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

Tracking issue for impl Trait (RFC 1522, RFC 1951, RFC 2071) #34511

Closed
21 of 29 tasks
Tracked by #63066
aturon opened this issue Jun 27, 2016 · 417 comments
Closed
21 of 29 tasks
Tracked by #63066

Tracking issue for impl Trait (RFC 1522, RFC 1951, RFC 2071) #34511

aturon opened this issue Jun 27, 2016 · 417 comments
Labels
B-RFC-implemented Blocker: Approved by a merged RFC and implemented. B-unstable Blocker: Implemented in the nightly compiler and unstable. C-tracking-issue Category: An issue tracking the progress of sth. like the implementation of an RFC disposition-merge This issue / PR is in PFCP or FCP with a disposition to merge it. finished-final-comment-period The final comment period is finished for this PR / Issue. T-lang Relevant to the language team, which will review and decide on the PR/issue.

Comments

@aturon
Copy link
Member

aturon commented Jun 27, 2016

NEW TRACKING ISSUE = #63066

Implementation status

The basic feature as specified in RFC 1522 is implemented, however there have been revisions that are still in need of work:

RFCs

There have been a number of RFCs regarding impl trait, all of which are tracked by this central tracking issue.

Unresolved questions

The implementation has raised a number of interesting questions as well:

@aturon aturon added B-unstable Blocker: Implemented in the nightly compiler and unstable. B-RFC-approved Blocker: Approved by a merged RFC but not yet implemented. labels Jun 27, 2016
@pthariensflame
Copy link
Contributor

pthariensflame commented Jul 15, 2016

@aturon Can we actually put the RFC in the repository? (@mbrubeck commented there that this was a problem.)

@aturon
Copy link
Member Author

aturon commented Jul 15, 2016

Done.

@eddyb
Copy link
Member

eddyb commented Jul 31, 2016

First attempt at implementation is #35091 (second, if you count my branch from last year).

One problem I ran into is with lifetimes. Type inference likes to put region variables everywhere and without any region-checking changes, those variables don't infer to anything other than local scopes.
However, the concrete type must be exportable, so I restricted it to 'static and explicitly named early-bound lifetime parameters, but it's never any of those if any function is involved - even a string literal doesn't infer to 'static, it's pretty much completely useless.

One thing I thought of, that would have 0 impact on region-checking itself, is to erase lifetimes:

  • nothing exposing the concrete type of an impl Trait should care about lifetimes - a quick search for Reveal::All suggests that's already the case in the compiler
  • a bound needs to be placed on all concrete types of impl Trait in the return type of a function, that it outlives the call of that function - this means that any lifetime is, by necessity, either 'static or one of the lifetime parameters of the function - even if we can't know which (e.g. "shortest of 'a and 'b")
  • we must choose a variance for the implicit lifetime parametrism of impl Trait (i.e. on all lifetime parameters in scope, same as with type parameters): invariance is easiest and gives more control to the callee, while contravariance lets the caller do more and would require checking that every lifetime in the return type is in a contravariant position (same with covariant type parametrism instead of invariant)
  • the auto trait leaking mechanism requires that a trait bound may be put on the concrete type, in another function - since we've erased the lifetimes and have no idea what lifetime goes where, every erased lifetime in the concrete type will have to be substituted with a fresh inference variable that is guaranteed to not be shorter than the shortest lifetime out of all actual lifetime parameters; the problem lies in the fact that trait impls can end up requiring stronger lifetime relationships (e.g. X<'a, 'a> or X<'static>), which must be detected and errored on, as they can't be proven for those lifetimes

That last point about auto trait leakage is my only worry, everything else seems straight-forward.
It's not entirely clear at this point how much of region-checking we can reuse as-is. Hopefully all.

cc @rust-lang/lang

@arielb1
Copy link
Contributor

arielb1 commented Jul 31, 2016

@eddyb

But lifetimes are important with impl Trait - e.g.

fn get_debug_str(s: &str) -> impl fmt::Debug {
    s
}

fn get_debug_string(s: &str) -> impl fmt::Debug {
    s.to_string()
}

fn good(s: &str) -> Box<fmt::Debug+'static> {
    // if this does not compile, that would be quite annoying
    Box::new(get_debug_string())
}

fn bad(s: &str) -> Box<fmt::Debug+'static> {
    // if this *does* compile, we have a problem
    Box::new(get_debug_str())
}

I mentioned that several times in the RFC threads

@arielb1
Copy link
Contributor

arielb1 commented Jul 31, 2016

trait-object-less version:

fn as_debug(s: &str) -> impl fmt::Debug;

fn example() {
    let mut s = String::new("hello");
    let debug = as_debug(&s);
    s.truncate(0);
    println!("{:?}", debug);
}

This is either UB or not depending on the definition of as_debug.

@eddyb
Copy link
Member

eddyb commented Jul 31, 2016

@arielb1 Ah, right, I forgot that one of the reasons I did what I did was to only capture lifetime parameters, not anonymous late-bound ones, except it doesn't really work.

@eddyb
Copy link
Member

eddyb commented Jul 31, 2016

@arielb1 Do we have a strict outlives relation we can put between lifetimes found in the concrete type pre-erasure and late-bound lifetimes in the signature? Otherwise, it might not be a bad idea to just look at lifetime relationships and insta-fail any direct or indirect 'a outlives 'b where 'a is anything other than 'static or a lifetime parameter and 'b appears in the concrete type of an impl Trait.

@nikomatsakis
Copy link
Contributor

Sorry for taking a while to write back here. So I've been thinking
about this problem. My feeling is that we do, ultimately, have to (and
want to) extend regionck with a new kind of constraint -- I'll call it
an \in constraint, because it allows you to say something like '0 \in {'a, 'b, 'c}, meaning that the region used for '0 must be
either 'a, 'b, or 'c. I'm not sure of the best way to integrate
this into solving itself -- certainly if the \in set is a singleton
set, it's just an equate relation (which we don't currently have as a
first-class thing, but which can be composed out of two bounds), but
otherwise it makes things complicated.

This all relates to my desire to make the set of region constraints
more expressive than what we have today. Certainly one could compose a
\in constraint out of OR and == constraints. But of course more
expressive constraints are harder to solve and \in is no different.

Anyway, let me just lay out a bit of my thinking here. Let's work with this
example:

pub fn foo<'a,'b>(x: &'a [u32], y: &'b [u32]) -> impl Iterator<Item=u32> {...}

I think the most accurate desugaring for a impl Trait is probably a
new type:

pub struct FooReturn<'a, 'b> {
    field: XXX // for some suitable type XXX
}

impl<'a,'b> Iterator for FooReturn<'a,'b> {
    type Item = <XXX as Iterator>::Item;
}

Now the impl Iterator<Item=u32> in foo should behave the same as
FooReturn<'a,'b> would behave. It's not a perfect match though. One
difference, for example, is variance, as eddyb brought up -- I am
assuming we will make impl Foo-like types invariant over the type
parameters of foo. The auto trait behavior works out, however.
(Another area where the match might not be ideal is if we ever add the
ability to "pierce" the impl Iterator abstraction, so that code
"inside" the abstraction knows the precise type -- then it would sort
of have an implicit "unwrap" operation taking place.)

In some ways a better match is to consider a kind of synthetic trait:

trait FooReturn<'a,'b> {
    type Type: Iterator<Item=u32>;
}

impl<'a,'b> FooReturn<'a,'b> for () {
    type Type = XXX;
}

Now we could consider the impl Iterator type to be like <() as FooReturn<'a,'b>>::Type. This is also not a perfect match, because we
would ordinarily normalize it away. You might imagine using specialization
to prevent that though:

trait FooReturn<'a,'b> {
    type Type: Iterator<Item=u32>;
}

impl<'a,'b> FooReturn<'a,'b> for () {
    default type Type = XXX; // can't really be specialized, but wev
}

In this case, <() as FooReturn<'a,'b>>::Type would not normalize,
and we have a much closer match. The variance, in particular, behaves
right; if we ever wanted to have some type that are "inside" the
abstraction, they would be the same but they are allowed to
normalize. However, there is a catch: the auto trait stuff doesn't
quite work. (We may want to consider harmonizing things here,
actually.)

Anyway, my point in exploring these potential desugarings is not to
suggest that we implement "impl Trait" as an actual desugaring
(though it might be nice...) but to give an intuition for our job. I
think that the second desugaring -- in terms of projections -- is a
pretty helpful one for guiding us forward.

One place that this projection desugaring is a really useful guide is
the "outlives" relation. If we wanted to check whether <() as FooReturn<'a,'b>>::Type: 'x, RFC 1214 tells us that we can prove this
so long as 'a: 'x and 'b: 'x holds. This is I think how we want
to handle things for impl trait as well.

At trans time, and for auto-traits, we will have to know what XXX
is, of course. The basic idea here, I assume, is to create a type
variable for XXX and check that the actual values which are returned
can all be unified with XXX. That type variable should, in theory,
tell us our answer. But of course the problem is that this type
variable may refer to a lot of regions which are not in scope in the
fn signature -- e.g., the regions of the fn body. (This same problem
does not occur with types; even though, technically, you could put
e.g. a struct declaration in the fn body and it would be unnameable,
that's a kind of artificial restriction -- one could just as well move
the struct outside the fn.)

If you look both at the struct desugaring or the impl, there is an
(implicit in the lexical structure of Rust) restriction that XXX can
only name either 'static or lifetimes like 'a and 'b, which
appear in the function signature. That is the thing we are not
modeling here. I'm not sure the best way to do it -- some type
inference schemes have a more direct representation of scoping, and
I've always wanted to add that to Rust, to help us with closures. But
let's think about smaller deltas first I guess.

This is where the \in constraint comes from. One can imagine adding
a type-check rule that (basically) FR(XXX) \subset {'a, 'b} --
meaning that the "free regions" appearing in XXX can only be 'a and
'b. This would wind up translating to \in requirements for the
various regions that appear in XXX.

Let's look at an actual example:

fn foo<'a,'b>(x: &'a [u32], y: &'b [u32]) -> impl Iterator<Item=u32> {
    if condition { x.iter().cloned() } else { y.iter().cloned() }
}

Here, the type if condition is true would be something like
Cloned<SliceIter<'a, i32>>. But if condition is false, we would
want Cloned<SliceIter<'b, i32>>. Of course in both cases we would
wind up with something like (using numbers for type/region variables):

Cloned<SliceIter<'0, i32>> <: 0
'a: '0 // because the source is x.iter()
Cloned<SliceIter<'1, i32>> <: 0
'b: '1 // because the source is y.iter()

If we then instantiate the variable 0 to Cloned<SliceIter<'2, i32>>,
we have '0: '2 and '1: '2, or a total set of region relations
like:

'a: '0
'0: '2
'b: '1
'1: '2
'2: 'body // the lifetime of the fn body

So what value should we use for '2? We have also the additional
constraint that '2 in {'a, 'b}. With the fn as written, I think we
would have to report an error, since neither 'a nor 'b is a
correct choice. Interestingly, though, if we added the constraint 'a: 'b, then there would be a correct value ('b).

Note that if we just run the normal algorithm, we would wind up with
'2 being 'body. I'm not sure how to handle the \in relations
except for exhaustive search (though I can imagine some special
cases).

OK, that's as far as I've gotten. =)

@nikomatsakis
Copy link
Contributor

On the PR #35091, @arielb1 wrote:

I don't like the "capture all lifetimes in the impl trait" approach and would prefer something more like lifetime elision.

I thought it would make more sense to discuss here. @arielb1, can you elaborate more on what you have in mind? In terms of the analogies I made above, I guess you are fundamentally talking about "pruning" the set of lifetimes that would appear either as parameters on the newtype or in the projection (i.e., <() as FooReturn<'a>>::Type instead of <() as FooReturn<'a,'b>>::Type or something?

I don't think that the lifetime elision rules as they exist would be a good guide in this respect: if we just picked the lifetime of &self to include only, then we wouldn't necessarily be able to include the type parameters from the Self struct, nor type parameters from the method, since they may have WF conditions that require us to name some of the other lifetimes.

Anyway, it'd be great to see some examples that illustrate the rules you have in mind, and perhaps any advantages thereof. :) (Also, I guess we would need some syntax to override the choice.) All other things being equal, if we can avoid having to pick from N lifetimes, I'd prefer that.

bors added a commit that referenced this issue Aug 12, 2016
Implement `impl Trait` in return type position by anonymization.

This is the first step towards implementing `impl Trait` (cc #34511).
`impl Trait` types are only allowed in function and inherent method return types, and capture all named lifetime and type parameters, being invariant over them.
No lifetimes that are not explicitly named lifetime parameters are allowed to escape from the function body.
The exposed traits are only those listed explicitly, i.e. `Foo` and `Clone` in `impl Foo + Clone`, with the exception of "auto traits" (like `Send` or `Sync`) which "leak" the actual contents.

The implementation strategy is anonymization, i.e.:
```rust
fn foo<T>(xs: Vec<T>) -> impl Iterator<Item=impl FnOnce() -> T> {
    xs.into_iter().map(|x| || x)
}

// is represented as:
type A</*invariant over*/ T> where A<T>: Iterator<Item=B<T>>;
type B</*invariant over*/ T> where B<T>: FnOnce() -> T;
fn foo<T>(xs: Vec<T>) -> A<T> {
    xs.into_iter().map(|x| || x): $0 where $0: Iterator<Item=$1>, $1: FnOnce() -> T
}
```
`$0` and `$1` are resolved (to `iter::Map<vec::Iter<T>, closure>` and the closure, respectively) and assigned to `A` and `B`, after checking the body of `foo`. `A` and `B` are *never* resolved for user-facing type equality (typeck), but always for the low-level representation and specialization (trans).

The "auto traits" exception is implemented by collecting bounds like `impl Trait: Send` that have failed for the obscure `impl Trait` type (i.e. `A` or `B` above), pretending they succeeded within the function and trying them again after type-checking the whole crate, by replacing `impl Trait` with the real type.

While passing around values which have explicit lifetime parameters (of the function with `-> impl Trait`) in their type *should* work, regionck appears to assign inference variables in *way* too many cases, and never properly resolving them to either explicit lifetime parameters, or `'static`.
We might not be able to handle lifetime parameters in `impl Trait` without changes to lifetime inference, but type parameters can have arbitrary lifetimes in them from the caller, so most type-generic usecases (or not generic at all) should not run into this problem.

cc @rust-lang/lang
@petrochenkov
Copy link
Contributor

petrochenkov commented Aug 12, 2016

I haven't seen interactions of impl Trait with privacy discussed anywhere.
Now fn f() -> impl Trait can return a private type S: Trait similarly to trait objects fn f() -> Box<Trait>. I.e. objects of private types can walk freely outside of their module in anonymized form.
This seems reasonable and desirable - the type itself is an implementation detail, only its interface, available through a public trait Trait is public.
However there's one difference between trait objects and impl Trait. With trait objects alone all trait methods of private types can get internal linkage, they will still be callable through function pointers. With impl Traits trait methods of private types are directly callable from other translation units. The algorithm doing "internalization" of symbols will have to try harder to internalize methods only for types not anonymized with impl Trait, or to be very pessimistic.

@arielb1
Copy link
Contributor

arielb1 commented Aug 14, 2016

@nikomatsakis

The "explicit" way to write foo would be

fn foo<'a: 'c,'b: 'c,'c>(x: &'a [u32], y: &'b [u32]) -> impl Iterator<Item=u32> + 'c {
    if condition { x.iter().cloned() } else { y.iter().cloned() }
}

Here there is no question about the lifetime bound. Obviously, having to write the lifetime bound each time would be quite repetitive. However, the way we deal with that kind of repetition is generally through lifetime elision. In the case of foo, elision would fail and force the programmer to explicitly specify lifetimes.

I am opposed to adding explicitness-sensitive lifetime elision as @eddyb did only in the specific case of impl Trait and not otherwise.

@nikomatsakis
Copy link
Contributor

@arielb1 hmm, I'm not 100% sure how to think about this proposed syntax in terms of the "desugarings" that I discussed. It allows you to specify what appears to be a lifetime bound, but the thing we are trying to infer is mostly what lifetimes appear in the hidden type. Does this suggest that at most one lifetime could be "hidden" (and that it would have to be specified exactly?)

It seems like it's not always the case that a "single lifetime parameter" suffices:

fn foo<'a, 'b>(x: &'a [u32], y: &'b [u32]) -> impl Iterator<Item=u32> {
    x.iter().chain(y).cloned()
}

In this case, the hidden iterator type refers to both 'a and 'b (although it is variant in both of them; but I guess we could come up with an example that is invariant).

@nikomatsakis
Copy link
Contributor

nikomatsakis commented Aug 18, 2016

So @aturon and I discussed this issue somewhat and I wanted to share. There are really a couple of orthogonal questions here and I want to separate them out. The first question is "what type/lifetime parameters can potentially be used in the hidden type?" In terms of the (quasi-)desugaring into a default type, this comes down to "what type parameters appear on the trait we introduce". So, for example, if this function:

fn foo<'a, 'b, T>() -> impl Trait { ... }

would get desugared to something like:

fn foo<'a, 'b, T>() -> <() as Foo<...>>::Type { ... }
trait Foo<...> {
  type Type: Trait;
}
impl<...> Foo<...> for () {
  default type Type = /* inferred */;
}

then this question comes down to "what type parameters appear on the trait Foo and its impl"? Basically, the ... here. Clearly this include include the set of type parameters that appear are used by Trait itself, but what additional type parameters? (As I noted before, this desugaring is 100% faithful except for the leakage of auto traits, and I would argue that we should leak auto traits also for specializable impls.)

The default answer we've been using is "all of them", so here ... would be 'a, 'b, T (along with any anonymous parameters that may appear). This may be a reasonable default, but it's not necessarily the best default. (As @arielb1 pointed out.)

This has an effect on the outlives relation, since, in order to determine that <() as Foo<...>>::Type (referring to some particular, opaque instantiation of impl Trait) outlives 'x, we effectively must show that ...: 'x (that is, every lifetime and type parameter).

This is why I say it is not enough to consider lifetime parameters: imagine that we have some call to foo like foo::<'a0, 'b0, &'c0 i32>. This implies that all three lifetimes, '[abc]0, must outlive 'x -- in other words, so long as the return value is in use, this will prolog the loans of all data given into the function. But, as @arielb1 poitned out, elision suggests that this will usually be longer than necessary.

So I imagine that what we need is:

  • to settle on a reasonable default, perhaps using intution from elision;
  • to have an explicit syntax for when the default is not appropriate.

@aturon spitballed something like impl<...> Trait as the explicit syntax, which seems reasonable. Therefore, one could write:

fn foo<'a, 'b, T>(...) -> impl<T> Trait { }

to indicate that the hidden type does not in fact refer to 'a or 'b but only T. Or one might write impl<'a> Trait to indicate that neither 'b nor T are captured.

As for the defaults, it seems like having more data would be pretty useful -- but the general logic of elision suggests that we would do well to capture all the parameters named in the type of self, when applicable. E.g., if you have fn foo<'a,'b>(&'a self, v: &'b [u8]) and the type is Bar<'c, X>, then the type of self would be &'a Bar<'c, X> and hence we would capture 'a, 'c, and X by default, but not 'b.


Another related note is what the meaning of a lifetime bound is. I think that sound lifetime bounds have an existing meaning that should not be changed: if we write impl (Trait+'a) that means that the hidden type T outlives 'a. Similarly one can write impl (Trait+'static) to indicate that there are no borrowed pointers present (even if some lifetimes are captured). When inferring the hidden type T, this would imply a lifetime bound like $T: 'static, where $T is the inference variable we create for the hidden type. This would be handled in the usual way. From a caller's perspective, where the hidden type is, well, hidden, the 'static bound would allow us to conclude that impl (Trait+'static) outlives 'static even if there are lifetime parameters captured.

Here it just behaves exactly as the desugaring would behave:

fn foo<'a, 'b, T>() -> <() as Foo<'a, 'b, 'T>>::Type { ... }
trait Foo<'a, 'b, T> {
  type Type: Trait + 'static; // <-- note the `'static` bound appears here
}
impl<'a, 'b, T> Foo<...> for () {
  default type Type = /* something that doesn't reference `'a`, `'b`, or `T` */;
}

All of this is orthogonal from inference. We still want (I think) to add the notion of a "choose from" constraint and modify inference with some heuristics and, possibly, exhaustive search (the experience from RFC 1214 suggests that heuristics with a conservative fallback can actually get us very far; I'm not aware of people running into limitations in this respect, though there is probably an issue somewhere). Certainly, adding lifetime bounds like 'static or 'a` may influence inference, and thus be helpful, but that is not a perfect solution: for one thing, they are visible to the caller and become part of the API, which may not be desired.

@arielb1
Copy link
Contributor

arielb1 commented Aug 18, 2016

Possible options:

Explicit lifetime bound with output parameter elision

Like trait objects today, impl Trait objects have a single lifetime bound parameter, which is inferred using the elision rules.

Disadvantage: unergonomic
Advantage: clear

Explicit lifetime bounds with "all generic" elision

Like trait objects today, impl Trait objects have a single lifetime bound parameter.

However, elision creates a new early-bound parameters that outlives all explicit parameters:

fn foo<T>(&T) -> impl Foo
-->
fn foo<'total, T: 'total>(&T) -> impl Foo + 'total

Disadvantage: adds an early-bound parameter

more.

@nrc nrc added the T-lang Relevant to the language team, which will review and decide on the PR/issue. label Aug 19, 2016
@nrc nrc added B-RFC-implemented Blocker: Approved by a merged RFC and implemented. and removed B-RFC-approved Blocker: Approved by a merged RFC but not yet implemented. labels Aug 29, 2016
@Boscop
Copy link

Boscop commented Nov 15, 2016

I ran into this issue with impl Trait +'a and borrowing: #37790

@TimDiekmann
Copy link
Member

TimDiekmann commented Apr 25, 2019

I think I found a bug in the current existential type implementation.

Code
trait Collection {
    type Element;
}
impl<T> Collection for Vec<T> {
    type Element = T;
}

existential type Existential<T>: Collection<Element = T>;

fn return_existential<I>(iter: I) -> Existential<I::Item>
where
    I: IntoIterator,
    I::Item: Collection,
{
    let item = iter.into_iter().next().unwrap();
    vec![item]
}
Error
error: type parameter `I` is part of concrete type but not used in parameter list for existential type
  --> src/lib.rs:16:1
   |
16 | / {
17 | |     let item = iter.into_iter().next().unwrap();
18 | |     vec![item]
19 | | }
   | |_^

error: defining existential type use does not fully define existential type
  --> src/lib.rs:12:1
   |
12 | / fn return_existential<I>(iter: I) -> Existential<I::Item>
13 | | where
14 | |     I: IntoIterator,
15 | |     I::Item: Collection,
...  |
18 | |     vec![item]
19 | | }
   | |_^

error: could not find defining uses
  --> src/lib.rs:10:1
   |
10 | existential type Existential<T>: Collection<Element = T>;
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

playground

You can find this on stackoverflow, too.

@oli-obk
Copy link
Contributor

oli-obk commented Apr 25, 2019

I'm not 100% sure we can support this case out of the box, but what you can do is rewrite the function to have two generic parameters:

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

fn return_existential<I, J>(iter: I) -> Existential<J>
where
    I: IntoIterator<Item = J>,
{
    let item = iter.into_iter().next().unwrap();
    vec![item]
}

@TimDiekmann
Copy link
Member

Thanks!
Yup, this is what I did as posted on the stackoverflow post:

fn return_existential<I, T>(iter: I) -> Existential<T>
where
    I: IntoIterator<Item = T>,
    I::Item: Collection,
{
    let item = iter.into_iter().next().unwrap();
    vec![item]
}

@DoumanAsh
Copy link

Are there plans for impl Trait to be available within a context of trait?
Not only as associated type, but also as return value in methods.

@cramertj
Copy link
Member

impl trait in traits is a separate feature to the ones being tracked here, and does not presently have an RFC. There is a fairly long history of designs in this space, and further iteration is being held off until the implementation of 2071 (existential type) is stabilized, which is blocked on implementation issues as well as unresolved syntax (which has a separate RFC).

@alexreg
Copy link
Contributor

alexreg commented May 14, 2019

@cramertj The syntax is nearly resolved. I believe the main blocker is GAT now.

@varkor
Copy link
Member

varkor commented May 14, 2019

@alexreg: rust-lang/rfcs#2515 is still waiting on @withoutboats.

@alexreg
Copy link
Contributor

alexreg commented May 14, 2019

@varkor Yeah, I'm just being optimistic they'll see the light with that RFC soon. ;-)

@robbym
Copy link

robbym commented Jun 20, 2019

Will something like the following be possible?

#![feature(existential_type)]

trait MyTrait {}

existential type Interface: MyTrait;

struct MyStruct {}
impl MyTrait for MyStruct {}

fn with<F, U>(cb: F) -> U
where
    F: FnOnce(&mut Interface) -> U
{
    let mut s = MyStruct {};
    cb(&mut s)
}

@RustyYato
Copy link
Contributor

You can do this now, although only with a hint function to specify the concrete type of Interface

#![feature(existential_type)]

trait MyTrait {}

existential type Interface: MyTrait;

struct MyStruct {}
impl MyTrait for MyStruct {}

fn with<F, U>(cb: F) -> U
where
    F: FnOnce(&mut Interface) -> U
{

    fn hint(x: &mut MyStruct) -> &mut Interface { x }
    
    let mut s = MyStruct {};
    cb(hint(&mut s))
}

@CryZe
Copy link
Contributor

CryZe commented Jun 20, 2019

How would you write it if the callback would be able to choose its argument type? Actually nvm, I guess you could solve that one via a normal generic.

@Ekleog
Copy link

Ekleog commented Jun 20, 2019

@CryZe What you're looking for is unrelated to impl Trait. See rust-lang/rfcs#2413 for all I know about it.

It would potentially look something like that :

trait MyTrait {}

struct MyStruct {}
impl MyTrait for MyStruct {}

fn with<F, U>(cb: F) -> U
where
    F: for<I: Interface> FnOnce(&mut I) -> U
{
    let mut s = MyStruct {};
    cb(hint(&mut s))
}

@robbym
Copy link

robbym commented Jun 20, 2019

@KrishnaSannasi Ah, interesting. Thanks!

@jethrogb
Copy link
Contributor

Is this supposed to work?

#![feature(existential_type)]

trait MyTrait {
    type AssocType: Send;
    fn ret(&self) -> Self::AssocType;
}

impl MyTrait for () {
    existential type AssocType: Send;
    fn ret(&self) -> Self::AssocType {
        ()
    }
}

impl<'a> MyTrait for &'a () {
    existential type AssocType: Send;
    fn ret(&self) -> Self::AssocType {
        ()
    }
}

trait MyLifetimeTrait<'a> {
    type AssocType: Send + 'a;
    fn ret(&self) -> Self::AssocType;
}

impl<'a> MyLifetimeTrait<'a> for &'a () {
    existential type AssocType: Send + 'a;
    fn ret(&self) -> Self::AssocType {
        *self
    }
}

@DoumanAsh
Copy link

DoumanAsh commented Jul 25, 2019

Do we have to keep existential keyword in language for existential_type feature?

@cramertj
Copy link
Member

@jethrogb Yes. The fact that it currently doesn't is a bug.

@jethrogb
Copy link
Contributor

@cramertj Ok. Should I file a separate issue for that or is my post here enough?

@cramertj
Copy link
Member

Filing an issue would be great, thanks! :)

@alexreg
Copy link
Contributor

alexreg commented Jul 26, 2019

Do we have to keep existential keyword in language for existential_type feature?

I think the intention is to immediately deprecate this when the type-alias-impl-trait feature is implemented (i.e., put in a lint) and eventually remove it from the syntax.

Someone can maybe clarify though.

@Centril
Copy link
Contributor

Centril commented Jul 28, 2019

Closing this in favor of a meta-issue which tracks impl Trait more generally: #63066

@Centril Centril closed this as completed Jul 28, 2019
@TitanThinktank

This comment has been minimized.

@rust-lang rust-lang locked and limited conversation to collaborators Aug 19, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
B-RFC-implemented Blocker: Approved by a merged RFC and implemented. B-unstable Blocker: Implemented in the nightly compiler and unstable. C-tracking-issue Category: An issue tracking the progress of sth. like the implementation of an RFC disposition-merge This issue / PR is in PFCP or FCP with a disposition to merge it. finished-final-comment-period The final comment period is finished for this PR / Issue. T-lang Relevant to the language team, which will review and decide on the PR/issue.
Projects
None yet
Development

No branches or pull requests