Bounded Async
Problem
Rust's async fn creates futures with no deadline. A forgotten .await on a network read can block a task forever. In OS kernels and blockchain nodes, this is not a bug — it is a liveness failure that can cost real money (slashing) or crash a system.
Existing workarounds (tokio::time::timeout()) are opt-in and forgettable. They are library-level, not language-level.
Solution
Rs extends async with an optional deadline parameter.
Syntax
// Standard Rust async — still valid, still works
async
// Rs bounded async — deadline is part of the function signature
async
// Duration expressions allowed
async
// Constant expressions allowed
const CONSENSUS_TIMEOUT: Duration = from_millis;
async
Semantics
When async(D) fn foo() -> T is called:
- An internal timer starts with duration
D - Every
.awaitinside the function checks the remaining time - If the timer expires before the function returns, the future resolves to a timeout error
- The function must return
Result<T, E>whereE: From<rs::Timeout>. On timeout, the future resolves toErr(E::from(rs::Timeout))
Nested calls:
async
async
The effective deadline is min(own_deadline, caller_remaining). Deadlines propagate inward, never expand.
Time Source
The time source backing with_deadline is provided by the runtime, not by the compiler. For deterministic systems (consensus nodes), the runtime must use logical time (step-based or block-height-based) so that deadline expiration is identical across all nodes. Wall-clock time is acceptable only in non-deterministic contexts. Rs enforces the presence of a deadline; the runtime determines the clock that measures it.
Rs Edition Enforcement
In edition = "rs":
// ERROR in rs edition: async fn without deadline
async
// OK: explicit opt-out for rare cases (must justify)
async
In standard Rust editions, async(duration) is available but not required.
Implementation
Code examples below use the rs:: logical namespace. In Rust code, import as rs_lang:: (see stdlib.md).
Inside cell! macro: the macro parses async(dur) fn from its own token stream and generates the timeout wrapping. The deadline expression must be a const expression of type Duration.
Outside cells: the #[bounded_async(dur)] attribute macro provides the same functionality with standard Rust syntax:
// Inside cell! — custom syntax, parsed by macro:
pub async
// Outside cell! — standard attribute syntax:
async
Both desugar to the same code:
// Desugared (approximately):
No rustc parser modification needed. The async(dur) syntax only exists inside cell! token streams.
The timeout marker type:
/// Unit struct returned when a bounded async function exceeds its deadline.
;
The bounded async function's error type must implement From<rs::Timeout>. For functions where timeout is the only error, use rs::Timeout directly as the error type:
async
For functions with application-specific errors, include a timeout variant:
async
Implementation: the cell! macro handles async(dur) syntax internally (~included in cell macro line count). Outside cells, #[bounded_async(dur)] attribute macro provides the same functionality (~200 lines in rs-lang-macros). No rustc parser modification needed. Diagnostic messages: ~100 lines.
Error Reference
See errors/async.md for detailed description of RS101.