Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does the introduction of a variable cause the returned Future to be Send?

Here is the code where I initially get an error:

async fn listen() -> std::io::Result<()> {
        HttpServer::new(|| App::new().service(hello))
        .bind(("0.0.0.0", 8888))?
        .run()
        .await
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    let listen_handler = tokio::spawn(listen());
    tokio::select! {
        _ = listen_handler => {},
    }
    Ok(())
}

and it fails to compile:

error: future cannot be sent between threads safely
   --> src/main.rs:98:39
    |
98  |     let listen_handler = tokio::spawn(listen());
    |                                       ^^^^^^^^ future returned by `listen` is not `Send`
    |
    = help: within `impl std::future::Future<Output = Result<(), std::io::Error>>`, the trait `Send` is not implemented for `Rc<[Box<(dyn Fn() -> Pin<Box<(dyn std::future::Future<Output = Result<Box<(dyn actix_web::data::DataFactory + 'static)>, ()>> + 'static)>> + 'static)>]>`, which is required by `impl std::future::Future<Output = Result<(), std::io::Error>>: Send`
note: future is not `Send` as this value is used across an await
   --> src/main.rs:92:10
    |
89  | /     HttpServer::new(|| App::new().service(hello))
90  | |         .bind(("0.0.0.0", 8888))?
    | |_________________________________- has type `ControlFlow<Result<Infallible, std::io::Error>, HttpServer<{closure@src/main.rs:89:21: 89:23}, App<actix_web::app_service::AppEntry>, actix_web::app_service::AppInit<actix_web::app_service::AppEntry, BoxBody>, BoxBody>>` which is not `Send`
91  |           .run()
92  |           .await
    |            ^^^^^ await occurs here, with the value maybe used later
note: required by a bound in `tokio::spawn`
   --> /home/liuzehao/.cargo/registry/src/index.crates.io-6f17d22bba15001f/tokio-1.42.0/src/task/spawn.rs:168:21
    |
166 |     pub fn spawn<F>(future: F) -> JoinHandle<F::Output>
    |            ----- required by a bound in this function
167 |     where
168 |         F: Future + Send + 'static,
    |                     ^^^^ required by this bound in `spawn`

When I introduce a variable for the result of run:

async fn listen() -> std::io::Result<()> {
    let server = HttpServer::new(|| App::new().service(hello))
        .bind(("0.0.0.0", 8888))?
        .run();
    server.await
}

It just works!

Why does the second version compile when the first one doesn't?

like image 464
btrack Avatar asked Nov 22 '25 21:11

btrack


1 Answers

This is due to due how the compiler handles the Drop Scope of Temporaries, in this case especially due to the presence Question Mark Operator.

In general, the compiler needs to handle temporary values that are commonly created in complex expressions like foo.iter().map(...).fold(...).count(): In this example, the values returned by .iter(), .map() and .fold() need to live somewhere, and get dropped at some point.
There are rules that govern how the Drop Scope of those temporary values should be chosen by the compiler. On one hand, we simply want "stuff to work"; that is, the Drop Scope for temporary values should be large enough so temporary values live long enough to be practical in most situations. On the other hand, we don't want temporaries to live too long, as either the execution of the destructor affects the program, or we run into borrow-checker problems. There is no clear technical reason why Drop Scopes have to be the way they are, except practicability. However, one might say the rules in place are too successful, because most programmers seldomly even notice that they exist.

In your example, the Question Mark Operator on the result of .bind() will desugar the expression up to that point into a match HttpServer::new().bind() { Ok(o) => ..., Err(e) => return ... }. That means that the value returned from .bind() is in a place context, as it appears as the scrutinee of a match, which is defined as such.

Up until now, we've established that the temporary returned from .bind() is a value expression in a place context. For temporaries in a place context, Rust defines

The temporary scope of an expression is the scope that is used for the temporary variable that holds the result of that expression when used in a place context [...] The temporary scope of an expression is the smallest scope that contains the expression and is one of the following

See the reference for a list of scopes that are applicable. You'll notice that for the temporary value returned from .bind(), the smallest available Drop Scope which that temporary could have in your first example is the entire function (because the entire function is just one big expression). That means that the value returned from .bind() has to be able to live until the very end of the expression, which includes the .await. Yet this can't be, because while the temporary could potentially live as long as that, in practice it would mean lifting a Non-Send across an .await, which is not allowed here.

If you split the function into separate statements, like in your second example, a different (smaller) Drop Scope applies: The second candidate in the list of possible scopes is the statement itself, which would be the let server = HttpServer::new().bind()?.run();. Here, the temporary returned from .bind() lives only as long as that one line, so it can never reach across the .await, and the problem is averted.

like image 86
user2722968 Avatar answered Nov 24 '25 16:11

user2722968



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!