[solved]Need Help with using closure that are passed onto another function


#1

Hello,

Thank You for taking time to help.

I am new to rust. I am learning. Therefore I choose to implement a small library that can be of use.

Code for this learning project is here https://github.com/huskercane/softayer-messaging-rust.

I was able to meet my initial goal of reading and sending messages.

Now I am trying to code cleaner and reuse common code.
For most of the function I have to do following 3 things.

  1. call a http url with set header
  2. return error if not proper return status code
  3. process response

I was think that I can implement step 3 as a closure that I can pass in to function that does rest of http transaction.

Here is the file implementing above
https://raw.githubusercontent.com/huskercane/softayer-messaging-rust/splitfiles/src/slclient.rs

Here is my function that does http round trip
pub fn http_operation(&mut self,
url: String,
oper: Operation,
closure: Option)
-> Result<bool, String>
where F: FnMut(hyper::client::Response, hyper::header::Headers, String)
{

and then here is the code that executes a closure if not None, on proper return code
match res.status {
hyper::Ok |
hyper::status::StatusCode::Created => {
match closure {
None => Ok(true),
Some(clo) => {
clo(res, headers, buffer);
Ok(true)
}
}
}
hyper::status::StatusCode::Unauthorized => {
Err(format!("{} - Unauthorized", res.status))
}
hyper::status::StatusCode::ServiceUnavailable => {
Err(format!("{} - Unauthorized", res.status))
}
_ => Err(format!(“Invalid http return code: {}!”, res.status)),
}
}

The code fails to compile with this error
the trait bound [closure@src/slclient.rs:69:23: 101:10 self:&slclient::SLMQClient, name:std::string::String, queue:std::result::Result<queue::Queue<'_>, std::string::String>]: std::ops::FnMut<(hyper::client::Response, hyper::header::Headers, std::string::String)> is not satisfied [E0277] slclient.rs /softayer-messaging-rust/src line 104

here is the call at line 104
self.http_operation(auth_url, Operation::Put(body.to_string()), Some(closure));

and the “closure” starts at line 69

    let closure = move |response: hyper::client::Response,
                        headers: hyper::header::Headers,
                        buffer: String| {
        // hyper::client::Response, hyper::header::Headers, String
        let data = Json::from_str(&buffer).unwrap();
        let obj = data.as_object().unwrap();
....

I have tried searching and various changes.

Can someone suggest what am I doing wrong? or this is wrong way to do this. What will be a better way to implement it.


#2

Your closure probably implements FnOnce, but not FnMut. Simple example of this sort of error:

pub fn takes_closure<F:FnMut() -> String>(f: F) {}
pub fn g(x: String) {
  let closure = ||x;
  takes_closure(closure);
}

Nightly rust gives a better error message:

error: expected a closure that implements the `FnMut` trait, but this closure only implements `FnOnce` [E0525]
 --> <anon>:5:17
5 |>   let closure = ||x;
  |>                 ^^^
note: the requirement to implement `FnMut` derives from here
 --> <anon>:6:3
6 |>   takes_closure(closure);
  |>   ^^^^^^^^^^^^^

It looks like the issue is that you need to clone the “name” variable. If a closure moves out of a captured variable, it can’t implement FnMut.

Also, if you only need FnOnce, it’s better to just require that.


#3

Thank you - that did solve my problem.