|
| 1 | +# Pin |
| 2 | + |
| 3 | +When you await a future, you effectively move the whole stack frame from which you called `.await` to an internal data structure of your executor. If your future has pointers to data on the stack, the addresses might get invalidated. This is extremely unsafe. Therefore, you want to guarantee that the addresses your future point to don't change. That is why we need to `pin` futures. In most cases, you won't have to think about it when using futures from common libraries unless you use `select` in a loop (which is a pretty common use case). If, you implement your own future, you will likely run into this issue. |
| 4 | + |
| 5 | +```rust,editable,compile_fail |
| 6 | +use tokio::sync::mpsc::{self, Receiver}; |
| 7 | +use tokio::time::{sleep, Duration}; |
| 8 | +
|
| 9 | +#[derive(Debug, PartialEq)] |
| 10 | +struct Runner { |
| 11 | + name: String, |
| 12 | +} |
| 13 | +
|
| 14 | +async fn race_finish_line(mut rcv: Receiver<String>, timeout: Duration) -> Option<Vec<Runner>> { |
| 15 | + let mut performances: Vec<Runner> = Vec::new(); |
| 16 | + let timeout_sleep = sleep(timeout); |
| 17 | + // Pinning here allows us to await `timeout_sleep` multiple times. |
| 18 | + tokio::pin!(timeout_sleep); |
| 19 | +
|
| 20 | + loop { |
| 21 | + tokio::select! { |
| 22 | + // Rcv.recv() returns a new future every time, hence it does not need to be pinned. |
| 23 | + name = rcv.recv() => performances.push(Runner { name: name? }), |
| 24 | + _ = timeout_sleep.as_mut() => break |
| 25 | + } |
| 26 | + } |
| 27 | + Some(performances) |
| 28 | +} |
| 29 | +
|
| 30 | +#[tokio::main] |
| 31 | +async fn main() { |
| 32 | + let (sender, receiver) = mpsc::channel(32); |
| 33 | +
|
| 34 | + let names_and_time = [ |
| 35 | + ("Leo", 9),("Milo", 3),("Luna", 13),("Oliver", 5),("Charlie", 11), |
| 36 | + ]; |
| 37 | +
|
| 38 | + let finish_line_future = race_finish_line(receiver, Duration::from_secs(6)); |
| 39 | +
|
| 40 | + for (name, duration_secs) in names_and_time { |
| 41 | + let sender = sender.clone(); |
| 42 | + tokio::spawn(async move { |
| 43 | + sleep(Duration::from_secs(duration_secs)).await; |
| 44 | + sender.send(String::from(name)).await.expect("Failed to send runner"); |
| 45 | + }); |
| 46 | + } |
| 47 | +
|
| 48 | + println!("{:?}", finish_line_future.await.expect("Failed to collect finish line")); |
| 49 | + // [Runner { name: "Milo" }, Runner { name: "Oliver" }] |
| 50 | +} |
| 51 | +``` |
| 52 | + |
| 53 | + |
| 54 | +<details> |
| 55 | + |
| 56 | +* `tokio::pin!` only works on futures that implement `Unpin`. Other futures need to use `box::pin`. |
| 57 | +* Another alternative is to not use `tokio::pin!` at all but spawn another task that will send to a `oneshot` channel after the end of the `sleep` call. |
| 58 | + |
| 59 | +```rust,editable,compile_fail |
| 60 | +use tokio::sync::mpsc::{self, Receiver}; |
| 61 | +use tokio::time::{sleep, Duration}; |
| 62 | +use tokio::sync::oneshot; |
| 63 | +
|
| 64 | +#[derive(Debug, PartialEq)] |
| 65 | +struct Runner { |
| 66 | + name: String, |
| 67 | +} |
| 68 | +
|
| 69 | +async fn race_finish_line(mut rcv: Receiver<String>, mut timeout: oneshot::Receiver<()>) -> Option<Vec<Runner>> { |
| 70 | + let mut performances: Vec<Runner> = Vec::new(); |
| 71 | + loop { |
| 72 | + tokio::select! { |
| 73 | + name = rcv.recv() => performances.push(Runner { name: name? }), |
| 74 | + _ = &mut timeout => break |
| 75 | + } |
| 76 | + } |
| 77 | + Some(performances) |
| 78 | +} |
| 79 | +
|
| 80 | +#[tokio::main] |
| 81 | +async fn main() { |
| 82 | + let (sender, receiver) = mpsc::channel(32); |
| 83 | + let (os_sender, os_receiver) = oneshot::channel(); |
| 84 | +
|
| 85 | + let names_and_time = [ |
| 86 | + ("Leo", 9),("Milo", 3),("Luna", 13),("Oliver", 5),("Charlie", 11), |
| 87 | + ]; |
| 88 | +
|
| 89 | + tokio::spawn(async move { |
| 90 | + sleep(Duration::from_secs(5)).await; |
| 91 | + os_sender.send(()).expect("Failed to send oneshot."); |
| 92 | + }); |
| 93 | + let finish_line_future = race_finish_line(receiver, os_receiver); |
| 94 | +
|
| 95 | + for (name, duration_secs) in names_and_time { |
| 96 | + let sender = sender.clone(); |
| 97 | + tokio::spawn(async move { |
| 98 | + sleep(Duration::from_secs(duration_secs)).await; |
| 99 | + sender.send(String::from(name)).await.expect("Failed to send runner"); |
| 100 | + }); |
| 101 | + } |
| 102 | +
|
| 103 | + println!("{:?}", finish_line_future.await.expect("Failed to collect finish line")); |
| 104 | + // [Runner { name: "Milo" }, Runner { name: "Oliver" }] |
| 105 | +} |
| 106 | +``` |
| 107 | + |
| 108 | +</details> |
0 commit comments