Error on using yield without also using #[coroutine] on the closure

And suggest adding the `#[coroutine]` to the closure
This commit is contained in:
Oli Scherer 2024-04-11 13:15:34 +00:00
parent a589632dad
commit aef0f4024a
279 changed files with 1290 additions and 886 deletions

View file

@ -26,13 +26,13 @@ tweaks to the overall design.
A syntactical example of a coroutine is:
```rust
#![feature(coroutines, coroutine_trait)]
#![feature(coroutines, coroutine_trait, stmt_expr_attributes)]
use std::ops::{Coroutine, CoroutineState};
use std::pin::Pin;
fn main() {
let mut coroutine = || {
let mut coroutine = #[coroutine] || {
yield 1;
return "foo"
};
@ -48,7 +48,8 @@ fn main() {
}
```
Coroutines are closure-like literals which can contain a `yield` statement. The
Coroutines are closure-like literals which are annotated with `#[coroutine]`
and can contain a `yield` statement. The
`yield` statement takes an optional expression of a value to yield out of the
coroutine. All coroutine literals implement the `Coroutine` trait in the
`std::ops` module. The `Coroutine` trait has one main method, `resume`, which
@ -58,13 +59,13 @@ An example of the control flow of coroutines is that the following example
prints all numbers in order:
```rust
#![feature(coroutines, coroutine_trait)]
#![feature(coroutines, coroutine_trait, stmt_expr_attributes)]
use std::ops::Coroutine;
use std::pin::Pin;
fn main() {
let mut coroutine = || {
let mut coroutine = #[coroutine] || {
println!("2");
yield;
println!("4");
@ -78,9 +79,9 @@ fn main() {
}
```
At this time the main intended use case of coroutines is an implementation
primitive for async/await syntax, but coroutines will likely be extended to
ergonomic implementations of iterators and other primitives in the future.
At this time the main use case of coroutines is an implementation
primitive for `async`/`await` and `gen` syntax, but coroutines
will likely be extended to other primitives in the future.
Feedback on the design and usage is always appreciated!
### The `Coroutine` trait
@ -163,14 +164,14 @@ which point all state is saved off in the coroutine and a value is returned.
Let's take a look at an example to see what's going on here:
```rust
#![feature(coroutines, coroutine_trait)]
#![feature(coroutines, coroutine_trait, stmt_expr_attributes)]
use std::ops::Coroutine;
use std::pin::Pin;
fn main() {
let ret = "foo";
let mut coroutine = move || {
let mut coroutine = #[coroutine] move || {
yield 1;
return ret
};
@ -183,7 +184,7 @@ fn main() {
This coroutine literal will compile down to something similar to:
```rust
#![feature(arbitrary_self_types, coroutines, coroutine_trait)]
#![feature(arbitrary_self_types, coroutine_trait)]
use std::ops::{Coroutine, CoroutineState};
use std::pin::Pin;

View file

@ -5,13 +5,13 @@ each one organized by a "feature flag." That is, when using an unstable
feature of Rust, you must use a flag, like this:
```rust
#![feature(coroutines, coroutine_trait)]
#![feature(coroutines, coroutine_trait, stmt_expr_attributes)]
use std::ops::{Coroutine, CoroutineState};
use std::pin::Pin;
fn main() {
let mut coroutine = || {
let mut coroutine = #[coroutine] || {
yield 1;
return "foo"
};

View file

@ -1,9 +1,9 @@
// Regression test for #5238 / https://github.com/rust-lang/rust/pull/69562
#![feature(coroutines, coroutine_trait)]
#![feature(coroutines, coroutine_trait, stmt_expr_attributes)]
fn main() {
let _ = || {
let _ = #[coroutine] || {
yield;
};
}

View file

@ -1,4 +1,3 @@
#![feature(coroutines)]
#![warn(clippy::large_futures)]
#![allow(clippy::never_loop)]
#![allow(clippy::future_not_send)]

View file

@ -1,4 +1,3 @@
#![feature(coroutines)]
#![warn(clippy::large_futures)]
#![allow(clippy::never_loop)]
#![allow(clippy::future_not_send)]

View file

@ -1,5 +1,5 @@
error: large future with a size of 16385 bytes
--> tests/ui/large_futures.rs:11:9
--> tests/ui/large_futures.rs:10:9
|
LL | big_fut([0u8; 1024 * 16]).await;
| ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider `Box::pin` on it: `Box::pin(big_fut([0u8; 1024 * 16]))`
@ -8,37 +8,37 @@ LL | big_fut([0u8; 1024 * 16]).await;
= help: to override `-D warnings` add `#[allow(clippy::large_futures)]`
error: large future with a size of 16386 bytes
--> tests/ui/large_futures.rs:15:5
--> tests/ui/large_futures.rs:14:5
|
LL | f.await
| ^ help: consider `Box::pin` on it: `Box::pin(f)`
error: large future with a size of 16387 bytes
--> tests/ui/large_futures.rs:20:9
--> tests/ui/large_futures.rs:19:9
|
LL | wait().await;
| ^^^^^^ help: consider `Box::pin` on it: `Box::pin(wait())`
error: large future with a size of 16387 bytes
--> tests/ui/large_futures.rs:25:13
--> tests/ui/large_futures.rs:24:13
|
LL | wait().await;
| ^^^^^^ help: consider `Box::pin` on it: `Box::pin(wait())`
error: large future with a size of 65540 bytes
--> tests/ui/large_futures.rs:33:5
--> tests/ui/large_futures.rs:32:5
|
LL | foo().await;
| ^^^^^ help: consider `Box::pin` on it: `Box::pin(foo())`
error: large future with a size of 49159 bytes
--> tests/ui/large_futures.rs:35:5
--> tests/ui/large_futures.rs:34:5
|
LL | calls_fut(fut).await;
| ^^^^^^^^^^^^^^ help: consider `Box::pin` on it: `Box::pin(calls_fut(fut))`
error: large future with a size of 65540 bytes
--> tests/ui/large_futures.rs:48:5
--> tests/ui/large_futures.rs:47:5
|
LL | / async {
LL | |
@ -59,7 +59,7 @@ LL + })
|
error: large future with a size of 65540 bytes
--> tests/ui/large_futures.rs:60:13
--> tests/ui/large_futures.rs:59:13
|
LL | / async {
LL | | let x = [0i32; 1024 * 16];

View file

@ -1,7 +1,7 @@
//@aux-build:proc_macros.rs
#![allow(unused, clippy::no_effect, clippy::needless_pass_by_ref_mut)]
#![warn(clippy::redundant_locals)]
#![feature(async_closure, coroutines)]
#![feature(async_closure, coroutines, stmt_expr_attributes)]
extern crate proc_macros;
use proc_macros::{external, with_span};
@ -191,11 +191,11 @@ fn issue12225() {
let v4 = v4;
dbg!(&v4);
});
assert_static(static || {
assert_static(#[coroutine] static || {
let v5 = v5;
yield;
});
assert_static(|| {
assert_static(#[coroutine] || {
let v6 = v6;
yield;
});

View file

@ -1,5 +1,5 @@
//@compile-flags: -Zmiri-disable-validation -Zmiri-disable-stacked-borrows
#![feature(coroutines, coroutine_trait)]
#![feature(coroutines, coroutine_trait, stmt_expr_attributes)]
use std::{
ops::{Coroutine, CoroutineState},
@ -7,7 +7,7 @@ use std::{
};
fn firstn() -> impl Coroutine<Yield = u64, Return = ()> {
static move || {
#[coroutine] static move || {
let mut num = 0;
let num = &mut num;
*num += 0;

View file

@ -1,6 +1,6 @@
//@revisions: stack tree
//@[tree]compile-flags: -Zmiri-tree-borrows
#![feature(coroutines, coroutine_trait, never_type)]
#![feature(coroutines, coroutine_trait, never_type, stmt_expr_attributes)]
use std::fmt::Debug;
use std::mem::ManuallyDrop;
@ -43,9 +43,9 @@ fn basic() {
panic!()
}
finish(1, false, || yield 1);
finish(1, false, #[coroutine] || yield 1);
finish(3, false, || {
finish(3, false, #[coroutine] || {
let mut x = 0;
yield 1;
x += 1;
@ -55,27 +55,27 @@ fn basic() {
assert_eq!(x, 2);
});
finish(7 * 8 / 2, false, || {
finish(7 * 8 / 2, false, #[coroutine] || {
for i in 0..8 {
yield i;
}
});
finish(1, false, || {
finish(1, false, #[coroutine] || {
if true {
yield 1;
} else {
}
});
finish(1, false, || {
finish(1, false, #[coroutine] || {
if false {
} else {
yield 1;
}
});
finish(2, false, || {
finish(2, false, #[coroutine] || {
if {
yield 1;
false
@ -88,7 +88,7 @@ fn basic() {
// also test self-referential coroutines
assert_eq!(
finish(5, true, static || {
finish(5, true, #[coroutine] static || {
let mut x = 5;
let y = &mut x;
*y = 5;
@ -99,7 +99,7 @@ fn basic() {
10
);
assert_eq!(
finish(5, true, || {
finish(5, true, #[coroutine] || {
let mut x = Box::new(5);
let y = &mut *x;
*y = 5;
@ -111,7 +111,7 @@ fn basic() {
);
let b = true;
finish(1, false, || {
finish(1, false, #[coroutine] || {
yield 1;
if b {
return;
@ -123,7 +123,7 @@ fn basic() {
drop(x);
});
finish(3, false, || {
finish(3, false, #[coroutine] || {
yield 1;
#[allow(unreachable_code)]
let _x: (String, !) = (String::new(), {
@ -172,7 +172,7 @@ fn smoke_resume_arg() {
}
drain(
&mut |mut b| {
&mut #[coroutine] |mut b| {
while b != 0 {
b = yield (b + 1);
}
@ -181,21 +181,21 @@ fn smoke_resume_arg() {
vec![(1, Yielded(2)), (-45, Yielded(-44)), (500, Yielded(501)), (0, Complete(-1))],
);
expect_drops(2, || drain(&mut |a| yield a, vec![(DropMe, Yielded(DropMe))]));
expect_drops(2, || drain(&mut #[coroutine] |a| yield a, vec![(DropMe, Yielded(DropMe))]));
expect_drops(6, || {
drain(
&mut |a| yield yield a,
&mut #[coroutine] |a| yield yield a,
vec![(DropMe, Yielded(DropMe)), (DropMe, Yielded(DropMe)), (DropMe, Complete(DropMe))],
)
});
#[allow(unreachable_code)]
expect_drops(2, || drain(&mut |a| yield return a, vec![(DropMe, Complete(DropMe))]));
expect_drops(2, || drain(&mut #[coroutine] |a| yield return a, vec![(DropMe, Complete(DropMe))]));
expect_drops(2, || {
drain(
&mut |a: DropMe| {
&mut #[coroutine] |a: DropMe| {
if false { yield () } else { a }
},
vec![(DropMe, Complete(DropMe))],
@ -205,7 +205,7 @@ fn smoke_resume_arg() {
expect_drops(4, || {
drain(
#[allow(unused_assignments, unused_variables)]
&mut |mut a: DropMe| {
&mut #[coroutine] |mut a: DropMe| {
a = yield;
a = yield;
a = yield;
@ -228,7 +228,7 @@ fn uninit_fields() {
}
fn run<T>(x: bool, y: bool) {
let mut c = || {
let mut c = #[coroutine] || {
if x {
let _a: T;
if y {

View file

@ -1,6 +1,6 @@
// See https://github.com/rust-lang/unsafe-code-guidelines/issues/148:
// this fails when Stacked Borrows is strictly applied even to `!Unpin` types.
#![feature(coroutines, coroutine_trait)]
#![feature(coroutines, coroutine_trait, stmt_expr_attributes)]
use std::{
ops::{Coroutine, CoroutineState},
@ -8,7 +8,7 @@ use std::{
};
fn firstn() -> impl Coroutine<Yield = u64, Return = ()> {
static move || {
#[coroutine] static move || {
let mut num = 0;
let num = &mut num;

View file

@ -232,7 +232,7 @@ fn test_coroutine() {
}
#[rustfmt::skip]
let coroutine = #[track_caller] |arg: String| {
let coroutine = #[track_caller] #[coroutine] |arg: String| {
yield ("first", arg.clone(), Location::caller());
yield ("second", arg.clone(), Location::caller());
};
@ -255,7 +255,7 @@ fn test_coroutine() {
assert_eq!(mono_loc.column(), 42);
#[rustfmt::skip]
let non_tracked_coroutine = || { yield Location::caller(); };
let non_tracked_coroutine = #[coroutine] || { yield Location::caller(); };
let non_tracked_line = line!() - 1; // This is the line of the coroutine, not its caller
let non_tracked_loc = match Box::pin(non_tracked_coroutine).as_mut().resume(()) {
CoroutineState::Yielded(val) => val,
@ -263,7 +263,7 @@ fn test_coroutine() {
};
assert_eq!(non_tracked_loc.file(), file!());
assert_eq!(non_tracked_loc.line(), non_tracked_line);
assert_eq!(non_tracked_loc.column(), 44);
assert_eq!(non_tracked_loc.column(), 57);
}
fn main() {

View file

@ -3869,7 +3869,7 @@ use std::ops::{Coroutine, CoroutineState};
use std::pin::Pin;
fn main() {
let mut coroutine = || {
let mut coroutine = #[coroutine] || {
yield 1;
return "foo"
};
@ -3901,7 +3901,7 @@ use std::ops::Coroutine;
use std::pin::Pin;
fn main() {
let mut coroutine = || {
let mut coroutine = #[coroutine] || {
println!("2");
yield;
println!("4");
@ -4007,7 +4007,7 @@ use std::pin::Pin;
fn main() {
let ret = "foo";
let mut coroutine = move || {
let mut coroutine = #[coroutine] move || {
yield 1;
return ret
};

View file

@ -1,7 +1,8 @@
#![feature(coroutines)]
unsafe fn foo() {
let mut ga = static || {
let mut ga = #[coroutine]
static || {
yield 1;
};
}

View file

@ -1,7 +1,8 @@
#![feature(coroutines)]
unsafe fn foo() {
let mut ga = static || {
let mut ga = #[coroutine]
static || {
yield 1;
};
}