Rollup merge of #123660 - compiler-errors:coroutine-closure-env, r=oli-obk

Make the computation of `coroutine_captures_by_ref_ty` more sophisticated

Currently, we treat all the by-(mut/)ref borrows of a coroutine-closure as having a "closure env" borrowed lifetime.

When we have the given code:
```rust
let x: &'a i32 = ...;
let c = async || {
    let _x = *x;
};
```

Then when we call:
```rust
c()
// which, because `AsyncFn` takes a `&self`, we insert an autoref:
(&c /* &'env {coroutine-closure} */)()
```

We will return a future whose captures contain `&'env i32` instead of `&'a i32`, which is way more restrictive than necessary. We should be able to drop `c` while the future is alive since it's not actually borrowing any data *originating from within* the closure's captures, but since the capture has that `'env` lifetime, this is not possible.

This wouldn't be true, for example, if the closure captured `i32` instead of `&'a i32`, because the `'env` lifetime is actually *necessary* since the data (`i32`) is owned by the closure.

This PR identifies two criteria where we *need* to take the borrow with the closure env lifetime:
1. If the closure borrows data from inside the closure's captures. This is not true if the parent capture is by-ref, OR if the parent capture is by-move and the child capture begins with a deref projection. This is the example described above.
2. If we're dealing with mutable references, since we cannot reborrow `&'env mut &'a mut i32` into `&'a mut i32`, *only* `&'env mut i32`.

See the documentation on `should_reborrow_from_env_of_parent_coroutine_closure` for more info.

**important:** As disclaimer states on that function, luckily, if this heuristic is not correct, then the program is not unsound, since we still borrowck and validate the choices made from this function -- the only side-effect is that the user may receive unnecessary borrowck errors.

Fixes #123241
This commit is contained in:
Matthias Krüger 2024-04-11 16:57:40 +02:00 committed by GitHub
commit ee73660368
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 459 additions and 105 deletions

View File

@ -367,37 +367,48 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
ty::INNERMOST,
ty::BoundRegion { var: ty::BoundVar::ZERO, kind: ty::BoundRegionKind::BrEnv },
);
let num_args = args
.as_coroutine_closure()
.coroutine_closure_sig()
.skip_binder()
.tupled_inputs_ty
.tuple_fields()
.len();
let typeck_results = self.typeck_results.borrow();
let tupled_upvars_ty_for_borrow = Ty::new_tup_from_iter(
self.tcx,
self.typeck_results
.borrow()
.closure_min_captures_flattened(
self.tcx.coroutine_for_closure(closure_def_id).expect_local(),
)
// Skip the captures that are just moving the closure's args
// into the coroutine. These are always by move, and we append
// those later in the `CoroutineClosureSignature` helper functions.
.skip(
args.as_coroutine_closure()
.coroutine_closure_sig()
.skip_binder()
.tupled_inputs_ty
.tuple_fields()
.len(),
)
.map(|captured_place| {
let upvar_ty = captured_place.place.ty();
let capture = captured_place.info.capture_kind;
ty::analyze_coroutine_closure_captures(
typeck_results.closure_min_captures_flattened(closure_def_id),
typeck_results
.closure_min_captures_flattened(
self.tcx.coroutine_for_closure(closure_def_id).expect_local(),
)
// Skip the captures that are just moving the closure's args
// into the coroutine. These are always by move, and we append
// those later in the `CoroutineClosureSignature` helper functions.
.skip(num_args),
|(_, parent_capture), (_, child_capture)| {
// This is subtle. See documentation on function.
let needs_ref = should_reborrow_from_env_of_parent_coroutine_closure(
parent_capture,
child_capture,
);
let upvar_ty = child_capture.place.ty();
let capture = child_capture.info.capture_kind;
// Not all upvars are captured by ref, so use
// `apply_capture_kind_on_capture_ty` to ensure that we
// compute the right captured type.
apply_capture_kind_on_capture_ty(
return apply_capture_kind_on_capture_ty(
self.tcx,
upvar_ty,
capture,
Some(closure_env_region),
)
}),
if needs_ref { Some(closure_env_region) } else { child_capture.region },
);
},
),
);
let coroutine_captures_by_ref_ty = Ty::new_fn_ptr(
self.tcx,
@ -1761,6 +1772,63 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
}
}
/// Determines whether a child capture that is derived from a parent capture
/// should be borrowed with the lifetime of the parent coroutine-closure's env.
///
/// There are two cases when this needs to happen:
///
/// (1.) Are we borrowing data owned by the parent closure? We can determine if
/// that is the case by checking if the parent capture is by move, EXCEPT if we
/// apply a deref projection, which means we're reborrowing a reference that we
/// captured by move.
///
/// ```rust
/// #![feature(async_closure)]
/// let x = &1i32; // Let's call this lifetime `'1`.
/// let c = async move || {
/// println!("{:?}", *x);
/// // Even though the inner coroutine borrows by ref, we're only capturing `*x`,
/// // not `x`, so the inner closure is allowed to reborrow the data for `'1`.
/// };
/// ```
///
/// (2.) If a coroutine is mutably borrowing from a parent capture, then that
/// mutable borrow cannot live for longer than either the parent *or* the borrow
/// that we have on the original upvar. Therefore we always need to borrow the
/// child capture with the lifetime of the parent coroutine-closure's env.
///
/// ```rust
/// #![feature(async_closure)]
/// let mut x = 1i32;
/// let c = async || {
/// x = 1;
/// // The parent borrows `x` for some `&'1 mut i32`.
/// // However, when we call `c()`, we implicitly autoref for the signature of
/// // `AsyncFnMut::async_call_mut`. Let's call that lifetime `'call`. Since
/// // the maximum that `&'call mut &'1 mut i32` can be reborrowed is `&'call mut i32`,
/// // the inner coroutine should capture w/ the lifetime of the coroutine-closure.
/// };
/// ```
///
/// If either of these cases apply, then we should capture the borrow with the
/// lifetime of the parent coroutine-closure's env. Luckily, if this function is
/// not correct, then the program is not unsound, since we still borrowck and validate
/// the choices made from this function -- the only side-effect is that the user
/// may receive unnecessary borrowck errors.
fn should_reborrow_from_env_of_parent_coroutine_closure<'tcx>(
parent_capture: &ty::CapturedPlace<'tcx>,
child_capture: &ty::CapturedPlace<'tcx>,
) -> bool {
// (1.)
(!parent_capture.is_by_ref()
&& !matches!(
child_capture.place.projections.get(parent_capture.place.projections.len()),
Some(Projection { kind: ProjectionKind::Deref, .. })
))
// (2.)
|| matches!(child_capture.info.capture_kind, UpvarCapture::ByRef(ty::BorrowKind::MutBorrow))
}
/// Truncate the capture so that the place being borrowed is in accordance with RFC 1240,
/// which states that it's unsafe to take a reference into a struct marked `repr(packed)`.
fn restrict_repr_packed_field_ref_capture<'tcx>(

View File

@ -6,6 +6,7 @@ use crate::{mir, ty};
use std::fmt::Write;
use crate::query::Providers;
use rustc_data_structures::captures::Captures;
use rustc_data_structures::fx::FxIndexMap;
use rustc_hir as hir;
use rustc_hir::def_id::LocalDefId;
@ -415,6 +416,72 @@ impl BorrowKind {
}
}
pub fn analyze_coroutine_closure_captures<'a, 'tcx: 'a, T>(
parent_captures: impl IntoIterator<Item = &'a CapturedPlace<'tcx>>,
child_captures: impl IntoIterator<Item = &'a CapturedPlace<'tcx>>,
mut for_each: impl FnMut((usize, &'a CapturedPlace<'tcx>), (usize, &'a CapturedPlace<'tcx>)) -> T,
) -> impl Iterator<Item = T> + Captures<'a> + Captures<'tcx> {
std::iter::from_coroutine(move || {
let mut child_captures = child_captures.into_iter().enumerate().peekable();
// One parent capture may correspond to several child captures if we end up
// refining the set of captures via edition-2021 precise captures. We want to
// match up any number of child captures with one parent capture, so we keep
// peeking off this `Peekable` until the child doesn't match anymore.
for (parent_field_idx, parent_capture) in parent_captures.into_iter().enumerate() {
// Make sure we use every field at least once, b/c why are we capturing something
// if it's not used in the inner coroutine.
let mut field_used_at_least_once = false;
// A parent matches a child if they share the same prefix of projections.
// The child may have more, if it is capturing sub-fields out of
// something that is captured by-move in the parent closure.
while child_captures.peek().map_or(false, |(_, child_capture)| {
child_prefix_matches_parent_projections(parent_capture, child_capture)
}) {
let (child_field_idx, child_capture) = child_captures.next().unwrap();
// This analysis only makes sense if the parent capture is a
// prefix of the child capture.
assert!(
child_capture.place.projections.len() >= parent_capture.place.projections.len(),
"parent capture ({parent_capture:#?}) expected to be prefix of \
child capture ({child_capture:#?})"
);
yield for_each(
(parent_field_idx, parent_capture),
(child_field_idx, child_capture),
);
field_used_at_least_once = true;
}
// Make sure the field was used at least once.
assert!(
field_used_at_least_once,
"we captured {parent_capture:#?} but it was not used in the child coroutine?"
);
}
assert_eq!(child_captures.next(), None, "leftover child captures?");
})
}
fn child_prefix_matches_parent_projections(
parent_capture: &ty::CapturedPlace<'_>,
child_capture: &ty::CapturedPlace<'_>,
) -> bool {
let HirPlaceBase::Upvar(parent_base) = parent_capture.place.base else {
bug!("expected capture to be an upvar");
};
let HirPlaceBase::Upvar(child_base) = child_capture.place.base else {
bug!("expected capture to be an upvar");
};
parent_base.var_path.hir_id == child_base.var_path.hir_id
&& std::iter::zip(&child_capture.place.projections, &parent_capture.place.projections)
.all(|(child, parent)| child.kind == parent.kind)
}
pub fn provide(providers: &mut Providers) {
*providers = Providers { closure_typeinfo, ..*providers }
}

View File

@ -77,9 +77,10 @@ pub use rustc_type_ir::ConstKind::{
pub use rustc_type_ir::*;
pub use self::closure::{
is_ancestor_or_same_capture, place_to_string_for_capture, BorrowKind, CaptureInfo,
CapturedPlace, ClosureTypeInfo, MinCaptureInformationMap, MinCaptureList,
RootVariableMinCaptureList, UpvarCapture, UpvarId, UpvarPath, CAPTURE_STRUCT_LOCAL,
analyze_coroutine_closure_captures, is_ancestor_or_same_capture, place_to_string_for_capture,
BorrowKind, CaptureInfo, CapturedPlace, ClosureTypeInfo, MinCaptureInformationMap,
MinCaptureList, RootVariableMinCaptureList, UpvarCapture, UpvarId, UpvarPath,
CAPTURE_STRUCT_LOCAL,
};
pub use self::consts::{
Const, ConstData, ConstInt, ConstKind, Expr, ScalarInt, UnevaluatedConst, ValTree,

View File

@ -71,7 +71,7 @@
use rustc_data_structures::unord::UnordMap;
use rustc_hir as hir;
use rustc_middle::hir::place::{PlaceBase, Projection, ProjectionKind};
use rustc_middle::hir::place::{Projection, ProjectionKind};
use rustc_middle::mir::visit::MutVisitor;
use rustc_middle::mir::{self, dump_mir, MirPass};
use rustc_middle::ty::{self, InstanceDef, Ty, TyCtxt, TypeVisitableExt};
@ -124,44 +124,10 @@ impl<'tcx> MirPass<'tcx> for ByMoveBody {
.tuple_fields()
.len();
let mut field_remapping = UnordMap::default();
let mut child_captures = tcx
.closure_captures(coroutine_def_id)
.iter()
.copied()
// By construction we capture all the args first.
.skip(num_args)
.enumerate()
.peekable();
// One parent capture may correspond to several child captures if we end up
// refining the set of captures via edition-2021 precise captures. We want to
// match up any number of child captures with one parent capture, so we keep
// peeking off this `Peekable` until the child doesn't match anymore.
for (parent_field_idx, parent_capture) in
tcx.closure_captures(parent_def_id).iter().copied().enumerate()
{
// Make sure we use every field at least once, b/c why are we capturing something
// if it's not used in the inner coroutine.
let mut field_used_at_least_once = false;
// A parent matches a child if they share the same prefix of projections.
// The child may have more, if it is capturing sub-fields out of
// something that is captured by-move in the parent closure.
while child_captures.peek().map_or(false, |(_, child_capture)| {
child_prefix_matches_parent_projections(parent_capture, child_capture)
}) {
let (child_field_idx, child_capture) = child_captures.next().unwrap();
// This analysis only makes sense if the parent capture is a
// prefix of the child capture.
assert!(
child_capture.place.projections.len() >= parent_capture.place.projections.len(),
"parent capture ({parent_capture:#?}) expected to be prefix of \
child capture ({child_capture:#?})"
);
let field_remapping: UnordMap<_, _> = ty::analyze_coroutine_closure_captures(
tcx.closure_captures(parent_def_id).iter().copied(),
tcx.closure_captures(coroutine_def_id).iter().skip(num_args).copied(),
|(parent_field_idx, parent_capture), (child_field_idx, child_capture)| {
// Store this set of additional projections (fields and derefs).
// We need to re-apply them later.
let child_precise_captures =
@ -192,7 +158,7 @@ impl<'tcx> MirPass<'tcx> for ByMoveBody {
),
};
field_remapping.insert(
(
FieldIdx::from_usize(child_field_idx + num_args),
(
FieldIdx::from_usize(parent_field_idx + num_args),
@ -200,18 +166,10 @@ impl<'tcx> MirPass<'tcx> for ByMoveBody {
needs_deref,
child_precise_captures,
),
);
field_used_at_least_once = true;
}
// Make sure the field was used at least once.
assert!(
field_used_at_least_once,
"we captured {parent_capture:#?} but it was not used in the child coroutine?"
);
}
assert_eq!(child_captures.next(), None, "leftover child captures?");
)
},
)
.collect();
if coroutine_kind == ty::ClosureKind::FnOnce {
assert_eq!(field_remapping.len(), tcx.closure_captures(parent_def_id).len());
@ -241,22 +199,6 @@ impl<'tcx> MirPass<'tcx> for ByMoveBody {
}
}
fn child_prefix_matches_parent_projections(
parent_capture: &ty::CapturedPlace<'_>,
child_capture: &ty::CapturedPlace<'_>,
) -> bool {
let PlaceBase::Upvar(parent_base) = parent_capture.place.base else {
bug!("expected capture to be an upvar");
};
let PlaceBase::Upvar(child_base) = child_capture.place.base else {
bug!("expected capture to be an upvar");
};
parent_base.var_path.hir_id == child_base.var_path.hir_id
&& std::iter::zip(&child_capture.place.projections, &parent_capture.place.projections)
.all(|(child, parent)| child.kind == parent.kind)
}
struct MakeByMoveBody<'tcx> {
tcx: TyCtxt<'tcx>,
field_remapping: UnordMap<FieldIdx, (FieldIdx, Ty<'tcx>, bool, &'tcx [Projection<'tcx>])>,

View File

@ -5,7 +5,6 @@ fn foo() -> Box<dyn std::future::Future<Output = u32>> {
let x = 0u32;
Box::new((async || x)())
//~^ ERROR cannot return value referencing local variable `x`
//~| ERROR cannot return value referencing temporary value
}
fn main() {

View File

@ -7,15 +7,6 @@ LL | Box::new((async || x)())
| | `x` is borrowed here
| returns a value referencing data owned by the current function
error[E0515]: cannot return value referencing temporary value
--> $DIR/async-borrowck-escaping-closure-error.rs:6:5
|
LL | Box::new((async || x)())
| ^^^^^^^^^------------^^^
| | |
| | temporary value created here
| returns a value referencing data owned by the current function
error: aborting due to 2 previous errors
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0515`.

View File

@ -0,0 +1,43 @@
//@ check-pass
//@ edition: 2021
#![feature(async_closure)]
use std::future::Future;
use std::pin::Pin;
use std::{marker::PhantomData, sync::Mutex};
type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
pub struct Scope<'scope, 'env: 'scope> {
enqueued: Mutex<Vec<BoxFuture<'scope, ()>>>,
phantom: PhantomData<&'env ()>,
}
impl<'scope, 'env: 'scope> Scope<'scope, 'env> {
pub fn spawn(&'scope self, future: impl Future<Output = ()> + Send + 'scope) {
self.enqueued.lock().unwrap().push(Box::pin(future));
}
}
fn scope_with_closure<'env, B>(_body: B) -> BoxFuture<'env, ()>
where
for<'scope> B: async FnOnce(&'scope Scope<'scope, 'env>),
{
todo!()
}
type ScopeRef<'scope, 'env> = &'scope Scope<'scope, 'env>;
async fn go<'a>(value: &'a i32) {
let closure = async |scope: ScopeRef<'_, 'a>| {
let _future1 = scope.spawn(async {
// Make sure that `*value` is immutably borrowed with lifetime of
// `'a` and not with the lifetime of the containing coroutine-closure.
let _v = *value;
});
};
scope_with_closure(closure).await;
}
fn main() {}

View File

@ -0,0 +1,44 @@
//@ edition: 2021
//@ check-pass
#![feature(async_closure)]
fn outlives<'a>(_: impl Sized + 'a) {}
async fn call_once(f: impl async FnOnce()) {
f().await;
}
fn simple<'a>(x: &'a i32) {
let c = async || { println!("{}", *x); };
outlives::<'a>(c());
outlives::<'a>(call_once(c));
let c = async move || { println!("{}", *x); };
outlives::<'a>(c());
outlives::<'a>(call_once(c));
}
struct S<'a>(&'a i32);
fn through_field<'a>(x: S<'a>) {
let c = async || { println!("{}", *x.0); };
outlives::<'a>(c());
outlives::<'a>(call_once(c));
let c = async move || { println!("{}", *x.0); };
outlives::<'a>(c());
outlives::<'a>(call_once(c));
}
fn through_field_and_ref<'a>(x: &S<'a>) {
let c = async || { println!("{}", *x.0); };
outlives::<'a>(c());
outlives::<'a>(call_once(c));
let c = async move || { println!("{}", *x.0); };
outlives::<'a>(c());
// outlives::<'a>(call_once(c)); // FIXME(async_closures): Figure out why this fails
}
fn main() {}

View File

@ -0,0 +1,47 @@
//@ edition: 2018
// This is `no-borrow-from-env.rs`, but under edition 2018 we still want to make
// sure that we don't ICE or anything, even if precise closure captures means
// that we can't actually borrowck successfully.
#![feature(async_closure)]
fn outlives<'a>(_: impl Sized + 'a) {}
async fn call_once(f: impl async FnOnce()) {
f().await;
}
fn simple<'a>(x: &'a i32) {
let c = async || { println!("{}", *x); }; //~ ERROR `x` does not live long enough
outlives::<'a>(c());
outlives::<'a>(call_once(c));
let c = async move || { println!("{}", *x); };
outlives::<'a>(c()); //~ ERROR `c` does not live long enough
outlives::<'a>(call_once(c)); //~ ERROR cannot move out of `c`
}
struct S<'a>(&'a i32);
fn through_field<'a>(x: S<'a>) {
let c = async || { println!("{}", *x.0); }; //~ ERROR `x` does not live long enough
outlives::<'a>(c());
outlives::<'a>(call_once(c));
let c = async move || { println!("{}", *x.0); }; //~ ERROR cannot move out of `x`
outlives::<'a>(c()); //~ ERROR `c` does not live long enough
outlives::<'a>(call_once(c)); //~ ERROR cannot move out of `c`
}
fn through_field_and_ref<'a>(x: &S<'a>) {
let c = async || { println!("{}", *x.0); }; //~ ERROR `x` does not live long enough
outlives::<'a>(c());
outlives::<'a>(call_once(c)); //~ ERROR explicit lifetime required in the type of `x`
let c = async move || { println!("{}", *x.0); };
outlives::<'a>(c()); //~ ERROR `c` does not live long enough
// outlives::<'a>(call_once(c)); // FIXME(async_closures): Figure out why this fails
}
fn main() {}

View File

@ -0,0 +1,152 @@
error[E0597]: `x` does not live long enough
--> $DIR/without-precise-captures-we-are-powerless.rs:16:13
|
LL | fn simple<'a>(x: &'a i32) {
| -- lifetime `'a` defined here
LL | let c = async || { println!("{}", *x); };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ borrowed value does not live long enough
LL | outlives::<'a>(c());
LL | outlives::<'a>(call_once(c));
| ------------ argument requires that `x` is borrowed for `'a`
...
LL | }
| - `x` dropped here while still borrowed
error[E0597]: `c` does not live long enough
--> $DIR/without-precise-captures-we-are-powerless.rs:21:20
|
LL | fn simple<'a>(x: &'a i32) {
| -- lifetime `'a` defined here
...
LL | let c = async move || { println!("{}", *x); };
| - binding `c` declared here
LL | outlives::<'a>(c());
| ^--
| |
| borrowed value does not live long enough
| argument requires that `c` is borrowed for `'a`
LL | outlives::<'a>(call_once(c));
LL | }
| - `c` dropped here while still borrowed
error[E0505]: cannot move out of `c` because it is borrowed
--> $DIR/without-precise-captures-we-are-powerless.rs:22:30
|
LL | fn simple<'a>(x: &'a i32) {
| -- lifetime `'a` defined here
...
LL | let c = async move || { println!("{}", *x); };
| - binding `c` declared here
LL | outlives::<'a>(c());
| ---
| |
| borrow of `c` occurs here
| argument requires that `c` is borrowed for `'a`
LL | outlives::<'a>(call_once(c));
| ^ move out of `c` occurs here
error[E0597]: `x` does not live long enough
--> $DIR/without-precise-captures-we-are-powerless.rs:28:13
|
LL | fn through_field<'a>(x: S<'a>) {
| -- lifetime `'a` defined here
LL | let c = async || { println!("{}", *x.0); };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ borrowed value does not live long enough
LL | outlives::<'a>(c());
LL | outlives::<'a>(call_once(c));
| ------------ argument requires that `x` is borrowed for `'a`
...
LL | }
| - `x` dropped here while still borrowed
error[E0505]: cannot move out of `x` because it is borrowed
--> $DIR/without-precise-captures-we-are-powerless.rs:32:13
|
LL | fn through_field<'a>(x: S<'a>) {
| -- lifetime `'a` defined here
LL | let c = async || { println!("{}", *x.0); };
| ---------------------------------- borrow of `x` occurs here
LL | outlives::<'a>(c());
LL | outlives::<'a>(call_once(c));
| ------------ argument requires that `x` is borrowed for `'a`
LL |
LL | let c = async move || { println!("{}", *x.0); };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ move out of `x` occurs here
error[E0597]: `c` does not live long enough
--> $DIR/without-precise-captures-we-are-powerless.rs:33:20
|
LL | fn through_field<'a>(x: S<'a>) {
| -- lifetime `'a` defined here
...
LL | let c = async move || { println!("{}", *x.0); };
| - binding `c` declared here
LL | outlives::<'a>(c());
| ^--
| |
| borrowed value does not live long enough
| argument requires that `c` is borrowed for `'a`
LL | outlives::<'a>(call_once(c));
LL | }
| - `c` dropped here while still borrowed
error[E0505]: cannot move out of `c` because it is borrowed
--> $DIR/without-precise-captures-we-are-powerless.rs:34:30
|
LL | fn through_field<'a>(x: S<'a>) {
| -- lifetime `'a` defined here
...
LL | let c = async move || { println!("{}", *x.0); };
| - binding `c` declared here
LL | outlives::<'a>(c());
| ---
| |
| borrow of `c` occurs here
| argument requires that `c` is borrowed for `'a`
LL | outlives::<'a>(call_once(c));
| ^ move out of `c` occurs here
error[E0597]: `x` does not live long enough
--> $DIR/without-precise-captures-we-are-powerless.rs:38:13
|
LL | fn through_field_and_ref<'a>(x: &S<'a>) {
| -- lifetime `'a` defined here
LL | let c = async || { println!("{}", *x.0); };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ borrowed value does not live long enough
LL | outlives::<'a>(c());
LL | outlives::<'a>(call_once(c));
| ------------ argument requires that `x` is borrowed for `'a`
...
LL | }
| - `x` dropped here while still borrowed
error[E0621]: explicit lifetime required in the type of `x`
--> $DIR/without-precise-captures-we-are-powerless.rs:40:20
|
LL | fn through_field_and_ref<'a>(x: &S<'a>) {
| ------ help: add explicit lifetime `'a` to the type of `x`: `&'a S<'a>`
...
LL | outlives::<'a>(call_once(c));
| ^^^^^^^^^^^^ lifetime `'a` required
error[E0597]: `c` does not live long enough
--> $DIR/without-precise-captures-we-are-powerless.rs:43:20
|
LL | fn through_field_and_ref<'a>(x: &S<'a>) {
| -- lifetime `'a` defined here
...
LL | let c = async move || { println!("{}", *x.0); };
| - binding `c` declared here
LL | outlives::<'a>(c());
| ^--
| |
| borrowed value does not live long enough
| argument requires that `c` is borrowed for `'a`
LL | // outlives::<'a>(call_once(c)); // FIXME(async_closures): Figure out why this fails
LL | }
| - `c` dropped here while still borrowed
error: aborting due to 10 previous errors
Some errors have detailed explanations: E0505, E0597, E0621.
For more information about an error, try `rustc --explain E0505`.