Skip to main content

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;
}
warning

await can only be used inside:

  • async fn functions
  • async { } blocks
  • async 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