Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Макрос pin

#![allow(unused)]
fn main() {
macro_rules! pin {
    ($($x:ident),*) => { ... };
    ($(
            let $x:ident = $init:expr;
    )*) => { ... };
}
}

Закрепляет значение в стеке.

Описание

Вызовы async fn возвращают анонимные значения Future, которые являются !Unpin. Эти значения должны быть закреплены до того, как их можно будет опросить. Вызов .await обработает это, но потребляет future. Если требуется вызвать .await на ссылке &mut _, вызывающая сторона отвечает за закрепление future.

Закрепление может быть выполнено путем выделения памяти с помощью Box::pin или использования стека с помощью макроса pin!.

Следующий код не скомпилируется: ⓘ

async fn my_async_fn() {
    // асинхронная логика здесь
}

#[tokio::main]
async fn main() {
    let mut future = my_async_fn();
    (&mut future).await; // Ошибка компиляции!
}

Чтобы это работало, требуется закрепление:

#![allow(unused)]
fn main() {
use tokio::pin;

async fn my_async_fn() {
    // асинхронная логика здесь
}

let future = my_async_fn();
pin!(future);

(&mut future).await; // Теперь работает
}

Закрепление полезно при использовании select! и операторов потоков, которые требуют T: Stream + Unpin.

Использование

Макрос pin! принимает идентификаторы в качестве аргументов. Он не работает с выражениями.

Следующее не компилируется, так как выражение передается в pin!: ⓘ

async fn my_async_fn() {
    // асинхронная логика здесь
}

#[tokio::main]
async fn main() {
    let mut future = pin!(my_async_fn()); // Ошибка: ожидается идентификатор
    (&mut future).await;
}

Примеры

Использование с select!

#![allow(unused)]
fn main() {
use tokio::{pin, select};
use tokio_stream::{self as stream, StreamExt};

async fn my_async_fn() {
    // асинхронная логика здесь
}

let mut stream = stream::iter(vec![1, 2, 3, 4]);

let future = my_async_fn();
pin!(future);

loop {
    select! {
        _ = &mut future => {
            // Прекращаем цикл после завершения future
            break;
        }
        Some(val) = stream.next() => {
            println!("получено значение = {}", val);
        }
    }
}
}

Одновременное объявление переменной и закрепление

Поскольку присваивание переменной с последующим закреплением является распространенной операцией, существует вариант макроса, который поддерживает выполнение обоих действий за один раз.

#![allow(unused)]
fn main() {
use tokio::{pin, select};

async fn my_async_fn() {
    // асинхронная логика здесь
}

pin! {
    let future1 = my_async_fn();
    let future2 = my_async_fn();
}

select! {
    _ = &mut future1 => {}
    _ = &mut future2 => {}
}
}

Закрепление нескольких значений

#![allow(unused)]
fn main() {
use tokio::pin;

async fn task_one() -> i32 { 42 }
async fn task_two() -> &'static str { "hello" }

let fut1 = task_one();
let fut2 = task_two();

pin!(fut1, fut2);

// Теперь можно использовать &mut fut1 и &mut fut2 с .await
}

Практический пример с обработкой ошибок

use tokio::{pin, select};
use std::time::Duration;

async fn network_request() -> Result<String, &'static str> {
    tokio::time::sleep(Duration::from_secs(1)).await;
    Ok("данные получены".to_string())
}

async fn timeout() -> Result<(), &'static str> {
    tokio::time::sleep(Duration::from_secs(2)).await;
    Err("таймаут")
}

#[tokio::main]
async fn main() {
    let request_future = network_request();
    let timeout_future = timeout();
    
    pin! {
        let request = request_future;
        let timeout = timeout_future;
    }
    
    select! {
        result = &mut request => {
            match result {
                Ok(data) => println!("Успех: {}", data),
                Err(e) => println!("Ошибка запроса: {}", e),
            }
        }
        _ = &mut timeout => {
            println!("Превышено время ожидания запроса");
        }
    }
}

Примечания

  • Закрепление в стеке более эффективно, чем использование Box::pin, так как избегает выделения памяти в куче
  • Закрепленные значения могут быть безопасно разыменованы для получения &mut T только через специальные интерфейсы
  • Макрос pin! гарантирует, что закрепленное значение не может быть перемещено после закрепления