Pin

Async blocks and functions return types implementing the Future trait. The type returned is the result of a compiler transformation which turns local variables into data stored inside the future.

Some of those variables can hold pointers to other local variables. Because of that, the future should never be moved to a different memory location, as it would invalidate those pointers.

To prevent moving the future type in memory, it can only be polled through a pinned pointer. Pin is a wrapper around a reference that disallows all operations that would move the instance it points to into a different memory location.

use tokio::sync::{mpsc, oneshot};
use tokio::task::spawn;
use tokio::time::{sleep, Duration};

// Um item de trabalho. Neste caso, apenas dormir pelo tempo dado e responder com uma mensagem no canal `respond_on`.
#[derive(Debug)]
struct Work {
    input: u32,
    respond_on: oneshot::Sender<u32>,
}

// Um trabalhador que escuta o trabalho em uma fila e o executa.
async fn worker(mut work_queue: mpsc::Receiver<Work>) {
    let mut iterations = 0;
    loop {
        tokio::select! {
            Some(work) = work_queue.recv() => {
                sleep(Duration::from_millis(10)).await; // Fingir trabalhar.
                work.respond_on
                    .send(work.input * 1000)
                    .expect("falha ao enviar resposta");
                iterations += 1;
            }
            // TODO: relatar o nĂșmero de iteraçÔes a cada 100ms
        }
    }
}

// Um solicitante que solicita trabalho e aguarda sua conclusĂŁo.
async fn do_work(work_queue: &mpsc::Sender<Work>, input: u32) -> u32 {
    let (tx, rx) = oneshot::channel();
    work_queue
        .send(Work { input, respond_on: tx })
        .await
        .expect("falha ao enviar na fila de trabalho");
    rx.await.expect("falha ao esperar pela resposta")
}

#[tokio::main]
async fn main() {
    let (tx, rx) = mpsc::channel(10);
    spawn(worker(rx));
    for i in 0..100 {
        let resp = do_work(&tx, i).await;
        println!("resultado do trabalho para a iteração {i}: {resp}");
    }
}
  • VocĂȘ pode reconhecer isso como um exemplo do padrĂŁo actor. Os actors tipicamente chamam select! em um loop.

  • Isso serve como uma sĂ­ntese de algumas das liçÔes anteriores, entĂŁo leve seu tempo com isso.

    • Adicione ingenuamente um _ = sleep(Duration::from_millis(100)) => { println!(..) } ao select!. Isso nunca serĂĄ executado. Por quĂȘ?

    • Em vez disso, adicione um timeout_fut contendo essa future fora do loop:

      #![allow(unused)]
      fn main() {
      let mut timeout_fut = sleep(Duration::from_millis(100));
      loop {
          select! {
              ..,
              _ = timeout_fut => { println!(..); },
          }
      }
      }
    • Isso ainda nĂŁo funciona. Siga os erros do compilador, adicionando &mut ao timeout_fut no select! para contornar a movimentação, e entĂŁo usando Box::pin.

      #![allow(unused)]
      fn main() {
      let mut timeout_fut = Box::pin(sleep(Duration::from_millis(100)));
      loop {
          select! {
              ..,
              _ = &mut timeout_fut => { println!(..); },
          }
      }
      }
    • Isso compila, mas uma vez que o timeout expira, ele Ă© Poll::Ready em cada iteração (uma fused future ajudaria com isso). Atualize para redefinir timeout_fut toda vez que expirar.

  • O Box aloca na pilha. Em alguns casos, std::pin::pin! (apenas recentemente estabilizado, com cĂłdigo mais antigo frequentemente usando tokio::pin!) tambĂ©m Ă© uma opção, mas Ă© difĂ­cil de usar para uma future que Ă© reatribuĂ­da.

  • Outra alternativa Ă© nĂŁo usar pin de forma alguma, mas iniciar outra tarefa que enviarĂĄ para um canal oneshot a cada 100ms.

  • Data that contains pointers to itself is called self-referential. Normally, the Rust borrow checker would prevent self-referential data from being moved, as the references cannot outlive the data they point to. However, the code transformation for async blocks and functions is not verified by the borrow checker.

  • Pin is a wrapper around a reference. An object cannot be moved from its place using a pinned pointer. However, it can still be moved through an unpinned pointer.

  • The poll method of the Future trait uses Pin<&mut Self> instead of &mut Self to refer to the instance. That’s why it can only be called on a pinned pointer.