Skip to content

RFC: impl trait expressions #2604

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

Closed
wants to merge 3 commits into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
148 changes: 148 additions & 0 deletions text/0000-impl-trait-expressions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
- Feature Name: `impl_trait_expressions`
- Start Date: 2018-12-03
- RFC PR: (leave this empty)
- Rust Issue: (leave this empty)

# Summary
[summary]: #summary

Rust closures allow the programmer to create values of anonymous types which
implement the `Fn*` traits. This RFC proposes a generalisation of this feature
to other traits. The syntax looks like this:

```rust
fn main() {
let world = "world";
let says_hello_world = impl fmt::Display {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "hello {}", world)
}
};

println!("{}", says_hello_world);
}
```

# Motivation
[motivation]: #motivation

Sometimes we need to create a once-off value which implements some trait,
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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.


# Guide-level explanation
[guide-level-explanation]: #guide-level-explanation

`impl Trait` expressions allow the user to create a value of an anonymous type which
implements `Trait`. These expressions behave the same as closures - they
capture local variables, by either move or reference, and collect them into an
anonymous struct which implements the required trait.

To better understand the behaviour of `impl Trait`, consider the following code:

```rust
let y = String::from("hello");
let foo = move || println!("{}", y);
```

With this RFC, the above code becomes syntax sugar for:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
With this RFC, the above code becomes syntax sugar for:
With this RFC, the above code becomes syntactic sugar for:


```rust
let y = String::from("hello");
let foo = move impl FnOnce<()> {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should probably be written as impl FnOnce() (cc @nikomatsakis)

type Output = ();

extern "rust-call" fn call_once(self, args: ()) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aaaa I keep being reminded I need to fix this (it should ideally be just fn call_once(self) here).

println!("{}", y);
}
};
```

Which, in turn, is syntax sugar for:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Which, in turn, is syntax sugar for:
Which, in turn, is syntactic sugar for:


```rust
let y = String::from("hello");

struct MyCoolAnonType {
y: String,
}

impl FnOnce<()> for MyCoolAnonType {
type Output = ();

extern "rust-call" fn call_once(self, args: ()) {
println!("{}", self.y);
}
}

let foo = MyCoolAnonType { y };
```

# Reference-level explanation
[reference-level-explanation]: #reference-level-explanation

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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like at least the following to be discussed:

  1. What is the interaction with Closures Capture Disjoint Fields #2229?

  2. What changes are there to the grammar? and are there any ambiguities (there are, see below)

  3. What happens when I implement the Fn trait and it is a subtrait once removed of FnOnce? Do I also get the supertrait impls?

  4. How do I implement several traits at once? Can I do that? This ties into 3.

  5. What is the type of an anonymous impl? A voldemort type?

  6. Will Copy and Clone be implemented for the impl trait expression if possible like for closures?

  7. What happens if I write let obj = impl Foo<'_> { ... };? Normally this would quantify a lifetime 'a so we'd have impl<'a> Foo<'a> for X { ... }...

  8. What rules if any apply for turning let x = impl Foo { ... } into a trait object?


Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cc @petrochenkov @eddyb @qmx ^ ideas?

# Drawbacks
[drawbacks]: #drawbacks

Adds yet another feature to an already-rich language.

# Rationale and alternatives
[rationale-and-alternatives]: #rationale-and-alternatives

Not do this.

# Prior art
[prior-art]: #prior-art

Other than closures I'm not aware of any prior art.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some prior art to consider:

  • Java's anonymous classes

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Kotlin and C++ also have a similar feature to Java's.


# Unresolved questions
[unresolved-questions]: #unresolved-questions

None.

# Future possibilities
[future-possibilities]: #future-possibilities

It would be good if both closures and `impl Trait` expressions could implement
traits generically. For example we should be able to write:

```rust
let mut count = 0u32;
let print_numbered = move <T: Display> |val: T| {
println!("{}: {}", count, val);
count += 1;
};
```

Or, more verbosely:

```rust
let mut count = 0u32;
let print_numbered = impl<T: Display> FnOnce<(T,)> {
type Output = ();

extern "rust-call" fn call_once(self, (val,): (T,)) {
println!("{}: {}", count, val);
count += 1;
}
};
```

To define a value that can be called with any `Display` type:

```rust
print_numbered(123);
print_numbered("hello");

// prints:
// 0: 123
// 1: hello
```