Join GitHub today
GitHub is home to over 31 million developers working together to host and review code, manage projects, and build software together.
Sign upRFC: impl trait expressions #2604
Conversation
Centril
added
T-lang
A-syntax
A-traits
A-typesystem
A-impl-trait
A-expressions
labels
Dec 3, 2018
Centril
reviewed
Dec 3, 2018
text/0000-impl-trait-expressions.md Outdated
text/0000-impl-trait-expressions.md Outdated
| though having to explicitly declare a type in these situations can be | ||
| unnecessarily painful and noisy. Closures are a good example of how | ||
| this problem can be ameliorated by adding the ability to declare once-off | ||
| values of anonymous types. |
This comment has been minimized.
This comment has been minimized.
Centril
Dec 3, 2018
Contributor
The motivation feels a bit thin; it would be good to work in some real world use cases of where it would be beneficial; I'm sure you won't have any problems doing that.
| let foo = move || println!("{}", y); | ||
| ``` | ||
|
|
||
| With this RFC, the above code becomes syntax sugar for: |
This comment has been minimized.
This comment has been minimized.
Centril
Dec 3, 2018
Contributor
I don't think calling it sugar does it justice; in particular, if you add type parameters to a closure, there's type inference going on to infer what the type of Self::Output is as well as what Args is.
| }; | ||
| ``` | ||
|
|
||
| Which, in turn, is syntax sugar for: |
This comment has been minimized.
This comment has been minimized.
Centril
Dec 3, 2018
Contributor
| Which, in turn, is syntax sugar for: | |
| Which, in turn, is syntactic sugar for: |
| let foo = move || println!("{}", y); | ||
| ``` | ||
|
|
||
| With this RFC, the above code becomes syntax sugar for: |
This comment has been minimized.
This comment has been minimized.
Centril
Dec 3, 2018
Contributor
| With this RFC, the above code becomes syntax sugar for: | |
| With this RFC, the above code becomes syntactic sugar for: |
|
|
||
| This feature is fully described in the guide-level explanation. As this is a | ||
| generalisation of the existing closure syntax I suspect that the implementation | ||
| would be fairly straight-forward. |
This comment has been minimized.
This comment has been minimized.
Centril
Dec 3, 2018
Contributor
I'd like at least the following to be discussed:
-
What is the interaction with #2229?
-
What changes are there to the grammar? and are there any ambiguities (there are, see below)
-
What happens when I implement the
Fntrait and it is a subtrait once removed ofFnOnce? Do I also get the supertrait impls? -
How do I implement several traits at once? Can I do that? This ties into 3.
-
What is the type of an anonymous impl? A voldemort type?
-
Will
CopyandClonebe implemented for the impl trait expression if possible like for closures? -
What happens if I write
let obj = impl Foo<'_> { ... };? Normally this would quantify a lifetime'aso we'd haveimpl<'a> Foo<'a> for X { ... }... -
What rules if any apply for turning
let x = impl Foo { ... }into a trait object?
| This feature is fully described in the guide-level explanation. As this is a | ||
| generalisation of the existing closure syntax I suspect that the implementation | ||
| would be fairly straight-forward. | ||
|
|
This comment has been minimized.
This comment has been minimized.
Centril
Dec 3, 2018
Contributor
As for the ambiguity aforementioned, consider:
fn foo() {
struct X;
impl X {
fn foo() {}
};
}This compiles today and impl X { ... } is an item followed by the empty statement ;.
According to your RFC however impl X { ... } is an expression which is made into a statement by the following ;.
This is not an insurmountable challenge but you'll need to think about how to deal with it.
This comment has been minimized.
This comment has been minimized.
| # Prior art | ||
| [prior-art]: #prior-art | ||
|
|
||
| Other than closures I'm not aware of any prior art. |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
burdges
commented
Dec 4, 2018
|
It's too bad this never came up before |
This comment has been minimized.
This comment has been minimized.
|
My version of this general idea (predating fn main() {
let world = "world";
let says_hello_world = struct {
world,
} impl fmt::Display {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "hello {}", self.world)
}
};
println!("{}", says_hello_world);
}The struct {
x,
y,
} impl Default {
fn default() -> Self {
Self { x: 1, y: -1 }
}
} impl Clone { // imagine `#[derive(Clone)]` generating this:
fn clone(&self) -> Self {
Self {
x: self.x.clone(),
y: self.y.clone(),
}
}
} |
This comment has been minimized.
This comment has been minimized.
|
I could be persuaded to be in favor of this, but my inclination is against it just because of Java. Anonymous classes in Java are huge source of noise and hard-to-understand code IMHO. In addition, I would like to see the following addressed in the RFC:
|
This comment has been minimized.
This comment has been minimized.
|
@mark-i-m I'd expect the answers to both of those questions to be identical to the answers for closures. |
ExpHP
reviewed
Dec 4, 2018
|
|
||
| ```rust | ||
| let y = String::from("hello"); | ||
| let foo = move impl FnOnce<()> { |
This comment has been minimized.
This comment has been minimized.
ExpHP
Dec 4, 2018
Mind that the type parameters of the Fn traits are not a stable part of Rust, so be careful not to imply that this should work
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
ExpHP
commented
Dec 4, 2018
|
Something like this which allows omitting the types of the closed over locals could be huge for macros that need to codegen implementors of a trait. Then again, the feature might also even eliminate the need for most such macros! |
eddyb
reviewed
Dec 4, 2018
| let foo = move impl FnOnce<()> { | ||
| type Output = (); | ||
| extern "rust-call" fn call_once(self, args: ()) { |
This comment has been minimized.
This comment has been minimized.
eddyb
Dec 4, 2018
Member
Aaaa I keep being reminded I need to fix this (it should ideally be just fn call_once(self) here).
This comment has been minimized.
This comment has been minimized.
|
Question: Does this work? const FOO: impl Foo = impl Foo { ... }; |
This comment has been minimized.
This comment has been minimized.
burdges
commented
Dec 4, 2018
|
I believe the syntax by @eddyb makes more sense so long as you must still write out I prefer anonymous types being explicit though, so maybe some syntax involving You could name the
In this, all captures take the form We could also improve the mechanisms for creating such types from procedural macros, so like some macro driven
There is also the |
Centril
and others
added some commits
Dec 5, 2018
This comment has been minimized.
This comment has been minimized.
jdahlstrom
commented
Dec 6, 2018
|
Yeah, this is basically exactly Java's anonymous inner classes. Somewhat amusing that for 20 years Java had closures but only using the verbose AIC syntax. Then they finally added a terse lambda syntax as a syntactic sugar (conceptually if not implementation-wise) . And now in Rust we first had lambdas and now entertain the thought of generalizing them :) |
This comment has been minimized.
This comment has been minimized.
mikeyhew
commented
Dec 6, 2018
|
It would be nice to see more examples where the trait is not one of the |
This comment has been minimized.
This comment has been minimized.
burdges
commented
Dec 7, 2018
|
Rust closures admit no polymorphism, not even lifetime polymorphism. I suggested It's less common but you might want to tie a closure return lifetime to an argument lifetime too. At that point, you want full lifetime polymorphism for closures. I think this proposal addresses roughly the same problems as polymorphism for closures. It gains some ergonomics in type descriptions via existing traits, but only with a dramatic ergonomics sacrifice for instantiation. |
This comment has been minimized.
This comment has been minimized.
fn foo() {
rank_2(|x: &u8| -> &u8 { x });
}
fn rank_2(x: impl for<'a> Fn(&'a u8) -> &'a u8) {} |
This comment has been minimized.
This comment has been minimized.
rodrimati1992
commented
Dec 7, 2018
•
|
|
This comment was marked as off-topic.
This comment was marked as off-topic.
burdges
commented
Dec 7, 2018
|
Oops thanks :) It's only the receiver lifetime that's unreachable then. |
This comment was marked as off-topic.
This comment was marked as off-topic.
Yes and that's from the traits, not the closures. I think with |
This comment was marked as off-topic.
This comment was marked as off-topic.
burdges
commented
Dec 7, 2018
•
|
How would
I'm still dubious this first Also, there are no plans to support ATCs as type arguments like this, right? Could
We'd still have the syntactic problem of naming this lifetime In the context of this RFC, if this worked then
|
This comment was marked as off-topic.
This comment was marked as off-topic.
|
You don't need to do anything for arguments, e.g.
|
This comment was marked as off-topic.
This comment was marked as off-topic.
burdges
commented
Dec 7, 2018
|
I'd think that, if you add only
because the |
This comment was marked as off-topic.
This comment was marked as off-topic.
|
You can shorten any lifetime parameters of the impl (e.g. |
This comment has been minimized.
This comment has been minimized.
|
This line of discussion seems increasingly off-topic... |
This comment has been minimized.
This comment has been minimized.
bill-myers
commented
Dec 8, 2018
|
I think this should support implementing multiple traits (even if they have items with the same signature), and also support deriving traits. |
This comment has been minimized.
This comment has been minimized.
rodrimati1992
commented
Dec 9, 2018
|
How does this handle implementing a trait with supertraits?For example implementing |
This comment has been minimized.
This comment has been minimized.
H2CO3
commented
Dec 12, 2018
|
I see very little value in this. The example in the RFC is trivial to rewrite as let world = "world";
let says_hello_world = {
struct SaysHello<'a>(&'a str);
impl<'a> fmt::Display for SaysHello<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "hello {}", self.0)
}
}
SaysHello(world)
};
println!("{}", says_hello_world);As others have already mentioned it, this is very similar to a lambda. Lambdas are great; however, the use-case of this one is so narrow and so specific that having to do the above transformation by hand once in a lifetime clearly doesn't justify the added language complexity. |
This comment has been minimized.
This comment has been minimized.
ExpHP
commented
Dec 14, 2018
•
|
This comment has been minimized.
This comment has been minimized.
H2CO3
commented
Dec 14, 2018
That's a moot point, you can use a tuple struct.
This is simply false. This would mean that Rust is simply a pain to write in, a viewpoint from which I beg to differ.
They are unstable. I would be highly in favor of stabilizing manual
It's narrow and specific because it replicates a small subset of functionality already offered by other parts of the language, but only for one concrete situation/style. I understand that there are situations that it might be useful. But "there are situations where it's useful" isn't at all sufficient justification for any feature to be added to the language.
In your example, this doesn't really require such |
This comment has been minimized.
This comment has been minimized.
burdges
commented
Dec 14, 2018
|
I suppose "narrow and specific" might be strongly phrased @ExpHP but the syntax mentioned by @H2CO3 is not much more complex than the syntax proposed here, and the syntax proposed here costs "strangeness budget". We're talking about declaring arbitrary traits here so one should not expect dramatic simplifications. In particular you'll never navigate the orphan rules without those where clauses. In other words, I'd consider this RFC "narrow and specific" because many traits will remain painful to implement this way, so crate authors must trade their preferred trait structure for one that "lambdas well". I'd think the most useful approach would be giving proc macros the information they require to capture like closures do, maybe eventually even doing a demo implementation of closures via such proc macros. There are likely a small number of traits, or trait combinations, that benefit enormously form this, so their authors could designed finely tuned constructions, rather than hack up their traits to make using this RFC's approach less painful. Also, I'd think almost everyone would love to pass more type information to proc macros, although some complexity exist of course. |
Centril
assigned
nikomatsakis
Jan 3, 2019
This comment has been minimized.
This comment has been minimized.
haslersn
commented
Jan 6, 2019
•
|
Why don't we just make closures castable to trait implementations if the trait has exactly one method? use std::fmt;
fn main() {
let world = "world";
let says_hello_world = | f: &mut fmt::Formatter | write!(f, "hello {}", world);
println!("{}", &says_hello_world as &fmt::Display);
} |
This comment has been minimized.
This comment has been minimized.
That would be a nice extension, but both approaches are valuable to have. For example, Java adopted this behavior (lambda definitions of "functional interfaces") in version 8, and has additionally always had support for the style of anonymous interface implementation described in this RFC. There are some issues with the lambda approach around specifying which trait should be implemented that this doesn't have. |
This comment has been minimized.
This comment has been minimized.
haslersn
commented
Jan 6, 2019
•
That would be specified by the cast. I added an example to my previous comment. It feels for me like:
Therefore, having closures castable to trait implementations (that have exactly one method) would be enaugh. |
This comment has been minimized.
This comment has been minimized.
graydon
commented
Jan 12, 2019
|
Opposed. This adds significant cognitive burden to readers and does not solve an important problem. The existing mechanism is a special case that should stay a special case, not be generalized. |
This comment has been minimized.
This comment has been minimized.
|
@rfcbot fcp postpone I personally am generally positive on this idea and I'd like to thank @canndrew for taking the time to write up and open it. Nonetheless, I am moving to postpone for the time being. Let me explain. First, why am I in favor of this idea? Well, because I've often found a desire for this feature. One example would be in Rayon, where we often have to make "one off structs" to implement the Why is the time not ripe? While the details of roadmap is still in play, I think it seems pretty clear that we are trending towards a "refine the language, finish up things in play" sort of year, and this seems like a clear expansion with insufficiently strong motivation. (Along those lines, there are some alternative, more limited approaches that we might also consider. For example, my Rayon use case above would be better served via generic closures, which seem like they have kind of zero extra "cognitive load". A more aggressive step might be to take the Java approach of permitting |
This comment has been minimized.
This comment has been minimized.
rfcbot
commented
Jan 15, 2019
•
|
Team member @nikomatsakis has proposed to postpone this. The next step is review by the rest of the tagged team members: No concerns currently listed. Once a majority of reviewers approve (and none object), this will enter its final comment period. If you spot a major issue that hasn't been raised at any point in this process, please speak up! See this document for info about what commands tagged team members can give me. |
rfcbot
added
proposed-final-comment-period
disposition-postpone
final-comment-period
and removed
proposed-final-comment-period
labels
Jan 15, 2019
This comment has been minimized.
This comment has been minimized.
rfcbot
commented
Jan 18, 2019
|
|
This comment has been minimized.
This comment has been minimized.
rfcbot
commented
Jan 28, 2019
|
The final comment period, with a disposition to postpone, as per the review above, is now complete. By the power vested in me by Rust, I hereby postpone this RFC. |
canndrew commentedDec 3, 2018
•
edited
Rendered view
This is an idea I've seen floating around for a while. I like it, so I decided to give it a proper RFC.
Summary: Add
impl Trait { ... }expressions as a kind-of generalization of closure syntax.