Что такое async Rust
Async/await в Rust позволяет писать асинхронный код, который выглядит как синхронный. Rust не имеет встроенного runtime — для этого используются библиотеки как Tokio.
▸Основы
1async fn fetch_data() -> String {2 "Hello from async".to_string()3}45#[tokio::main]6async fn main() {7 let result = fetch_data().await;8 println!("{}", result);9}
Tokio Runtime
▸Запуск Tokio
1use tokio;23#[tokio::main]4async fn main() {5 // Tokio runtime автоматически создаётся6 println!("Running in Tokio runtime");7}89// Или с явной конфигурацией10#[tokio::main(flavor = "multi_thread", worker_threads = 10)]11async fn main() {12 // ...13}
▸Задачи (spawn)
1use tokio::task;23#[tokio::main]4async fn main() {5 // Запуск задачи6 let handle = task::spawn(async {7 // Асинхронная работа8 "result from task"9 });1011 // Ожидание результата12 let result = handle.await.unwrap();13 println!("{}", result);14}
Futures
▸Что такое Future
1use std::future::Future;2use std::pin::Pin;34// Future — это значение, которое станет доступным в будущем5fn my_future() -> impl Future<Output = String> {6 async {7 "hello".to_string()8 }9}
▸Self-referential futures
1use std::future::Future;2use std::pin::Pin;34fn make_future() -> Pin<Box<dyn Future<Output = i32>>> {5 Box::pin(async {6 427 })8}
Await
▸Использование await
1async fn get_user(id: u32) -> User {2 // await приостанавливает выполнения до готовности результата3 let response = reqwest::get(&format!("https://api.example.com/users/{}", id))4 .await5 .unwrap();67 response.json().await.unwrap()8}
▸Параллельное выполнение
1use tokio::join;23async fn main() {4 let (user, orders) = join!(5 get_user(1),6 get_orders(1)7 );89 println!("User: {:?}", user);10 println!("Orders: {:?}", orders);11}
Streams
▸Основы streams
1use tokio_stream::StreamExt;23async fn main() {4 let mut stream = tokio_stream::iter(vec![1, 2, 3, 4, 5]);56 while let Some(value) = stream.next().await {7 println!("Got: {}", value);8 }9}
▸Создание stream
1use tokio_stream::Stream;2use std::pin::Pin;3use std::task::{Context, Poll};45fn create_stream() -> impl Stream<Item = i32> {6 async_stream::stream! {7 for i in 0..10 {8 tokio::time::sleep(Duration::from_millis(100)).await;9 yield i;10 }11 }12}
Таймауты
1use tokio::time::{timeout, Duration};23async fn main() {4 // Таймаут 5 секунд5 match timeout(Duration::from_secs(5), long_operation()).await {6 Ok(result) => println!("Got: {}", result),7 Err(_) => println!("Operation timed out"),8 }9}
Каналы (mpsc)
1use tokio::sync::mpsc;23#[tokio::main]4async fn main() {5 let (tx, mut rx) = mpsc::channel(32);67 let tx2 = tx.clone();89 tokio::spawn(async move {10 tx.send("hello from task 1").await.unwrap();11 });1213 tokio::spawn(async move {14 tx2.send("hello from task 2").await.unwrap();15 });1617 while let Some(message) = rx.recv().await {18 println!("Received: {}", message);19 }20}
Мьютексы
1use tokio::sync::Mutex;2use std::sync::Arc;34async fn main() {5 let data = Arc::new(Mutex::new(0));67 let mut handles = vec![];89 for _ in 0..10 {10 let data = Arc::clone(&data);11 let handle = tokio::spawn(async move {12 let mut num = data.lock().await;13 *num += 1;14 });15 handles.push(handle);16 }1718 for handle in handles {19 handle.await.unwrap();20 }2122 println!("Result: {}", *data.lock().await);23}
Обработка ошибок
1use tokio;23#[derive(Debug)]4enum AppError {5 NetworkError,6 ParseError,7}89async fn fetch_data() -> Result<String, AppError> {10 let response = reqwest::get("https://api.example.com")11 .await12 .map_err(|_| AppError::NetworkError)?;1314 let body = response.text().await15 .map_err(|_| AppError::ParseError)?;1617 Ok(body)18}1920#[tokio::main]21async fn main() {22 match fetch_data().await {23 Ok(data) => println!("Data: {}", data),24 Err(e) => println!("Error: {:?}", e),25 }26}
Best Practices
▸Избегайте блокирующего кода
1// Плохо: блокирует runtime2async fn bad() {3 std::thread::sleep(Duration::from_secs(1)); // Блокирует весь поток4}56// Хорошо: использует таймер Tokio7async fn good() {8 tokio::time::sleep(Duration::from_secs(1)).await;9}
▸Spawn для долгих операций
1tokio::spawn(async {2 // Долгая операция, не блокируя основной поток3 expensive_computation().await;4});
Заключение
async/await в Rust — мощный инструмент для создания высокопроизводительных приложений. Tokio предоставляет весь необходимый runtime. Понимание futures, streams и каналов критически важно для backend-разработки. На собеседовании спрашивают про разницу async vs threading, как работает Tokio, и типичные ошибки.