编译报错
主线位置:宏展开一段的出口。这四条报错都不是 async-stream 自己发出的,是编译器在看宏改写之后的代码时报的,所以报错里提到的东西(async 块、
yield语法、一个奇怪的标签、AsyncStream<..>)都要对回到改写规则才看得懂。
flowchart TB
src["stream! { ... }"] --> scrub["Scrub 改写"]
scrub -->|"yield 不在字面位置,没改写"| e0658["E0658 yield syntax is experimental"]
scrub -->|"yield 改写了,但落进别的 async 块"| e0767["E0767 unreachable label"]
scrub -->|"? 改写成 send(Err(e.into()))"| e0282["E0282 type annotations needed"]
scrub --> out["AsyncStream::new(rx, async move { .. })"]
out -->|"直接 .next()"| e0277["E0277 cannot be unpinned"]
依赖:
async-stream = "=0.3.6"
futures = "0.3"
tokio = { version = "=1.54.1", features = ["full"] }
{async block@...} cannot be unpinned(E0277)
use async_stream::stream;
use futures::StreamExt;
#[tokio::main]
async fn main() {
let mut s = stream! {
yield 1;
yield 2;
};
while let Some(v) = s.next().await {
println!("{v}");
}
}
error[E0277]: `{async block@lib.rs:193:9}` cannot be unpinned
--> src/bin/unpin.rs:10:25
|
10 | while let Some(v) = s.next().await {
| ^^^^ within `__Origin<'_, {integer}, {async block@lib.rs:193:9}>`, the trait `Unpin` is not implemented for `{async block@lib.rs:193:9}`
|
= note: consider using the `pin!` macro
consider using `Box::pin` if you need to access the pinned value outside of the current scope
...
= note: required for `AsyncStream<{integer}, {async block@lib.rs:193:9}>` to implement `Unpin`
note: required by a bound in `futures::StreamExt::next`
...
275 | Self: Unpin,
| ^^^^^ required by this bound in `StreamExt::next`
同样的报错对 .await 再报一次。读这条报错要知道三件事:
lib.rs:193:9不是你的文件。 它是 async-stream 的lib.rs第 193 行,stream!这个macro_rules!调用stream_inner!的那一行。宏生成的 async 块借的是这个位置的 span,所以报错里的 async 块「定义在」async-stream 里。__Origin是 pin-project-lite 生成的。AsyncStream用pin_project!声明,generator字段标了#[pin],生成的Unpin实现要求这个字段Unpin;async 块不是Unpin,AsyncStream也就不是(poll_next 的「!Unpin、Send、Sync 与 Debug」)。- 要求
Unpin的是StreamExt::next。next(&mut self)拿的是普通的&mut,要在里面 poll 就得能安全地造出Pin<&mut Self>,只有Self: Unpin时才行。
改法是先 pin。async-stream 自己的文档示例在每个循环前都写了 pin_mut!(s); // needed for iteration(lib.rs 第 41 行),这是 futures_util::pin_mut!;Rust 1.68 起标准库有 std::pin::pin!。三种写法:
// 写法一:pin 在栈上
let mut s = pin!(stream! {
yield 1;
yield 2;
});
while let Some(v) = s.next().await {
println!("pin!: {v}");
}
// 写法二:装箱,可以存进结构体、跨函数返回
let mut b = Box::pin(stream! {
yield 3;
});
while let Some(v) = b.next().await {
println!("Box::pin: {v}");
}
// 写法三:消费方式自己会 pin
let all: Vec<i32> = stream! { yield 4; yield 5; }.collect().await;
println!("collect: {all:?}");
pin!: 1
pin!: 2
Box::pin: 3
collect: [4, 5]
collect、for_each、fold 这些拿 self 的组合子把流存进自己的 future 里,由外层 pin,不需要你操心;stream! 块里的 for await 也一样(for await)。只有 next()、try_next() 这类拿 &mut self 的才要先 pin。
yield syntax is experimental(E0658)
想用迭代器的 for_each 产出每一项:
let s = stream! {
(0..3).for_each(|i| {
yield i;
});
};
let items: Vec<i32> = s.collect().await;
error[E0658]: yield syntax is experimental
--> src/bin/closure_yield.rs:8:7
|
8 | yield i;
| ^^^^^^^
|
= note: see issue #43122 <https://github.com/rust-lang/rust/issues/43122> for more information
error: `yield` can only be used in `#[coroutine]` closures, or `gen` blocks
--> src/bin/closure_yield.rs:8:7
|
8 | yield i;
| ^^^^^^^
|
help: use `#[coroutine]` to make this closure a coroutine
|
7 | (0..3).for_each(#[coroutine] |i| {
| ++++++++++++
error[E0277]: expected an `FnMut({integer})` closure, found `{coroutine@src/bin/closure_yield.rs:7:21: 7:24}`
...
error[E0277]: the trait bound `Vec<i32>: Extend<()>` is not satisfied
四条报错,没有一条提到 stream!。原因在 Scrub:闭包分支原样跳过,yield i 没被改写,留给了编译器。编译器在 stable 上不认 yield 语法,于是报 E0658;它还把这个闭包当成了 coroutine,于是有第二、三条,建议你加 #[coroutine](这条建议在 stable 上没用)。最后一条更绕:块里一个被改写的 yield 都没有,宏插入了 if false { __yield_tx.send(()).await; }(dummy_yield),流的 Item 被推成了 (),collect 进 Vec<i32> 当然不行。
仓库的 tests/ui 里还有另外两个位置,本机 rustc 1.99.0 上的第一条报错相同,后面跟的不一样:
| 位置 | 用例 | 本机的其余报错 |
|---|---|---|
| 闭包里 | yield_in_closure.rs | `yield` can only be used in `#[coroutine]` closures, or `gen` blocks;E0277 闭包类型不对 |
| async 块里 | yield_in_async.rs | E0727 `async` coroutines are not yet supported |
嵌套 fn 里 | yield_in_nested_fn.rs | `yield` can only be used in `#[coroutine]` closures, or `gen` blocks;E0627 yield expression outside of coroutine literal |
看到 yield syntax is experimental,就去找那个 yield 和 stream! 块之间隔着什么:闭包、async { }、fn。改法都是把 yield 搬回块的字面位置:
闭包:换成
for循环。let s = stream! { // 闭包换成 for 循环,yield 回到 stream! 块的字面位置 for i in 0..3 { yield i; } };[0, 1, 2]async 块:让 async 块只算出值,在外面
yield;要消费另一个流,用for await或while let Some(v) = s.next().await。嵌套
fn:让函数返回值(或返回一个Vec/ 一个 stream),在块里yield它的结果。
use of unreachable label '__async_stream_private_check_scope(E0767)
想让两段逻辑并发执行、各自产出值,用了 tokio::join!:
use std::time::Duration;
use async_stream::stream;
use futures::StreamExt;
use tokio::time::sleep;
#[tokio::main]
async fn main() {
// 想让两路并发执行、各自 yield
let s = stream! {
tokio::join!(
async {
sleep(Duration::from_millis(1)).await;
yield "a";
},
async {
yield "b";
},
);
};
let items: Vec<&str> = s.collect().await;
println!("{items:?}");
}
error[E0767]: use of unreachable label `'__async_stream_private_check_scope`
--> src/bin/join_yield.rs:10:11
|
10 | let s = stream! {
| ___________^
11 | | tokio::join!(
12 | | async {
13 | | sleep(Duration::from_millis(1)).await;
... |
19 | | );
20 | | };
| | ^
| | |
| |___unreachable label `'__async_stream_private_check_scope`
| unreachable label defined here
|
= note: labels are unreachable through functions, closures, async blocks and modules
error[E0267]: `break` inside `async` block
--> src/bin/join_yield.rs:10:11
...
| | async {
| | ----- enclosing `async` block
E0267 对两个 async 块各报一次。和上一节对比:这里的 yield 写在 join! 的参数里,join! 是宏调用,Scrub 会进宏调用的 token 找 yield(为了支持 select!,Scrub 的「宏调用」一节);token 层看不出 async { } 的边界,于是两个 yield 都被改写了。改写结果里那句 if false { break '__async_stream_private_check_scope (loop {}); } 落进了 async { },标签穿不过 async 块,编译失败。
这条报错是 0.3.6 加的防线(取舍与对比的 #106 一节),不是宏坏了。它挡住的是「yield 在别的 async 块里执行」这一类代码,不管那个 async 块在运行时会不会真的在别的 stream 里被 poll。仓库自己的回归用例 unsoundness_issue_106.rs 报的是同样两条。
改法:并发的部分只算值,yield 放回块的字面位置:
let s = stream! {
let (a, b) = tokio::join!(
async {
sleep(Duration::from_millis(1)).await;
"a"
},
async { "b" },
);
yield a;
yield b;
};
["a", "b"]
区别是两项要等两路都完成才一起产出。想要「谁先完成谁先产出」,用 tokio::select! 把 yield 写在分支的处理部分(处理部分在外层 async 块里执行,标签可达,见 Scrub 的例子),或者把两路做成两个 stream,用 tokio-stream 的 merge 合并。
type annotations needed for AsyncStream<Result<i32, _>, ...>(E0282)
use async_stream::try_stream;
use futures::StreamExt;
fn parse(s: &str) -> Result<i32, std::num::ParseIntError> {
s.parse()
}
#[tokio::main]
async fn main() {
let s = try_stream! {
let v = parse("1")?;
yield v;
};
let items: Vec<_> = s.collect().await;
println!("{items:?}");
}
error[E0282]: type annotations needed for `AsyncStream<Result<i32, _>, {async block@lib.rs:229:9}>`
--> src/bin/infer.rs:10:7
|
10 | let s = try_stream! {
| ^
...
14 | let items: Vec<_> = s.collect().await;
| - type must be known at this point
|
help: consider giving `s` an explicit type, where the placeholders `_` are specified
|
10 | let s: async_stream::__private::AsyncStream<Result<_, _>, {async block@.../async-stream-0.3.6/src/lib.rs:229:9: 229:63}> = try_stream! {
parse 的错误类型明明是 ParseIntError,为什么推不出来?因为 ? 被改写成了 send(Err(e.into())).await(Scrub 的 ? 分支):e 是 ParseIntError,into() 能转成任何实现了 From<ParseIntError> 的类型,流的错误类型只能从别处得知。这里的 Vec<_> 没给出任何信息。Ok 那一边是 i32,来自 yield v,所以报错里是 Result<i32, _>。
编译器给的建议没法照做:它让你写出 AsyncStream<.., {async block@..}>,async 块的类型写不出名字,AsyncStream 也在 #[doc(hidden)] 的 __private 里。改法是在别处把错误类型钉死:
// 返回类型把错误类型钉死,`?` 里的 e.into() 就有了目标
fn numbers(input: Vec<&'static str>) -> impl Stream<Item = Result<i32, ParseIntError>> {
try_stream! {
for t in input {
let v = parse(t)?;
yield v;
}
}
}
#[tokio::main]
async fn main() {
let items: Vec<_> = numbers(vec!["1", "2"]).collect().await;
println!("fn 返回类型: {items:?}");
// 不想单独写函数时,给变量标类型也行
let s: std::pin::Pin<Box<dyn Stream<Item = Result<i32, ParseIntError>>>> = Box::pin(try_stream! {
let v = parse("3")?;
yield v;
});
let items: Vec<_> = s.collect().await;
println!("变量标注: {items:?}");
}
fn 返回类型: [Ok(1), Ok(2)]
变量标注: [Ok(3)]
两种写法在依赖里都见得到:sea-orm 的 into_stream 返回 PinBoxStream<'db, Result<Vec<_>, DbErr>>,dioxus-fullstack 的 SSE 客户端把流标成 Pin<Box<dyn Stream<Item = Result<ServerSentEvent, ServerFnError>>>>,见 05 在本站中。消费端写成 Vec<Result<i32, ParseIntError>> 也能让推断通过,但把类型信息放在消费端,换一个消费者就又要写一遍,不如放在返回类型上。
小结
- E0277
cannot be unpinned:StreamExt::next要求Unpin,AsyncStream里是 async 块,不是Unpin。先pin!/Box::pin,或者用collect这类拿self的组合子。报错里的lib.rs:193:9是 async-stream 的宏定义位置。 - E0658
yield syntax is experimental:yield隔着闭包、async 块或嵌套fn,Scrub 没改写。把yield搬回块的字面位置。闭包的情况还会附带Extend<()>之类的报错,因为一个yield都没改写时Item被推成()。 - E0767
unreachable label:yield写在别的宏的参数里,被改写后落进了那个宏的 async 块,0.3.6 的标签检查挡住了它。让 async 块只算值,在外面yield。 - E0282
type annotations needed:?改写成了e.into(),错误类型推不出。给返回值写impl Stream<Item = Result<T, E>>,或者给变量标上装箱后的类型。