rust-lang / rust-lang/rust-clippy
clippy::large_futures does not fire when the .await comes from a macro expansion (e.g. #[tauri::command])
@0xull is already working on this.
Since Sep 4, 2026.
- Dominant language
- Rust
- Stars
- 13.5k
- Forks
- 2.2k
- Avg merge
- 2d 10h
- Merged PRs (30d)
- 32
Description
Summary
clippy::large_futures does not fire when the .await expression is produced by a macro expansion, even though the awaited future is just as large as in the direct-call case. The only difference is whether the .await itself is macro-generated.
Version
clippy 0.1.93 (01f6ddf758 2026-02-11)
rustc 1.93.1 (01f6ddf75 2026-02-11)
Reproduction (no dependencies)
#![allow(dead_code)]
async fn yield_now() {}
async fn big() -> u8 {
let big = [0u8; 65536];
yield_now().await;
big[0]
}
async fn direct_caller() {
let _v = big().await; // ✔ fires: "large future with a size of 65538 bytes"
}
macro_rules! call_it {
($f:expr) => {
$f.await
};
}
async fn macro_caller() {
let _v = call_it!(big()); // ✗ silently passes
}
fn main() {}
cargo clippy -- -W clippy::large_futures
Only direct_caller is flagged. Replacing the macro with a plain .await on the same expression makes the lint fire, so the future size itself is clearly computable in both cases — the from_expansion() guard on the await expression appears to suppress it. The same behavior occurs with a crate-level #![warn(clippy::large_futures)] instead of the command-line flag, and with -D instead of -W.
Not covered by the existing test
The macro_expn case in tests/ui/large_futures.rs does expand a macro, but the macro produces the async block (the future itself) while the .await is hand-written outside the macro (macro_!().await). I verified that running that test file as-is does produce the annotated warnings on this toolchain — but the case where the .await itself comes from macro expansion (future from a clean call site, await generated inside the macro) is not exercised by the test suite.
The from_expansion() exclusion may well be intentional to avoid lint noise inside macros; the problem is that it also exempts the pattern where a framework's macro polls a user-provided future, which is exactly where large futures bite.
Why this matters in practice
Frameworks that poll user futures through macro-generated code are where large futures cause real stack overflows:
- Tauri's
#[tauri::command]on anasync fnpolls the command future through macro-generated wrapper code. We recently debugged a production crash (0xC00000FDstack overflow on Windows, no logs since the kernel kills the process) caused by a ~329 KB command future blowing up the invoke-dispatch stack frame. The dispatch chain held ~993 KiB of frames in total. Fixed byBox::pin-ing the future inside the command body — butclippy::large_futuresnever flagged it, because the await point lives inside the macro expansion. - In debug builds Tauri itself
Box::pins command futures (for faster compiles), so the problem is release-only and invisible during development, which makes a working lint the natural first line of defense.
Meta
Tried variants to rule out other causes: awaiting across an async fn boundary vs. an async block, tokio::spawn, future-size-threshold in clippy.toml (note: changing clippy.toml alone does not invalidate cargo's fingerprint cache — needs a touch on the sources to re-run), a full #[tauri::command] reproduction, and crate-level attributes vs. command-line flags. Direct awaits fire in every variant; macro-expanded awaits never do.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.