🚧 Attention, peinture fraîche !

Cette page a été traduite par une seule personne et n'a pas été relue et vérifiée par quelqu'un d'autre ! Les informations peuvent par exemple être erronées, être formulées maladroitement, ou contenir d'autres types de fautes.

L'approximation de Send

Certaines machines à état de fonctions asynchrones sont sûres pour être envoyées entre des processus, alors que d'autres ne le sont pas. Le fait que la Future d'une fonction asynchrone est Send ou non est conditionné par le fait qu'un type Send soit maintenu par un .await, mais cette approche est aujourd'hui trop conservatrice sur certains points.

Par exemple, imaginez un simple type qui n'est pas Send, comme un type qui contient un Rc :


#![allow(unused)]
fn main() {
use std::rc::Rc;

#[derive(Default)]
struct EstPasSend(Rc<()>);
}

Les variables du type EstPasSend peuvent intervenir brièvement dans des fonctions asynchrones même si le type résultant de la Future retournée par la fonction asynchrone doit être Send :

use std::rc::Rc;
#[derive(Default)]
struct EstPasSend(Rc<()>);
async fn beta() {}
async fn alpha() {
    EstPasSend::default();
    beta().await;
}

fn necessite_send(_: impl Send) {}

fn main() {
    necessite_send(alpha());
}

Cependant, si nous changeons alpha pour stocker le EstPasSend dans une variable, cet exemple ne se compile plus :

use std::rc::Rc;
#[derive(Default)]
struct EstPasSend(Rc<()>);
async fn beta() {}
async fn alpha() {
    let x = EstPasSend::default();
    beta().await;
}
fn necessite_send(_: impl Send) {}
fn main() {
   necessite_send(alpha());
}
error[E0277]: `std::rc::Rc<()>` cannot be sent between threads safely
  -- > src/main.rs:15:5
   |
15 |     necessite_send(foo());
   |     ^^^^^^^^^^^^^^ `std::rc::Rc<()>` cannot be sent between threads safely
   |
   = help: within `impl std::future::Future`, the trait `std::marker::Send` is not implemented for `std::rc::Rc<()>`
   = note: required because it appears within the type `EstPasSend`
   = note: required because it appears within the type `{EstPasSend, impl std::future::Future, ()}`
   = note: required because it appears within the type `[static generator@src/main.rs:7:16: 10:2 {EstPasSend, impl std::future::Future, ()}]`
   = note: required because it appears within the type `std::future::GenFuture<[static generator@src/main.rs:7:16: 10:2 {EstPasSend, impl std::future::Future, ()}]>`
   = note: required because it appears within the type `impl std::future::Future`
   = note: required because it appears within the type `impl std::future::Future`
note: required by `necessite_send`
  -- > src/main.rs:12:1
   |
12 | fn necessite_send(_: impl Send) {}
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error: aborting due to previous error

For more information about this error, try `rustc --explain E0277`.

Cette erreur est justifiée. Si nous stockons x dans une variable, il ne sera pas libéré avant d'arriver après le .await, moment où la fonction asynchrone s'exécute peut-être sur un processus différent. Comme Rc n'est pas Send, lui permettre de voyager entre les processus ne serait pas sain. Une solution simple à cela serait de libérer le Rc avec drop avant le .await, mais malheureusement cela ne fonctionne pas aujourd'hui.

Pour contourner ce problème, vous pouvez créer une portée de bloc qui englobe chacune des variables qui ne sont pas Send. Cela permet de dire facilement au compilateur que ces variables ne vivent plus en dehors de l'utilisation du .await.

use std::rc::Rc;
#[derive(Default)]
struct EstPasSend(Rc<()>);
async fn beta() {}
async fn alpha() {
    {
        let x = EstPasSend::default();
    }
    beta().await;
}
fn necessite_send(_: impl Send) {}
fn main() {
   necessite_send(alpha());
}