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

Add Future::map #111347

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
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
24 changes: 10 additions & 14 deletions compiler/rustc_ty_utils/src/instance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -195,21 +195,17 @@ fn resolve_associated_item<'tcx>(
})
}
traits::ImplSource::Future(future_data) => {
if cfg!(debug_assertions) && tcx.item_name(trait_item_id) != sym::poll {
// For compiler developers who'd like to add new items to `Future`,
// you either need to generate a shim body, or perhaps return
// `InstanceDef::Item` pointing to a trait default method body if
// it is given a default implementation by the trait.
span_bug!(
tcx.def_span(future_data.generator_def_id),
"no definition for `{trait_ref}::{}` for built-in async generator type",
tcx.item_name(trait_item_id)
)
if Some(trait_item_id) == tcx.lang_items().future_poll_fn() {
// `Future::poll` is generated by the compiler.
Some(Instance {
def: ty::InstanceDef::Item(future_data.generator_def_id),
substs: future_data.substs,
})
} else {
// All other methods are default methods of the `Future` trait.
// (this assumes that `ImplSource::Future` is only used for methods on `Future`)
Some(Instance::new(trait_item_id, rcvr_substs))
}
Some(Instance {
def: ty::InstanceDef::Item(future_data.generator_def_id),
substs: future_data.substs,
})
}
traits::ImplSource::Closure(closure_data) => {
if cfg!(debug_assertions)
Expand Down
34 changes: 34 additions & 0 deletions library/core/src/future/future.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#![stable(feature = "futures_api", since = "1.36.0")]

#[cfg(not(bootstrap))]
use crate::future::Map;
use crate::marker::Unpin;
use crate::ops;
use crate::pin::Pin;
Expand Down Expand Up @@ -103,6 +105,38 @@ pub trait Future {
#[lang = "poll"]
#[stable(feature = "futures_api", since = "1.36.0")]
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output>;

/// Map this future's output to a different type, returning a new future of
/// the resulting type.
///
/// This function is similar to [`Option::map`] or [`Iterator::map`] where
/// it will change the type of the underlying future. This is useful to
/// chain along a computation once a future has been resolved.
///
/// Note that this function consumes the receiving future and returns a
/// wrapped version of it, similar to the existing `map` methods in the
joshtriplett marked this conversation as resolved.
Show resolved Hide resolved
/// standard library.
///
/// # Examples
///
/// ```
/// #![feature(future_map)]
/// use core::future::Future;
/// # async fn f() {
/// let future = async { 1 };
/// let new_future = future.map(|x| x + 3);
/// assert_eq!(new_future.await, 4);
/// # }
/// ```
#[cfg(not(bootstrap))]
#[unstable(feature = "future_map", issue = "none")]
fn map<U, F>(self, f: F) -> Map<Self, F>
where
F: FnOnce(Self::Output) -> U,
Self: Sized,
{
Map::new(self, f)
}
}

#[stable(feature = "futures_api", since = "1.36.0")]
Expand Down
57 changes: 57 additions & 0 deletions library/core/src/future/map.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#![allow(unused)]

use crate::future::Future;
use crate::pin::Pin;
use crate::task::{Context, Poll};
use crate::{fmt, mem, ptr};

/// A [`Future`] that maps the output of a wrapped [`Future`].
///
/// Returned by [`Future::map`].
#[unstable(feature = "future_map", issue = "none")]
pub struct Map<Fut, F> {
inner: Option<(Fut, F)>,
}

impl<Fut, F> Map<Fut, F> {
pub(crate) fn new(future: Fut, f: F) -> Self {
Self { inner: Some((future, f)) }
}
}

#[unstable(feature = "future_map", issue = "none")]
impl<Fut: fmt::Debug, F> fmt::Debug for Map<Fut, F> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Map").field("future", &self.inner.as_ref().map(|(fut, _)| fut)).finish()
}
}

#[unstable(feature = "future_map", issue = "none")]
impl<Fut: Future, F: FnOnce(Fut::Output) -> U, U> Future for Map<Fut, F> {
type Output = U;

fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
// SAFETY: we make sure to not move the inner future
unsafe {
let this = Pin::into_inner_unchecked(self);
match &mut this.inner {
Some((future, _)) => {
let pin = Pin::new_unchecked(&mut *future);
match pin.poll(cx) {
Poll::Ready(value) => {
// The future must be dropped in-place since it is pinned.
ptr::drop_in_place(future);
Copy link
Member

Choose a reason for hiding this comment

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

If the future panics here, self.inner will not be set to None and will cause use-after-free on the second poll. (similar case to async-rs/async-std#903)

Polling the future after panic is a bit of an odd case, but since poll is a safe function, it must not cause unsoundness in such a case.

The efficient, correct, and safe pattern here is using pin-project{,-lite}'s project_replace as done in futures.

If adding dependencies to core is difficult, I personally recommend vendoring pin-project-lite and doing the same thing as futures. (see also previous discussion)


let (future, map) = this.inner.take().unwrap_unchecked();
mem::forget(future);

Poll::Ready(map(value))
}
Poll::Pending => Poll::Pending,
}
}
None => panic!("Map must not be polled after it returned `Poll::Ready`"),
}
}
}
}
6 changes: 6 additions & 0 deletions library/core/src/future/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ use crate::task::Context;
mod future;
mod into_future;
mod join;
#[cfg(not(bootstrap))]
mod map;
mod pending;
mod poll_fn;
mod ready;
Expand All @@ -36,6 +38,10 @@ pub use ready::{ready, Ready};
#[stable(feature = "future_poll_fn", since = "1.64.0")]
pub use poll_fn::{poll_fn, PollFn};

#[cfg(not(bootstrap))]
#[unstable(feature = "future_map", issue = "none")]
pub use map::Map;

/// This type is needed because:
///
/// a) Generators cannot implement `for<'a, 'b> Generator<&'a mut Context<'b>>`, so we need to pass
Expand Down
12 changes: 10 additions & 2 deletions library/core/tests/future.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ mod test_join_function_like_value_arg_semantics {
}
}

fn block_on(fut: impl Future) {
fn block_on<F: Future>(fut: F) -> F::Output {
struct Waker;
impl Wake for Waker {
fn wake(self: Arc<Self>) {
Expand All @@ -113,7 +113,7 @@ fn block_on(fut: impl Future) {

loop {
match fut.as_mut().poll(&mut cx) {
Poll::Ready(_) => break,
Poll::Ready(value) => break value,
Poll::Pending => thread::park(),
}
}
Expand All @@ -126,3 +126,11 @@ fn _pending_impl_all_auto_traits<T>() {

all_auto_traits::<std::future::Pending<T>>();
}

#[cfg(not(bootstrap))]
#[test]
fn test_map() {
let future = async { 1 };
let future = future.map(|x| x + 3);
assert_eq!(block_on(future), 4);
}
1 change: 1 addition & 0 deletions library/core/tests/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
#![feature(fmt_internals)]
#![feature(float_minimum_maximum)]
#![feature(future_join)]
#![cfg_attr(not(bootstrap), feature(future_map))]
#![feature(generic_assert_internals)]
#![feature(array_try_from_fn)]
#![feature(hasher_prefixfree_extras)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ error: lifetime may not live long enough
LL | move |()| s.chars().map(|c| format!("{}{}", c, s))
| --------- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ returning this value requires that `'1` must outlive `'2`
| | |
| | return type of closure `Map<Chars<'_>, [closure@$DIR/issue-95079-missing-move-in-nested-closure.rs:11:29: 11:32]>` contains a lifetime `'2`
| | return type of closure `std::iter::Map<Chars<'_>, [closure@$DIR/issue-95079-missing-move-in-nested-closure.rs:11:29: 11:32]>` contains a lifetime `'2`
| lifetime `'1` represents this closure's body
|
= note: closure implements `Fn`, so references to captured variables can't escape the closure
Expand Down
4 changes: 2 additions & 2 deletions tests/ui/impl-trait/static-return-lifetime-infered.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ error[E0700]: hidden type for `impl Iterator<Item = u32>` captures lifetime that
LL | fn iter_values_anon(&self) -> impl Iterator<Item=u32> {
| ----- ----------------------- opaque type defined here
| |
| hidden type `Map<std::slice::Iter<'_, (u32, u32)>, [closure@$DIR/static-return-lifetime-infered.rs:7:27: 7:30]>` captures the anonymous lifetime defined here
| hidden type `std::iter::Map<std::slice::Iter<'_, (u32, u32)>, [closure@$DIR/static-return-lifetime-infered.rs:7:27: 7:30]>` captures the anonymous lifetime defined here
LL | self.x.iter().map(|a| a.0)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
Expand All @@ -19,7 +19,7 @@ error[E0700]: hidden type for `impl Iterator<Item = u32>` captures lifetime that
LL | fn iter_values<'a>(&'a self) -> impl Iterator<Item=u32> {
| -- ----------------------- opaque type defined here
| |
| hidden type `Map<std::slice::Iter<'a, (u32, u32)>, [closure@$DIR/static-return-lifetime-infered.rs:11:27: 11:30]>` captures the lifetime `'a` as defined here
| hidden type `std::iter::Map<std::slice::Iter<'a, (u32, u32)>, [closure@$DIR/static-return-lifetime-infered.rs:11:27: 11:30]>` captures the lifetime `'a` as defined here
LL | self.x.iter().map(|a| a.0)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
Expand Down
2 changes: 1 addition & 1 deletion tests/ui/lint/issue-106991.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ error[E0271]: expected `foo` to be a fn item that returns `i32`, but it returns
LL | fn bar() -> impl Iterator<Item = i32> {
| ^^^^^^^^^^^^^^^^^^^^^^^^^ expected `()`, found `i32`
|
= note: required for `Map<std::slice::IterMut<'_, Vec<u8>>, for<'a> fn(&'a mut Vec<u8>) {foo}>` to implement `Iterator`
= note: required for `std::iter::Map<std::slice::IterMut<'_, Vec<u8>>, for<'a> fn(&'a mut Vec<u8>) {foo}>` to implement `Iterator`

error: aborting due to previous error

Expand Down
2 changes: 1 addition & 1 deletion tests/ui/methods/method-missing-call.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ help: use parentheses to call the method
LL | .get_x();
| ++

error[E0615]: attempted to take value of method `filter_map` on type `Filter<Map<std::slice::Iter<'_, {integer}>, [closure@$DIR/method-missing-call.rs:27:20: 27:23]>, [closure@$DIR/method-missing-call.rs:28:23: 28:28]>`
error[E0615]: attempted to take value of method `filter_map` on type `Filter<std::iter::Map<std::slice::Iter<'_, {integer}>, [closure@$DIR/method-missing-call.rs:27:20: 27:23]>, [closure@$DIR/method-missing-call.rs:28:23: 28:28]>`
--> $DIR/method-missing-call.rs:29:16
|
LL | .filter_map;
Expand Down
2 changes: 1 addition & 1 deletion tests/ui/methods/method-not-found-generic-arg-elision.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ fn main() {
//~^ ERROR no method named `other` found for struct `Point
let v = vec![1, 2, 3];
v.iter().map(Box::new(|x| x * x) as Box<dyn Fn(&i32) -> i32>).extend(std::iter::once(100));
//~^ ERROR no method named `extend` found for struct `Map
//~^ ERROR no method named `extend` found for struct `std::iter::Map
let wrapper = Wrapper(true);
wrapper.method();
//~^ ERROR no method named `method` found for struct `Wrapper<bool>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ LL | struct Point<T> {
LL | let d = point_i32.other();
| ^^^^^ method not found in `Point<i32>`

error[E0599]: no method named `extend` found for struct `Map` in the current scope
error[E0599]: no method named `extend` found for struct `std::iter::Map` in the current scope
--> $DIR/method-not-found-generic-arg-elision.rs:87:67
|
LL | v.iter().map(Box::new(|x| x * x) as Box<dyn Fn(&i32) -> i32>).extend(std::iter::once(100));
Expand Down
8 changes: 4 additions & 4 deletions tests/ui/mismatched_types/closure-arg-count.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ LL | let _it = vec![1, 2, 3].into_iter().enumerate().map(foo);
LL | fn foo() {}
| -------- takes 0 arguments
|
note: required by a bound in `map`
note: required by a bound in `std::iter::Iterator::map`
--> $SRC_DIR/core/src/iter/traits/iterator.rs:LL:COL

error[E0593]: closure is expected to take a single 2-tuple as argument, but it takes 3 distinct arguments
Expand All @@ -139,7 +139,7 @@ LL | let _it = vec![1, 2, 3].into_iter().enumerate().map(bar);
| |
| required by a bound introduced by this call
|
note: required by a bound in `map`
note: required by a bound in `std::iter::Iterator::map`
--> $SRC_DIR/core/src/iter/traits/iterator.rs:LL:COL

error[E0593]: function is expected to take a single 2-tuple as argument, but it takes 2 distinct arguments
Expand All @@ -153,7 +153,7 @@ LL | let _it = vec![1, 2, 3].into_iter().enumerate().map(qux);
LL | fn qux(x: usize, y: usize) {}
| -------------------------- takes 2 distinct arguments
|
note: required by a bound in `map`
note: required by a bound in `std::iter::Iterator::map`
--> $SRC_DIR/core/src/iter/traits/iterator.rs:LL:COL

error[E0593]: function is expected to take 1 argument, but it takes 2 arguments
Expand All @@ -164,7 +164,7 @@ LL | let _it = vec![1, 2, 3].into_iter().map(usize::checked_add);
| |
| required by a bound introduced by this call
|
note: required by a bound in `map`
note: required by a bound in `std::iter::Iterator::map`
--> $SRC_DIR/core/src/iter/traits/iterator.rs:LL:COL

error[E0593]: function is expected to take 0 arguments, but it takes 1 argument
Expand Down
6 changes: 3 additions & 3 deletions tests/ui/mismatched_types/closure-arg-type-mismatch.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ LL | a.iter().map(|_: (u32, u32)| 45);
|
= note: expected closure signature `fn(&(u32, u32)) -> _`
found closure signature `fn((u32, u32)) -> _`
note: required by a bound in `map`
note: required by a bound in `std::iter::Iterator::map`
--> $SRC_DIR/core/src/iter/traits/iterator.rs:LL:COL
help: consider borrowing the argument
|
Expand All @@ -25,7 +25,7 @@ LL | a.iter().map(|_: &(u16, u16)| 45);
|
= note: expected closure signature `fn(&(u32, u32)) -> _`
found closure signature `for<'a> fn(&'a (u16, u16)) -> _`
note: required by a bound in `map`
note: required by a bound in `std::iter::Iterator::map`
--> $SRC_DIR/core/src/iter/traits/iterator.rs:LL:COL

error[E0631]: type mismatch in closure arguments
Expand All @@ -38,7 +38,7 @@ LL | a.iter().map(|_: (u16, u16)| 45);
|
= note: expected closure signature `fn(&(u32, u32)) -> _`
found closure signature `fn((u16, u16)) -> _`
note: required by a bound in `map`
note: required by a bound in `std::iter::Iterator::map`
--> $SRC_DIR/core/src/iter/traits/iterator.rs:LL:COL

error: aborting due to 3 previous errors
Expand Down
2 changes: 1 addition & 1 deletion tests/ui/nll/issue-54556-stephaneyfx.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ LL | }
| -
| |
| `stmt` dropped here while still borrowed
| ... and the borrow might be used here, when that temporary is dropped and runs the destructor for type `Map<Rows<'_>, [closure@$DIR/issue-54556-stephaneyfx.rs:28:14: 28:19]>`
| ... and the borrow might be used here, when that temporary is dropped and runs the destructor for type `std::iter::Map<Rows<'_>, [closure@$DIR/issue-54556-stephaneyfx.rs:28:14: 28:19]>`
|
= note: the temporary is part of an expression at the end of a block;
consider forcing this temporary to be dropped sooner, before the block's local variables are dropped
Expand Down
4 changes: 2 additions & 2 deletions tests/ui/recursion/issue-83150.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ LL | func(&mut iter.map(|x| x + 1))
= help: a `loop` may express intention better if this is on purpose
= note: `#[warn(unconditional_recursion)]` on by default

error[E0275]: overflow evaluating the requirement `Map<&mut std::ops::Range<u8>, [closure@$DIR/issue-83150.rs:12:24: 12:27]>: Iterator`
error[E0275]: overflow evaluating the requirement `std::iter::Map<&mut std::ops::Range<u8>, [closure@$DIR/issue-83150.rs:12:24: 12:27]>: Iterator`
|
= help: consider increasing the recursion limit by adding a `#![recursion_limit = "256"]` attribute to your crate (`issue_83150`)
= note: required for `&mut Map<&mut std::ops::Range<u8>, [closure@$DIR/issue-83150.rs:12:24: 12:27]>` to implement `Iterator`
= note: required for `&mut std::iter::Map<&mut std::ops::Range<u8>, [closure@$DIR/issue-83150.rs:12:24: 12:27]>` to implement `Iterator`
= note: 65 redundant requirements hidden
= note: required for `&mut Map<&mut Map<&mut Map<&mut Map<&mut Map<&mut Map<&mut Map<..., ...>, ...>, ...>, ...>, ...>, ...>, ...>` to implement `Iterator`
= note: the full type name has been written to '$TEST_BUILD_DIR/recursion/issue-83150/issue-83150.long-type-hash.txt'
Expand Down
2 changes: 1 addition & 1 deletion tests/ui/typeck/return_type_containing_closure.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ LL | vec!['a'].iter().map(|c| c)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `()`, found `Map<Iter<'_, char>, ...>`
|
= note: expected unit type `()`
found struct `Map<std::slice::Iter<'_, char>, [closure@$DIR/return_type_containing_closure.rs:3:26: 3:29]>`
found struct `std::iter::Map<std::slice::Iter<'_, char>, [closure@$DIR/return_type_containing_closure.rs:3:26: 3:29]>`
help: consider using a semicolon here
|
LL | vec!['a'].iter().map(|c| c);
Expand Down