rustlifetimecurrying

How to curry a function that captures a mutable reference?


I saw many questions about currying in Rust, but the examples where always very simple. As my current function involves lifetimes and so on, I needed some help. Here's the code I want to curry:

pub type POSTFunc = fn(&POSTData) -> HTTPResult;
pub type GETFunc = fn(&GETData) -> HTTPResult;
pub trait Handler {
    fn call(&self, request: &HTTPRequest) -> HTTPResult;
}
impl Handler for GETFunc {
    fn call(&self, request: &HTTPRequest) -> HTTPResult {
        match request.get_method() {
            Method::GET(data) => self(&data),
            _ => method_not_allowed(),
        }
    }
}

impl Handler for POSTFunc {
    fn call(&self, request: &HTTPRequest) -> HTTPResult {
        match request.get_method() {
            Method::POST(data) => self(&data),
            _ => method_not_allowed(),
        }
    }
}
fn add_endpoint<'a>(
    path: &'a str,
    endpoints: &mut HashMap<&'a str, HashMap<&str, Box<dyn Handler>>>,
    get: Option<GETFunc>,
    post: Option<POSTFunc>,
) {
    let mut methods: HashMap<&str, Box<dyn Handler>> = HashMap::new();
    if let Some(get) = get {
        methods.insert("GET", Box::new(get) as Box<dyn Handler>);
    }
    if let Some(post) = post {
        methods.insert("POST", Box::new(post) as Box<dyn Handler>);
    }

    endpoints.insert(path, methods);
}

As you can probably guess, this is about adding endpoints to a web server. I made some attempts at currying this function, and the closest I got was this:

fn curry_add_endpoint<'a> (endpoints: &mut HashMap<&'a str, HashMap<&'a str, Box<(dyn Handler)>>>) -> impl FnMut(&'a str, Option<GETFunc>, Option<POSTFunc>) {
    let inner = |path: &'a str, get: Option<GETFunc>, post: Option<POSTFunc>| {
        let mut methods: HashMap<&'a str, Box<dyn Handler>> = HashMap::new();
        if let Some(get) = get {
            methods.insert("GET", Box::new(get) as Box<dyn Handler>);
        }
        if let Some(post) = post {
            methods.insert("POST", Box::new(post) as Box<dyn Handler>);
        }

        endpoints.insert(path, methods);
    };
    return inner;
}

which gives the error:

hidden type for `impl FnMut(&'a str, std::option::Option<for<'b> fn(&'b methods::GETData) -> Result<methods::HTTPResponse, HTTPResponseError>>, std::option::Option<for<'b> fn(&'b methods::POSTData) -> Result<methods::HTTPResponse, HTTPResponseError>>)` captures lifetime that does not appear in bounds [E0700]
Note: hidden type  captures lifetime '_`

This problem may be related to this issue, but I tried the wrapping and it didn't work. Maybe it still is related to the issue but I'm not good enough in rust to identify the solution.


Solution

  • Based on our discussion in the comments, this might be what you're after:

    fn curry_add_endpoint<'a, 'b>(
        endpoints: &'b mut HashMap<&'a str, HashMap<&'a str, Box<dyn Handler>>>
    ) -> impl FnMut(&'a str, Option<GETFunc>, Option<POSTFunc>) + 'b
    

    We borrow the endpoints for 'b instead of moving them, we give explicit 'a lifetimes for the keys inside the map, and we mark the returned closure as capturing the 'b lifetime.

    You could also use 'a everywhere but that might be too restrictive, you don't actually need to borrow endpoints for that long.

    If you decide to use String for the hashmap keys instead then the signature gets simplified further

    fn curry_add_endpoint<'a>(
        endpoints: &'a mut HashMap<String, HashMap<String, Box<dyn Handler>>>
    ) -> impl FnMut(String, Option<GETFunc>, Option<POSTFunc>) + 'a