Skip to content

ACP: Add future::ok and future::err #529

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
tisonkun opened this issue Jan 28, 2025 · 5 comments
Closed

ACP: Add future::ok and future::err #529

tisonkun opened this issue Jan 28, 2025 · 5 comments
Labels
api-change-proposal A proposal to add or alter unstable APIs in the standard libraries I-async-nominated T-libs-api

Comments

@tisonkun
Copy link

tisonkun commented Jan 28, 2025

Proposal

Problem statement

We already bring futures::future::ready to std. I find it frequent to use ok and err also. Perhaps we bring the same to the core lib.

Motivating examples or use cases

See as in the solution.

It is currently available in futures-util but it'll bring several extra parts. I may consider that the async wg would have a plan for the reasonable state of the final stable separation (cc @taiki-e @nrc @yoshuawuyts).

Solution sketch

/// Creates a future that is immediately ready with a success value.
///
/// Futures created through this function are functionally similar to those
/// created through `async {}`. The main difference is that futures created
/// through this function are named and implement `Unpin`.
///
/// # Examples
///
/// ```
/// use std::future;
///
/// # async fn run() {
/// let a = future::ok(1);
/// assert_eq!(a.await, Ok(1));
/// # }
/// ```
pub fn ok<T, E>(t: T) -> Ready<Result<T, E>> {
    Ready(Some(Ok(t)))
}

/// Creates a future that is immediately ready with an error value.
///
/// Futures created through this function are functionally similar to those
/// created through `async {}`. The main difference is that futures created
/// through this function are named and implement `Unpin`.
///
/// # Examples
///
/// ```
/// use std::future;
///
/// # async fn run() {
/// let a = future::err(42);
/// assert_eq!(a.await, Err(42));
/// # }
/// ```
pub fn err<T, E>(err: E) -> Ready<Result<T, E>> {
    Ready(Some(Err(err)))
}

Alternatives

N/A

Links and related work

Bringing from:

https://github.com/rust-lang/futures-rs/blob/048995af9e6f24de71ac7e009ab2e5f5ac290f48/futures-util/src/future/ready.rs#L52-L82

and expected into after:

https://github.com/rust-lang/rust/blob/aa6f5ab18e67cb815f73e0d53d217bc54b0da924/library/core/src/future/ready.rs#L66-L69

What happens now?

This issue contains an API change proposal (or ACP) and is part of the libs-api team feature lifecycle. Once this issue is filed, the libs-api team will review open proposals as capability becomes available. Current response times do not have a clear estimate, but may be up to several months.

Possible responses

The libs team may respond in various different ways. First, the team will consider the problem (this doesn't require any concrete solution or alternatives to have been proposed):

  • We think this problem seems worth solving, and the standard library might be the right place to solve it.
  • We think that this probably doesn't belong in the standard library.

Second, if there's a concrete solution:

  • We think this specific solution looks roughly right, approved, you or someone else should implement this. (Further review will still happen on the subsequent implementation PR.)
  • We're not sure this is the right solution, and the alternatives or other materials don't give us enough information to be sure about that. Here are some questions we have that aren't answered, or rough ideas about alternatives we'd want to see discussed.
@tisonkun tisonkun added api-change-proposal A proposal to add or alter unstable APIs in the standard libraries T-libs-api labels Jan 28, 2025
@tisonkun
Copy link
Author

These functions can be written as:

std::future::ready(Ok(t));
std::future::ready(Err(err));

The upside of ok and err can have:

  • It's well-known and used via futures-rs for quite several time;
  • It can save some letters;
  • It can be used as a bare function pointer in combinators.

@yoshuawuyts
Copy link
Member

yoshuawuyts commented Jan 28, 2025

@tisonkun Thank you for filing MCP this and tagging me on it. When I filed to add future::pending and future::ready, it was motivated by the fact that iter::once and iter::empty also exist. As well as the fact that future::ready creates a concrete future Ready<T>, where each async {} invocation creates a new unique type. This turns out to be particularly useful for both tests and examples.

Can you share a bit more about how you're thinking about this space? If we were to add Future::err and Future::ok, would you also expect us to add other Try-variants? For example, do you also expect us to add Future::some and Future::none? Can you explain your motivation?

The reason why I'm asking is because there are always more APIs that can be added. There tends to be a pretty high bar for an API to be included in the stdlib. It would be useful to better understand why you believe that these specific APIs are important enough to be included.

edit: I realized you wrote: "It can be used as a bare function pointer in combinators." I'm not quite sure what you meant by that. Could you elaborate?

@tisonkun
Copy link
Author

tisonkun commented Jan 29, 2025

If we were to add Future::err and Future::ok, would you also expect us to add other Try-variants? For example, do you also expect us to add Future::some and Future::none? Can you explain your motivation?

Thanks for pointing this out @yoshuawuyts and sharing the background of ready/pending. This seems a good point to reject this proposal.

"It can be used as a bare function pointer in combinators." Could you elaborate?

When filling a parameter accept fn(T) -> impl Future<Output=T>, one can use future::ok instead of wrapping a closure or local function. But that's not theoretically different, only saving some letters.

For example, I've written recently:

        let mut concurrent_tasks = futures::stream::iter(tasks)
            .buffer_by_unordered(total_uncompressed_bytes)
            .try_filter_map(futures::future::ok);

Can you share a bit more about how you're thinking about this space?

Yes. I'd share my current findings and would love to know where I can participate in helping the async WG.

We currently have core definitions Future/Pin/Poll in the core lib, as well as helper functions ready/pending/poll_fn, etc.

We have a semi-standard crate futures-rs provides extra definitions like Stream/Sink, IOs, and more utilities.

We have more third-party crates that provides runtime-agnostic primitives (mea, flume) that can be analogies to std::sync's primitives

I would expect we bring Stream into the core lib at some point. This is an essential concept the whole ecosystem depends on.

For combinators, we have iterator combinators and utilities in the core lib, but we leave more complex tools to the ecosystem (itertools, etc.). futures-rs ever hit the combination explosion, as in rust-lang/futures-rs#2755 (combine three monads: list, try, future). So I'm hesitate to redo the same in core lib (this ACP can be considered part of it).

However, currently, these utilities are located at futures-util. It includes quite a few other utilities except the combinators, making the crate a "default to others" one, which may be hard to depend on. (UPDATE: another issue is that futures-utils, as a third-patry crate, has to maintain a low MSRV, currently 1.63. And users have to poll in a dependency to pin-utils which is deprecated for the new libcore macro core::pin::pin.)

For primitives, I have no certain idea. I'd prefer to see them (async Mutex, Condvar, Semaphore, WaitGroup, etc.) in the core lib since they're runtime-agnostic, can be implemented in a determinate way, and we have sync primitives. But others could have different considerations.

For IOs and executors abstractions, they are more complex concepts to discuss, so I don't dive into them here.

To sum up, I'm looking for a way to help gradually stabilize the good part of async toolchains so that the ecosystem can converge into several base points rather than handling multiple similar abstractions and utilities and adapting their subtle differences.

@traviscross
Copy link

cc @rust-lang/wg-async

@traviscross
Copy link

Let's close this. We raised this over in the async stream, and nobody much argued against the position that this just doesn't carry its weight, and that if we did this, it would imply wanting some and none functions too, and that we wouldn't want those either.

@Amanieu Amanieu closed this as completed Feb 25, 2025
@cuviper cuviper closed this as not planned Won't fix, can't repro, duplicate, stale Feb 25, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
api-change-proposal A proposal to add or alter unstable APIs in the standard libraries I-async-nominated T-libs-api
Projects
None yet
Development

No branches or pull requests

5 participants