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

FnPinMut: fully generalized stackless semicoroutines #1

Closed
wants to merge 2 commits into from
Closed
Changes from 1 commit
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
83 changes: 64 additions & 19 deletions text/0000-fn-pin-put.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ Generators (and as such, `async`) are built on top of `FnPinMut`.
This replaces the eRFC #2033 as the implementation of the functionality first proposed therein.

```rust
trait FnPinMut<Args> {
type Output;
trait FnPinMut<Args>: FnOnce<Args> {
// type Output; // from FnOnce
extern "rust-call" fn call_pin(self: Pin<&mut Self>, args: Args) -> Self::Output;
}
```
Expand All @@ -36,9 +36,6 @@ pub trait Future {

Specifically, `Future` is isomorphic to `FnPinMut(&mut Context) -> Poll<Output>`.

`FnPinMut` can additionally be further used for other self-referential use cases,
which include but are not fundamentally limited to:

The primary additional use case that justifies adding `FnPinMut` in addition to
`Future` is `Generator`s, which are experimentally accepted with eRFC #2033.
As of writing, `Generator` is defined in nightly as:
Expand All @@ -54,9 +51,14 @@ trait Generator {
and there are ongoing proposals to add optional arguments to `resume`,
which this RFC provides a potential avenue for.

`FnPinMut` is not fundamentally limited to producing `Generator`,
and can be used for other potentially self-referential use cases.

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

(see reference-level; I've written a reference-level including guide-level like bits)

Explain the proposal as if it was already included in the language and you were teaching it to another Rust programmer. That generally means:

- Introducing new named concepts.
Expand All @@ -70,13 +72,56 @@ For implementation-oriented RFCs (e.g. for compiler internals), this section sho
# Reference-level explanation
[reference-level-explanation]: #reference-level-explanation

This is the technical portion of the RFC. Explain the design in sufficient detail that:
We add a new trait, `FnPinMut`, with the signature above, to `std::ops`.
It is subject to the same restrictions as the stable `Fn` traits:
it cannot be named directly (use the `FnPinMut()` form),
and it cannot be implemented by hand.
(These restrictions may be lifted in the future.)

An implementation of `FnPinMut` is provided for all closures where said implementation is sound.
Practically, this means all currently stable `FnMut` closures, as they are `Unpin`, get the following implementation:
Copy link

Choose a reason for hiding this comment

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

A closure can be !Unpin by capturing a !Unpin value:

use std::marker::{Unpin, PhantomPinned};
fn main() {
    struct Pinned(PhantomPinned);
    impl Pinned { fn method(&mut self) {} }
    let mut pinned = Pinned(PhantomPinned);
    let closure = move || pinned.method();
    fn is_unpin(_: impl Unpin) {}
    is_unpin(closure);  // ERROR: the trait bound `std::marker::PhantomPinned: std::marker::Unpin` is not satisfied 
}


```rust
impl FnPinMut<Args> for $GeneratedClosureType {
extern "rust-call" fn call_pin(self: Pin<&mut Self>, args: Args) -> Self::Output {
(&mut *self).call_mut(args)
}
}
```

Closures are now allowed to contain **yield expression**s of the form `yield $expr`.
If a closure contains a yield expression, it _may not_ also contain a return expression.

Choose a reason for hiding this comment

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

My main concern with disallowing return is that try blocks would then be required to have any kind of reasonable ? story. I would mention in the alternatives section that return could be supported through a desugar like return $x:expr => yield $x; unreachable!() without fundamentally changing the behavior of coroutines.

This includes the trailing return: a closure containing a `yield` must "return" `!`.
This will most often be accomplished by a `panic!` or a `loop` at the tail end.
In most ways (allowed positions, return type), a yield statement acts the same as a return expression.

- Its interaction with other features is clear.
- It is reasonably clear how the feature would be implemented.
- Corner cases are dissected by example.
We call a closure that uses `yield` instead of `return` a **yield closure**.

The section should return to the examples given in the previous section, and explain more fully how the detailed proposal makes those examples work.
The key difference is that rather than execution restarting at the top of
the closure on the next call, execution resumes after the yield statement.
The compiler will generate a state machine to jump to the correct place upon each resumation.

This is subject to the normal rules about what `Fn` traits are implemented!
This means that a yield closure can implment all four traits: `Fn`, `FnMut`, `FnPinMut`, and `FnOnce`!
For example, [`std::iter`'s `Counter` example](counter) can be reimplemented as simply

```rust
std::iter::from_fn(|| {
for count in 1..6 {
yield Some(count);
}
loop {
yield None;
}
})
```

This closure will implement all four `Fn` traits.
Copy link

Choose a reason for hiding this comment

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

Is it correct that this closure can implement Fn?
A call modifies the state by iterating 1..6. It needs a mutable reference.

Choose a reason for hiding this comment

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

The only type of yield closure that can implement Fn is a yield closure with only 1 state. For example, the yield closure |x| loop { yield x + 1; } could be Fn because it is exactly identical to |x| x + 1.


So far, however, everything we've seen has been within the domain of the stable `Fn` traits.
`FnPinMut` is required to support borrowing over `yield` points.

(TODO)

# Drawbacks
[drawbacks]: #drawbacks
Expand Down Expand Up @@ -112,15 +157,13 @@ Please also take into consideration that rust sometimes intentionally diverges f

- Exact naming of `trait FnPinMut`. `FnPinMut` was chosen for this RFC as an

Choose a reason for hiding this comment

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

FnPin could also be an option since (as y'all pointed out) Pin<&mut Self is the only obviously useful way to take self as pinned.

obvious but somewhat flawed name. `Coroutine` or `Semicoroutine` would also
be a good fit for this concept. `Generator` should be reserved for the
`GeneratorState`-returning trait for the purpose of semantics, just as
`Iterator` is distinct from `FnMut() -> Item`.
- The exact signiture of `trait Generator`. This RFC suggests potential
signatures, but is focused on the `FnPinMut` foundation layer instead.

- What parts of the design do you expect to resolve through the RFC process before this gets merged?
- What parts of the design do you expect to resolve through the implementation of this feature before stabilization?
- What related issues do you consider out of scope for this RFC that could be addressed in the future independently of the solution that comes out of this RFC?
be a good fit for this concept.
- The exact fate of `trait Generator`. This RFC suggests focuses on the
`FnPinMut` foundation layer instead. `trait Generator` is still a usefull
abstraction boundary, just like `Iterator` is distinct from `FnMut() -> Item`.
- Lifetimes, lifetimes, lifetimes!
- Passing in single-yield lifetimes!
- Yielding references to closure state!

# Future possibilities
[future-possibilities]: #future-possibilities
Expand All @@ -142,3 +185,5 @@ Note that having something written down in the future-possibilities section
is not a reason to accept the current or a future RFC; such notes should be
in the section on motivation or rationale in this or subsequent RFCs.
The section merely provides additional information.

[counter]: <https://doc.rust-lang.org/1.39.0/std/iter/index.html#implementing-iterator>