Async/Await
Oxide provides comprehensive async/await support with modern concurrency patterns inspired by Swift and Kotlin.
Async Functions
Mark functions as async for asynchronous execution:
async fn fetchData(url: String): String {
let response = await http::get(url)
return response.text()
}
async fn loadUser(id: Int): User {
let data = await fetchData("/users/$id")
return parseUser(data)
}
Transpiles to:
async fn fetch_data(url: String) -> String {
let response = http::get(url).await;
return response.text();
}
async fn load_user(id: isize) -> User {
let data = fetch_data(&format!("/users/{}", id)).await;
return parse_user(&data);
}
Await Expressions
Use prefix await syntax (like JavaScript/Swift):
async fn process() {
let data = await fetchData()
let result = await transform(data)
await save(result)
}
Transpiles to:
async fn process() {
let data = fetch_data().await;
let result = transform(data).await;
save(result).await;
}
await can only be used inside:
async fnfunctionsasync { }blocksasync capture { }blocks
Using await outside these contexts produces error E0728.
Async Blocks
Create ad-hoc futures with async blocks:
let future = async {
let data = await fetchData()
processData(data)
}
// With captured ownership
let name = "task"
let future = async capture {
println!("Running $name")
await doWork()
}
Transpiles to:
let future = async {
let data = fetch_data().await;
process_data(data)
};
let name = "task".to_string();
let future = async move {
println!("Running {}", name);
do_work().await
};
Concurrent Bindings (async let)
Start multiple async operations concurrently:
async fn fetchAll(): (User, Posts, Comments) {
// Start all operations concurrently
async let user = fetchUser()
async let posts = fetchPosts()
async let comments = fetchComments()
// Await them together
await (user, posts, comments)
}
Transpiles to:
async fn fetch_all() -> (User, Posts, Comments) {
tokio::join!(
fetch_user(),
fetch_posts(),
fetch_comments()
)
}
Task Spawning
Spawn background tasks with Task { }:
// Fire-and-forget
Task { await doBackgroundWork() }
// Get handle for result
let task = Task { await computeValue() }
let result = await task.value
// Safe result (won't panic)
let result = await task.result // Result<T, TaskError>
Transpiles to:
tokio::spawn(async move { do_background_work().await });
let task = tokio::spawn(async move { compute_value().await });
let result = task.await.unwrap();
Task Cancellation
Tasks support cooperative cancellation:
let task = Task {
while !self.isCancelled {
await doWork()
}
}
// Cancel from outside
task.cancel()
// With guard pattern
let task = Task {
guard !self.isCancelled else { return }
await doExpensiveWork()
}
Transpiles to: Uses tokio_util::sync::CancellationToken pattern.
Structured Concurrency (TaskGroup)
Execute parallel tasks with TaskGroup:
// Basic task group
let results = await TaskGroup<Data> { group in
for url in urls {
group.add { await fetch(url) }
}
}
// With concurrency limit
let results = await TaskGroup<Data>(maxConcurrency: 10) { group in
for url in urls {
group.add { await fetch(url) }
}
}
Transpiles to:
let results = {
let mut set = tokio::task::JoinSet::new();
for url in urls {
let url = url.clone();
set.spawn(async move { fetch(&url).await });
}
let mut results = Vec::new();
while let Some(result) = set.join_next().await {
results.push(result.unwrap());
}
results
};
Async Streams
Create and consume async streams:
Creating Streams
fn countdown(from: Int): Stream<Int> = stream {
var i = from
while i > 0 {
yield i
i -= 1
await delay(1.second)
}
}
Transpiles to:
fn countdown(from: isize) -> impl futures::Stream<Item = isize> {
async_stream::stream! {
let mut i = from;
while i > 0 {
yield i;
i -= 1;
delay(Duration::from_secs(1)).await;
}
}
}
Consuming Streams
for await n in countdown(from: 5) {
println!("$n...")
}
println!("Liftoff!")
Transpiles to:
while let Some(n) = countdown(5).next().await {
println!("{}...", n);
}
println!("Liftoff!");
Stream Operators
let doubled = countdown(from: 3)
.map { it * 2 }
.filter { it > 2 }
for await n in doubled {
println!(n)
}
Select/Racing
Race multiple async operations:
select {
let a = await fetchA() -> handleA(a)
let b = await fetchB() -> handleB(b)
await timeout(1.second) -> handleTimeout()
}
// Select as expression
let result = select {
let data = await fetchFast() -> data
let data = await fetchSlow() -> data
await timeout(5.seconds) -> null
}
Transpiles to:
tokio::select! {
a = fetch_a() => handle_a(a),
b = fetch_b() => handle_b(b),
_ = timeout(Duration::from_secs(1)) => handle_timeout(),
}
Timeout Helpers
Add timeouts to async operations:
// Throws TimeoutError if exceeded
let result = await within(5.seconds) {
await slowOperation()
}
// Returns null on timeout
let result = await within?(5.seconds) {
await slowOperation()
}
// With null coalescing
let data = await within?(3.seconds) {
await fetchFromSlowServer()
} ?? defaultData
Transpiles to:
let result = tokio::time::timeout(
Duration::from_secs(5),
async { slow_operation().await }
).await?;
let result = tokio::time::timeout(
Duration::from_secs(5),
async { slow_operation().await }
).await.ok();
Duration Literals
Convenient duration syntax:
5.seconds // Duration::from_secs(5)
100.millis // Duration::from_millis(100)
1.minute // Duration::from_secs(60)
2.hours // Duration::from_secs(7200)
Async Closures
// Async closure without parameters
let fetch = async { await getData() }
// Async closure with parameters
let process = async { url -> await fetchUrl(url) }
// Async capture closure
let owned = async capture { await consume(data) }
Transpiles to:
let fetch = async || get_data().await;
let process = async |url| fetch_url(url).await;
let owned = async move || consume(data).await;
impl Future Return Types
For trait methods with async returns:
use std::future::Future
trait Service {
fn call(request: Request): impl Future<Output = Response>
}
fn fetch(): impl Future<Output = String> {
async { "data" }
}
See Also
- Functions - Function modifiers
- Closures - Async closures
- Error Handling - Handling async errors