Why Async?
Async programming lets your code perform other work while waiting for I/O operations. This is essential for:
- Network requests (API calls, database queries)
- File operations
- AI inference calls
- Any operation with latency
Async Functions
Mark functions as async to indicate they may suspend:
// Async function returns Future<T>
async fn fetch_user(id: String) -> Result<User, HttpError> {
let response = await http::get("https://api.example.com/users/{id}")
let user: User = await response.json()
Ok(user)
}
fn main() {
// Await the async function
let user = await fetch_user("123")
print("Found user: {user.name}")
}
Futures
An async function returns a Future<T> - a value that will be available later.
Use await to get the actual value when it's ready.
Running in Parallel
Use parallel to run multiple async operations concurrently:
async fn fetch_dashboard_data() -> Dashboard {
// Run all three requests in parallel
let (user, posts, notifications) = await parallel(
fetch_user(),
fetch_posts(),
fetch_notifications()
)
Dashboard { user, posts, notifications }
}
// Parallel with a list of futures
async fn fetch_all_users(ids: List<String>) -> List<User> {
let futures = ids.map(id => fetch_user(id))
await parallel(futures)
}
Sequential Await
Sometimes you need to run operations in sequence, where each depends on the previous:
async fn process_order(order_id: String) -> Result<Receipt, Error> {
// Step 1: Validate the order
let order = await fetch_order(order_id)?
// Step 2: Check inventory (depends on order)
let inventory = await check_inventory(order.items)?
// Step 3: Process payment (depends on order)
let payment = await charge_payment(order.total)?
// Step 4: Create shipment (depends on all above)
let shipment = await create_shipment(order, inventory)?
Ok(Receipt { order, payment, shipment })
}
Timeouts
Prevent operations from hanging indefinitely with timeout:
async fn fetch_with_timeout(url: String) -> Result<Response, Error> {
match await timeout(Duration::seconds(5), http::get(url)) {
Ok(response) => Ok(response),
Err(TimeoutError) => Err(Error::new("Request timed out"))
}
}
// Retry with exponential backoff
async fn fetch_with_retry(url: String, max_retries: i64) -> Result<Response, Error> {
var delay = Duration::ms(100)
for attempt in 1..=max_retries {
match await fetch_with_timeout(url.clone()) {
Ok(response) => return Ok(response),
Err(e) if attempt < max_retries => {
print("Attempt {attempt} failed, retrying...")
await sleep(delay)
delay = delay * 2 // Exponential backoff
},
Err(e) => return Err(e)
}
}
}
Select - First to Complete
Use select when you want the result from whichever future
completes first:
async fn fetch_fastest(urls: List<String>) -> Response {
let futures = urls.map(url => http::get(url))
await select(futures) // Returns first to complete
}
// Race between operation and timeout
async fn with_deadline<T>(
deadline: Duration,
operation: Future<T>
) -> Result<T, TimeoutError> {
await select {
result = operation => Ok(result),
_ = sleep(deadline) => Err(TimeoutError)
}
}
Async in Actors
Actor message handlers can be async:
actor DataFetcher {
var cache: Map<String, Data> = {}
// Async receive handler
async receive Fetch(key: String) -> Data {
// Check cache first
if let Some(cached) = cache.get(key) {
return cached
}
// Fetch from remote
let data = await http::get("https://api.example.com/data/{key}")
let parsed: Data = await data.json()
// Cache the result
cache.insert(key, parsed.clone())
parsed
}
async receive FetchMany(keys: List<String>) -> List<Data> {
// Fetch all in parallel
let futures = keys.map(key => ask(self, Fetch(key)))
await parallel(futures)
}
}
Async Streams
Process sequences of async values with streams:
// Async iterator
async fn process_events(stream: EventStream) {
// Process each event as it arrives
for await event in stream {
match event {
Event::Message(msg) => handle_message(msg),
Event::Error(e) => handle_error(e),
Event::Close => break
}
}
}
// Transform a stream
async fn transform_stream(input: Stream<RawData>) -> Stream<Processed> {
input
.filter(data => data.is_valid())
.map(async data => {
await process(data)
})
}
Try It Yourself
Build an async web scraper that:
- Takes a list of URLs
- Fetches them all in parallel with a concurrency limit of 5
- Has a 10-second timeout per request
- Retries failed requests up to 3 times
- Returns the successful responses
Summary
In this tutorial, you learned:
- Declaring async functions with
async fn - Awaiting futures with the
awaitkeyword - Running operations in parallel with
parallel - Using
timeoutto prevent hangs - Using
selectto race futures - Implementing async message handlers in actors
- Processing async streams with
for await
In the next tutorial, we'll explore error handling with Result, Option, and the ? operator.