Join GitHub today
GitHub is home to over 28 million developers working together to host and review code, manage projects, and build software together.
Sign upTracking issue for `impl Trait` (RFC 1522, RFC 1951, RFC 2071) #34511
Comments
aturon
added
B-unstable
B-RFC-approved
labels
Jun 27, 2016
This comment has been minimized.
This comment has been minimized.
|
@aturon Can we actually put the RFC in the repository? (@mbrubeck commented there that this was a problem.) |
This comment has been minimized.
This comment has been minimized.
|
Done. |
eddyb
referenced this issue
Jul 28, 2016
Merged
Implement `impl Trait` in return type position by anonymization. #35091
This comment has been minimized.
This comment has been minimized.
|
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. One thing I thought of, that would have 0 impact on region-checking itself, is to erase lifetimes:
That last point about auto trait leakage is my only worry, everything else seems straight-forward. |
This comment has been minimized.
This comment has been minimized.
|
But lifetimes are important with 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 |
This comment has been minimized.
This comment has been minimized.
|
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 |
This comment has been minimized.
This comment has been minimized.
|
@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. |
This comment has been minimized.
This comment has been minimized.
|
@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 |
This comment has been minimized.
This comment has been minimized.
|
Sorry for taking a while to write back here. So I've been thinking This all relates to my desire to make the set of region constraints Anyway, let me just lay out a bit of my thinking here. Let's work with this pub fn foo<'a,'b>(x: &'a [u32], y: &'b [u32]) -> impl Iterator<Item=u32> {...}I think the most accurate desugaring for a 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 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 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, Anyway, my point in exploring these potential desugarings is not to One place that this projection desugaring is a really useful guide is At trans time, and for auto-traits, we will have to know what If you look both at the struct desugaring or the impl, there is an This is where the 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
If we then instantiate the variable 0 to
So what value should we use for Note that if we just run the normal algorithm, we would wind up with OK, that's as far as I've gotten. =) |
This comment has been minimized.
This comment has been minimized.
|
On the PR #35091, @arielb1 wrote:
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., 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 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. |
added a commit
that referenced
this issue
Aug 12, 2016
This comment has been minimized.
This comment has been minimized.
|
I haven't seen interactions of |
This comment has been minimized.
This comment has been minimized.
|
The "explicit" way to write 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 I am opposed to adding explicitness-sensitive lifetime elision as @eddyb did only in the specific case of |
This comment has been minimized.
This comment has been minimized.
|
@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 |
This comment has been minimized.
This comment has been minimized.
|
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 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 The default answer we've been using is "all of them", so here This has an effect on the outlives relation, since, in order to determine that This is why I say it is not enough to consider lifetime parameters: imagine that we have some call to So I imagine that what we need is:
@aturon spitballed something like fn foo<'a, 'b, T>(...) -> impl<T> Trait { }to indicate that the hidden type does not in fact refer to 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 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 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 |
This comment has been minimized.
This comment has been minimized.
|
Possible options: Explicit lifetime bound with output parameter elisionLike trait objects today, Disadvantage: unergonomic Explicit lifetime bounds with "all generic" elisionLike trait objects today, 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 + 'totalDisadvantage: adds an early-bound parameter more. |
nrc
added
the
T-lang
label
Aug 19, 2016
nrc
added
B-RFC-implemented
and removed
B-RFC-approved
labels
Aug 29, 2016
Mark-Simulacrum
referenced this issue
Sep 11, 2016
Closed
RFC: Add unboxed, abstract return types #105
This comment has been minimized.
This comment has been minimized.
Boscop
commented
Nov 15, 2016
|
I ran into this issue with impl Trait +'a and borrowing: #37790 |
jeanphilippeD
referenced this issue
Nov 21, 2016
Closed
Replace clang.rs iterator with generic boxed Map<Range<>> #213
alexreg
referenced this issue
Aug 20, 2018
Merged
`impl trait` in bindings (feature: impl-trait-existential-types) #53542
Arnavion
referenced this issue
Aug 29, 2018
Open
impl-trait return type is bounded by all input type parameters, even when unnecessary #42940
Centril
referenced this issue
Sep 13, 2018
Open
generic existential types should (at most) warn, not error, if a type parameter is unused #54184
added a commit
that referenced
this issue
Sep 25, 2018
This comment has been minimized.
This comment has been minimized.
runiq
commented
Sep 26, 2018
|
Triage: #53542 has been merged, so the ticky boxes for |
jonhoo
referenced this issue
Oct 15, 2018
Open
Incorrect inference of lifetime bound for existential type #55099
oli-obk
referenced this issue
Nov 5, 2018
Open
RFC for trait bounds on generic parameters of const fns #8
eddyb
referenced this issue
Nov 25, 2018
Merged
trigger unsized coercions keyed on Sized bounds #56219
This comment has been minimized.
This comment has been minimized.
andysalerno
commented
Dec 13, 2018
|
Will I ever be able to write:
?? |
This comment has been minimized.
This comment has been minimized.
|
Probably not. But there are ongoing plans to preparing everything so we might get trait Foo {
type Assoc: Bar;
fn get_a_bar() -> Assoc;
}
impl Foo for SomeType {
fn get_a_bar() -> impl Bar {
SomeThingImplingBar
}
}You can experiment with this feature on nightly in the form of impl Foo for SomeType {
existential type Assoc;
fn get_a_bar() -> Assoc {
SomeThingImplingBar
}
}A good start to get more info about this is rust-lang/rfcs#2071 (and everything linked from it) |
This comment has been minimized.
This comment has been minimized.
|
@oli-obk on |
This comment has been minimized.
This comment has been minimized.
|
@jonhoo being able to specify the traits is useful because you can provide more than just the required traits impl Foo for SomeDebuggableType {
existential type Assoc: Bar + Debug;
fn get_a_bar() -> Assoc {
SomeThingImplingBarAndDebug
}
}
fn use_debuggable_foo<F>(f: F) where F: Foo, F::Assoc: Debug {
println!("bar is: {:?}", f.get_a_bar())
}The required traits could be implicitly added to an existential associated type, so you only need bounds there when extending them, but personally I'd prefer the local documentation of having to put them in the implementation. |
This comment has been minimized.
This comment has been minimized.
|
@Nemo157 Ah, sorry, what I meant is that currently you must have bounds there. That is, this won't compile: impl A for B {
existential type Assoc;
// ...
}whereas this will: impl A for B {
existential type Assoc: Debug;
// ...
} |
This comment has been minimized.
This comment has been minimized.
|
Oh, so even in the case where a trait requires no bounds of the associated type, you must still give a bound to the existential type (which may be empty) (playground): trait Foo {
type Assoc;
fn foo() -> Self::Assoc;
}
struct Bar;
impl Foo for Bar {
existential type Assoc: ;
fn foo() -> Self::Assoc { Bar }
}This seems like an edge-case to me, having a bound-less existential type means it provides no operations to users (other than auto-traits), so what could it be used for? Also of note is there is no way to do the same thing with |
This comment has been minimized.
This comment has been minimized.
|
@Nemo157 yeah, I only realized because I tried literally the code you suggested above, and it didn't work :p I sort of assumed that it would infer the bounds from the trait. For example: trait Foo {
type Assoc: Future<Output = u32>;
}
struct Bar;
impl Foo for Bar {
existential type Assoc;
}It seemed reasonable to not have to specify |
This comment has been minimized.
This comment has been minimized.
Couldn't these be used for consumption in the same trait implementation? Something like this: trait Foo {
type Assoc;
fn create_constructor() -> Self::Assoc;
fn consume(marker: Self::Assoc) -> Self;
fn consume_box(marker: Self::Assoc) -> Box<Foo>;
}It's a bit contrived, but it could be useful - I could imagine a situation where some preliminary part needs to be constructed before the real struct for lifetime reasons. Or it could be something like: trait MarkupSystem {
type Cache;
fn create_cache() -> Cache;
fn translate(cache: &mut Self::Cache, input: &str) -> String;
}In both these cases |
jonhoo
referenced this issue
Dec 14, 2018
Open
`existential type` can accept generic bounds that do not specify a trait #53090
This comment has been minimized.
This comment has been minimized.
acycliczebra
commented
Dec 25, 2018
|
What is the proper way of defining associated types for impl Trait? For example, if I have an
Is something this currently not possible? |
This comment has been minimized.
This comment has been minimized.
|
@acycliczebra I think the syntax for that is |
aturon commentedJun 27, 2016
•
edited by alexreg
Implementation status
The basic feature as specified in RFC 1522 is implemented, however there have been revisions that are still in need of work:
let x: impl Traitstaticandconst T: impl Traitabstract typeRFCs
There have been a number of RFCs regarding impl trait, all of which are tracked by this central tracking issue.
abstract typein modules and implslet,const, andstaticpositionsimpl Traitanddyn Traitwith multiple boundsUnresolved questions
The implementation has raised a number of interesting questions as well:
implkeyword when parsing types? Discussion: 1Sendforwhere F: Fn() -> impl Foo + Send?impl Traitafter->infntypes or parentheses sugar? #45994fn foo<T>(x: impl Iterator<Item = T>>)?impl Traitas arguments in the list, permitting migrationexistential type Foo: Barortype Foo = impl Bar? (see here for discussion)existential typein an impl be just items of the impl, or include nested items within the impl functions etc? (see here for example)