Skip to content

RFC: Include Future and IntoFuture in the 2024 prelude #3509

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

Merged
merged 3 commits into from
Feb 13, 2024
Merged
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
94 changes: 94 additions & 0 deletions 0000-prelude-2024-future.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
- Feature Name: `prelude_2024_future`
- Start Date: 2023-10-05
- RFC PR: [rust-lang/rfcs#0000](https://github.com/rust-lang/rfcs/pull/0000)
- Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000)

# Summary
[summary]: #summary

This RFC describes the inclusion of the `Future` and `IntoFuture` traits in the 2024 edition prelude.

# Motivation
[motivation]: #motivation

When an `async fn` is desugared we obtain an anonymous type with a signature of `impl Future`. In order to use this type we must first be able to _name_ it, which can only be done if the `Future` trait in scope. Currently this can be a little inconvenient since it requires `std::future::Future` to be manually imported.

Most other reifications of control-flow effects have their respective types and traits included in the prelude header. To support iteration we include both the `Iterator` and `IntoIterator` traits in the prelude. To support fallibility we include both `Option` and `Result` in the prelude. This RFC proposes we include both `Future` and `IntoFuture` to match.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
Most other reifications of control-flow effects have their respective types and traits included in the prelude header. To support iteration we include both the `Iterator` and `IntoIterator` traits in the prelude. To support fallibility we include both `Option` and `Result` in the prelude. This RFC proposes we include both `Future` and `IntoFuture` to match.
`IntoFuture` comes up regularly when writing operations that accept a `Future`. Adding `IntoFuture` makes it easy to call `.into_future()` to bridge between code accepting only `Future` and code supplying an `IntoFuture` impl, as well as making it easy to write new code that accepts any `IntoFuture` impl.
Both of these traits are generally useful, come up regularly, don't conflict with anything, and will not produce any surprising behavior if added to the prelude.
Most other reifications of control-flow effects have their respective types and traits included in the prelude header. To support iteration we include both the `Iterator` and `IntoIterator` traits in the prelude. To support fallibility we include both `Option` and `Result` in the prelude. This RFC proposes we include both `Future` and `IntoFuture` to match.


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

Let's say someone wrote an async function which takes a future and operates on it. For example, it could be something like this:
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
Let's say someone wrote an async function which takes a future and operates on it. For example, it could be something like this:
In Rust 2024, you can name and make use of the `Future` and `IntoFuture` traits without explicitly importing them, since they appear in the Rust 2024 prelude. For instance, you can write a function that accepts or returns an `impl Future`, or one that accepts any `impl IntoFuture`.
Let's say someone wrote an async function which takes a future and operates on it. For example, it could be something like this:


```rust
use std::future::IntoFuture;

async fn meow(phrase: impl IntoFuture<Output = String>) {
println!("{}, meow", phrase.await);
}
```

In the Rust 2021 edition this code would require an explicit import of the `IntoFuture` trait. When migrating to the 2024 edition the import of the `IntoFuture` trait would be taken care of by the prelude, and the code could remove the explicit import:

Choose a reason for hiding this comment

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

This doesn't feel like the strongest example. Generally I find that traits are most useful in preludes when you want to implicitly use their methods. Are there applications like that?

Copy link

Choose a reason for hiding this comment

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

is this more similar to having IntoIterator in the prelude?

Choose a reason for hiding this comment

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

Yeah, exactly. Being able to always call .into_iter() is really useful. Being able to return a impl IntoIterator or create a Box<dyn IntoIterator> is moderately useful, but nowhere near as valuable, both in terms of frequency and beginner-accessiblity.

Choose a reason for hiding this comment

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

Looks like poll is the only method. Hmm, not actually sure which one is more useful to show off. I guess you could do both.

Copy link

Choose a reason for hiding this comment

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

@alice-i-cecile that sounds like an argument for #1880 (inherent trait impls) instead of adding more symbols to the prelude.


```rust
async fn meow(phrase: impl IntoFuture<Output = String>) {
println!("{}, meow", phrase.await);
}
```

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

This RFC proposes we include both `Future` and `IntoFuture` as part of the 2024 prelude. The prelude in `std` re-exports the prelude in `core`. So the only change we need to make is in the prelude in `core`, changing it to both include `Future` and `IntoFuture`:

```rust
// core::prelude::rust_2024
mod rust_2024 {
pub use crate::future::Future;
pub use crate::future::IntoFuture;
pub use super::rust_2021::*;
}
```

# Tradeoffs
[tradeoffs]: #tradeoffs

Both the `Future` and `IntoFuture` definitions in the standard library are considered _canonical_: there exist no widespread alternative definitions in the Rust ecosystem. Simply having both traits in scope is unlikely to lead to any issues, and the only likely noticable outcome is that authoring async code will require slightly less effort.

# Rationale and alternatives
Copy link
Contributor

Choose a reason for hiding this comment

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

I think it'd be good to add a "Do nothing" section as an alternative. This is kind of addressed elsewhere in the RFC (limited expected breakage and async code is slightly harder to write), but having it called out into its own section would be helpful.

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

## Include `Future` in 2024, re-consider `IntoFuture` in 2027

This RFC takes what could be called a: _"systems-based perspective"_. We're arguing that the core property which qualifies the `Future` and `IntoFuture` traits for inclusion in the prelude is their fundamental relationship to the language. Similar to how `Iterator` and `Result` correspond to core control-flow effects, `Future` and `IntoFuture` do too.
Copy link
Contributor

Choose a reason for hiding this comment

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

Does the systems-based justification work for other things that are in the prelude currently? In other words, have we used this justification before, even if we may not have had a name for it?


However, it's possible to use different criteria for what should be included in the prelude. One example is what could be referred to as a: _"merit-based perspective"_. From this perspective, items would only qualify for inclusion once they've crossed some usage threshold. From that perspective the `Future` trait would likely qualify since it's in wide use. But `IntoFuture` likely would not since it's a newer trait which sees less use.

Both of these perspectives can be applied to other scenarios too. Let's say we're finally able to stabilize the `Try` trait; should this be included in the following prelude? From a systems-based perspective, the answer would be "yes", since it's a fundamental operation which enables types to be named. From the merit-based perspective the answer would likely be "no", since it will be a new trait with limited usage. But it might be re-considered once it sees enough usage.

We believe that taking a merit-based perspective makes sense if the upsides of a choice also carry noteable downsides. But as covered in the "tradeoffs" section of this RFC, there don't appear to be any meaningful downsides. So instead it seems better to base our evalutation on how the traits relate to the language, rather than on how much usage they see.

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

## New inclusions in the Rust 2021 edition

The Rust 2021 edition includes three new traits:

- `FromIterator` - conversion from an iterator to a type
- `TryFrom` - fallible conversion
- `TryInto` - fallible conversion (inverted)

All three of these traits represent fundamental operations present in Rust. This is a natural supplement to other fundamental operations present in earlier editions such as `Try`, `Into`, and `IntoIterator`. I'd argue that `Future` and `IntoFuture` have an an equal, if not more fundamental relationship to the Rust language than `TryFrom` or `FromIterator` do.

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

None at this time.

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

## Inclusion of `Try` in a future prelude

As in the "alternatives and rationale" section of this RFC, if we apply the same reasoning we're using in this RFC to the `Try` trait. Then once stabilized the `Try` trait's fundamental relationship to the language would qualify it for inclusion in an future edition's prelude as well.