Search code examples
rustclosuresselflifetimejson-rpc

Adding a closure within a '&self' method to an attribute in a struct


Consider the following example code:

#[macro_use]
extern crate serde_derive;
extern crate serde;
extern crate serde_json;

extern crate jsonrpc_core as rpc;

#[derive(Serialize, Deserialize)]
struct Test {
    var: u32,
}

struct TestRpc {
    test: Test,
    rpc_io_handler: rpc::IoHandler,
}

impl TestRpc {
    fn new() -> Self {
        let ret = Self {
            test: Test { var: 1 },
            rpc_io_handler: rpc::IoHandler::new(),
        };
        ret.register_rpc_methods();
        ret
    }

    fn register_rpc_methods(&self) {
        let get_var = |_params: rpc::Params| match rpc::to_value(&self.test) {
            Ok(x) => Ok(x),
            Err(_) => Err(rpc::Error::internal_error()),
        };
        self.rpc_io_handler.add_method("get_var", get_var);
    }

    fn get_var_test(&self, msg: &str) -> Option<String> {
        self.rpc_io_handler.handle_request_sync(msg)
    }
}

fn main() {
    let test = TestRpc::new();
    let request = r#"{"jsonrpc": "2.0", "method": "get_var", "id": 1}"#;
    let response = r#"{"jsonrpc":"2.0","result":{"var":1},"id":1}"#;
    assert_eq!(test.get_var_test(request), Some(response.to_owned()));
}

with the following method signature for 'rpc::IoHandler::add_method'

pub fn add_method<F>(&mut self, name: &str, method: F)
where
    F: RpcMethodSimple,

The method is from jsonrpc as is RpcMethodSimple.

I get the following error when I try to compile this

error[E0495]: cannot infer an appropriate lifetime due to conflicting requirements
  --> src/main.rs:26:27
   |
26 |           let mut get_var = |_params: rpc::Params | {
   |  ___________________________^
27 | |             match rpc::to_value(&self.test) {
28 | |                 Ok(x) => Ok(x),
29 | |                 Err(_) => Err(rpc::Error::internal_error())
30 | |             }
31 | |         };
   | |_________^
   |
note: first, the lifetime cannot outlive the anonymous lifetime #1 defined on the method body at 25:5...
  --> src/main.rs:25:5
   |
25 | /     fn register_rpc_methods(&self) {
26 | |         let mut get_var = |_params: rpc::Params | {
27 | |             match rpc::to_value(&self.test) {
28 | |                 Ok(x) => Ok(x),
...  |
32 | |         self.rpc_io_handler.add_method("get_var", get_var);
33 | |     }
   | |_____^
   = note: ...so that the types are compatible:
           expected &&TestRpc
              found &&TestRpc
   = note: but, the lifetime must be valid for the static lifetime...
note: ...so that the type `[closure@src/main.rs:26:27: 31:10 self:&&TestRpc]` will meet its required lifetime bounds
  --> src/main.rs:32:29
   |
32 |         self.rpc_io_handler.add_method("get_var", get_var);
   |                             ^^^^^^^^^^

Is it possible to use this method (rpc::IoHandler::add_method) without changing the method in the crate? I'm struggling with lifetimes in Rust; is there a simple way to restrict the lifetime of the closure?


Solution

  • I'm not too familiar with jsonrpc's internals, but the jsonrpc library is implemented in a completely asynchronous way with Tokio. Though you call synchronous request handling, internally it still performs the request asynchronously and simply blocks your thread until it's done. The downside of this is that Tokio cannot guarantee anything about scheduling your closures in a task executor. Thus any such closure's lifetime is tied more to the executor than any self.

    In the code above, you capture a reference to self, but there's no guarantee that self still lives when the closure is executed. Hence you have to move any data the closure uses. Additionally, the closure must be Send to be used with Tokio, so you cannot simply use Rc and move a copy into the closure.

    In your case, the easiest way I know of would be to change test to type Arc<Test>. Then change the closure definition to move a copy of the variable into the closure. You also had some mutability problems, here's a complete example that compiles:

    #[macro_use]
    extern crate serde_derive;
    extern crate serde;
    extern crate serde_json;
    
    extern crate jsonrpc_core as rpc;
    
    use std::borrow::Borrow;
    use std::sync::Arc;
    
    #[derive(Serialize, Deserialize)]
    struct Test {
        var: u32,
    }
    
    struct TestRpc {
        test: Arc<Test>,
        rpc_io_handler: rpc::IoHandler,
    }
    
    impl TestRpc {
        fn new() -> Self {
            let mut ret = Self {
                test: Arc::new(Test { var: 1 }),
                rpc_io_handler: rpc::IoHandler::new(),
            };
            ret.register_rpc_methods();
            ret
        }
    
        fn register_rpc_methods(&mut self) {
            let test_clone = self.test.clone();
            let get_var = move |_params: rpc::Params| match rpc::to_value(test_clone.borrow() as &Test)
            {
                Ok(x) => Ok(x),
                Err(_) => Err(rpc::Error::internal_error()),
            };
            self.rpc_io_handler.add_method("get_var", get_var);
        }
    
        fn get_var_test(&self, msg: &str) -> Option<String> {
            self.rpc_io_handler.handle_request_sync(msg)
        }
    }
    
    fn main() {
        let test = TestRpc::new();
        let request = r#"{"jsonrpc": "2.0", "method": "get_var", "id": 1}"#;
        let response = r#"{"jsonrpc":"2.0","result":{"var":1},"id":1}"#;
        assert_eq!(test.get_var_test(request), Some(response.to_owned()));
    }