如何将生命周期设置为闭包中捕获的值?

How to set a lifetime to a value captured in a closure?

我写了我认为简单的代码:

#![feature(plugin)]
#![plugin(rocket_codegen)]

extern crate rocket;
extern crate statsd;

use rocket::{Data, Request};
use rocket::fairing::AdHoc;
use statsd::Client;

#[get("/")]
fn index() -> &'static str {
    "Hello, World"
}

fn main() {
    let mut client = Client::new("127.0.0.1:9125", "miniserver-rs").unwrap();

    rocket::ignite()
        .attach(AdHoc::on_request(|request, data|{
            client.incr("http.requests");
            println!("Request URI: {}", request.uri());
        }))
       .mount("/", routes![index])
       .launch();
   client.incr("server.bootstrap");
}

我尝试在每个请求上发送一些指标,但我收到以下编译器错误:

   Compiling miniserver-rs v0.1.0 (main.rs)
error[E0373]: closure may outlive the current function, but it borrows `client`, which is owned by the current function
  --> src\main.rs:19:33
   |
19 |       .attach(AdHoc::on_request(|request, _data|{
   |                                 ^^^^^^^^^^^^^^^^ may outlive borrowed value `client`
20 |           client.incr("http.requests");
   |           ------ `client` is borrowed here help: to force the closure to take ownership of `client` (and any other referenced variables), use the `move` keyword
   |
19 |       .attach(AdHoc::on_request(move |request, _data|{
   |                                 ^^^^^^^^^^^^^^^^^^^^^

error[E0387]: cannot borrow data mutably in a captured outer variable in an `Fn` closure
  --> src\main.rs:20:11
   |
20 |           client.incr("http.requests");
   |           ^^^^^^
   |
help: consider changing this closure to take self by mutable reference
  --> src\main.rs:19:33
   |
19 |         .attach(AdHoc::on_request(|request, _data|{
   |  _________________________________^
20 | |           client.incr("http.requests");
21 | |           println!("Request URI: {}", request.uri());
22 | |       }))
   | |_______^

我知道 client 被捕获在一个闭包中并由另一个函数 (main) 拥有,该函数可能比闭包存活时间短。我不能 move 因为 Client 没有实现 Copy,所以以后不能使用引用。

我也明白我不能在闭包中借用可变数据(Client 是可变的)。经过大量搜索,我可以得出结论,我需要将 Arc/RcMutex/RwLock/RefCell 结合使用,但在继续之前,我想确定它是必需的。

让我们看看要求。你想从闭包内部调用 statsd::Client::incr(&mut client, metric),所以你需要对 client 进行可变访问。这是您用 ||.

关闭的变量

现在 AdHoc::on_request<F>(f: F) 需要 F: Fn(...) + Send + Sync + 'staticFn 意味着您只能通过 &self 不可变地访问您的捕获。 'static 绑定意味着捕获本身不能是引用,因此它需要 move ||。最后 Sync 意味着您不能使用 CellRefCell&self.client 获取可变引用,因为 Rocket 将在线程之间共享它。

正如您所怀疑的,通过 Send + Sync 值共享可变访问的规范解决方案是使用 Arc<Mutex<_>>。这也解决了"losing access by moving"的问题。您的代码如下所示(未经测试):

fn main() {
    let client = Arc::new(Mutex::new(
        Client::new("127.0.0.1:9125", "miniserver-rs").unwrap()));

    // shallow-clone the Arc to move it into closure
    let rocket_client = client.clone();
    rocket::ignite()
        .attach(AdHoc::on_request(move |request, data|{
            rocket_client.lock()
                .unwrap()
                .incr("http.requests");

            println!("Request URI: {}", request.uri());
        }))
       .mount("/", routes![index])
       .launch();

   client.lock()
       .unwrap()
       .incr("server.bootstrap");
}