0

I am trying to get an async closure working in the and_then filter from Warp.

This is the smallest example I could come up with where I am reasonably sure I didn't leave any important details out:

use std::{convert::Infallible, sync::Arc, thread, time};
use tokio::sync::RwLock;
use warp::Filter;

fn main() {
    let man = Manifest::new();

    let check = warp::path("updates").and_then(|| async move { GetAvailableBinaries(&man).await });
}

async fn GetAvailableBinaries(man: &Manifest) -> Result<impl warp::Reply, Infallible> {
    Ok(warp::reply::json(&man.GetAvailableBinaries().await))
}

pub struct Manifest {
    binaries: Arc<RwLock<Vec<i32>>>,
}

impl Manifest {
    pub fn new() -> Manifest {
        let bins = Arc::new(RwLock::new(Vec::new()));

        thread::spawn(move || async move {
            loop {
                thread::sleep(time::Duration::from_millis(10000));
            }
        });

        Manifest { binaries: bins }
    }

    pub async fn GetAvailableBinaries(&self) -> Vec<i32> {
        self.binaries.read().await.to_vec()
    }
}

I am using:

[dependencies]
tokio = { version = "0.2", features = ["full"] }
warp = { version = "0.2", features = ["tls"] }

The error is:

error[E0525]: expected a closure that implements the `Fn` trait, but this closure only implements `FnOnce`
 --> src/main.rs:9:48
  |
9 |     let check = warp::path("updates").and_then(|| async move { GetAvailableBinaries(&man).await });
  |                                       -------- ^^^^^^^^^^^^^ ------------------------------------ closure is `FnOnce` because it moves the variable `man` out of its environment
  |                                       |        |
  |                                       |        this closure implements `FnOnce`, not `Fn`
  |                                       the requirement to implement `Fn` derives from here
Matthew Goulart
  • 2,873
  • 4
  • 28
  • 63
  • By the way, idiomatic Rust uses `snake_case` for variables, methods, macros, fields and modules; `UpperCamelCase` for types and enum variants; and `SCREAMING_SNAKE_CASE` for statics and constants. – Shepmaster Oct 01 '20 at 01:27
  • The code in the question does not implement `Clone` for manifest, but you've removed that error. – Shepmaster Oct 02 '20 at 02:21

2 Answers2

0

I'm not sure this is what you're going for, but this solution builds for me:

use std::{convert::Infallible, sync::Arc, thread, time};
use tokio::sync::RwLock;
use warp::Filter;

fn main() {
    let man = Manifest::new();

    let check = warp::path("updates").and_then(|| async { GetAvailableBinaries(&man).await });
}

async fn GetAvailableBinaries(man: &Manifest) -> Result<impl warp::Reply, Infallible> {
    Ok(warp::reply::json(&man.GetAvailableBinaries().await))
}

#[derive(Clone)]
pub struct Manifest {
    binaries: Arc<RwLock<Vec<i32>>>,
}

impl Manifest {
    pub fn new() -> Manifest {
        let bins = Arc::new(RwLock::new(Vec::new()));

        thread::spawn(move || async {
            loop {
                thread::sleep(time::Duration::from_millis(10000));
                //mutate bins here
            }
        });

        Manifest { binaries: bins }
    }

    pub async fn GetAvailableBinaries(&self) -> Vec<i32> {
        self.binaries.read().await.to_vec()
    }
}

The move here is the reason the compiler gave a warning regarding the signature: let check = warp::path("updates").and_then(|| async move { GetAvailableBinaries(&man).await });. This means that everything referenced in this closure will be moved into the context of the closure. In this case, the compiler can't guarantee the closure to be Fn but only FnOnce meaning that the closure can only be guaranteed to execute once.

Matthew Goulart
  • 2,873
  • 4
  • 28
  • 63
AdaShoelace
  • 342
  • 1
  • 14
  • Well this technically allows the code to compile, it doesn’t actually solve the real problem. The real code will most likely have a call to `warp::serve(check)` to start the webserver. That call will impose additional restrictions on `check` – Shepmaster Oct 01 '20 at 11:10
0

After making Manifest implement Clone, you can fix the error by balancing when the manifest object is cloned:

fn main() {
    let man = Manifest::new();

    let check = warp::path("updates").and_then(move || {
        let man = man.clone();
        async move { get_available_binaries(&man).await }
    });

    warp::serve(check);
}

This moves man into the closure passed to and_then, then provides a clone of man to the async block each time the closure is executed. The async block then owns that data and can take a reference to it without worrying about executing the future after the data has been deallocated.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • Unfortunately this doesn't work. This causes `use of moved value: bins` on the last line of the `impl Manifest` block – Matthew Goulart Oct 03 '20 at 00:11
  • @MatthewGoulart there is no usage of `bins` in `GetAvailableBinaries`, which is the end of the impl block. – Shepmaster Oct 03 '20 at 00:15
  • Sorry, the error occurs on the last line of the `new()` fn of the `impl Manifest` block – Matthew Goulart Oct 03 '20 at 00:33
  • I see now, it's because in my real code the thread spawned in `new` *does* use `bins`. This is mostly why I was hesitant to remove much from my original question, I don't know rust well enough to know what is important and what isn't especially when I don't know why an error is occurring. – Matthew Goulart Oct 03 '20 at 00:36
  • @MatthewGoulart you are not extrapolating from the answers you've received. You need to clone `bins` before passing it to the spawned thread. It's the same problem in a different location. – Shepmaster Oct 04 '20 at 17:17