Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
984 views
in Technique[技术] by (71.8m points)

rust - How do I share a HashMap between Hyper handlers?

I'm attempting to learn Rust by implementing a simple in-memory URL shortener with Hyper 0.10. I'm running into an issue that I think is caused by trying to close over a mutable HashMap in my handler:

fn post(mut req: Request, mut res: Response, short_uris: &mut HashMap<&str, &str>) {
    let mut body = String::new();
    match req.read_to_string(&mut body) {
        Ok(_) => {
            let key = short_uris.len();
            short_uris.insert(&key.to_string(), &body.to_string());
            *res.status_mut() = StatusCode::Created;
            res.start().unwrap().write(&key.to_string().into_bytes());
        },
        Err(_) => *res.status_mut() = StatusCode::BadRequest
    }
}

fn get(req: Request, mut res: Response, short_uris: &HashMap<&str, &str>) {
    match req.uri.clone() {
        AbsolutePath(path) => {
            match short_uris.get::<str>(&path) {
                Some(short_uri) => {
                    *res.status_mut() = StatusCode::MovedPermanently;
                    res.headers_mut().set(Location(short_uri.to_string()));
                },
                None => *res.status_mut() = StatusCode::NotFound
            }
        },
        _ => *res.status_mut() = StatusCode::BadRequest
    }
}

fn main() {
    let mut short_uris: HashMap<&str, &str> = HashMap::new();
    short_uris.insert("/example", "http://www.example.com");
    Server::http("0.0.0.0:3001").unwrap().handle(move |req: Request, mut res: Response| {
        match req.method {
            hyper::Post => post(req, res, &mut short_uris),
            hyper::Get => get(req, res, &short_uris),
            _ => *res.status_mut() = StatusCode::MethodNotAllowed
        }
    }).unwrap();
}
src/main.rs:42:40: 42:46 error: the trait bound `for<'r, 'r, 'r> [closure@src/main.rs:42:47: 48:3 short_uris:std::collections::HashMap<&str, &str>]: std::ops::Fn<(hyper::server::Request<'r, 'r>, hyper::server::Response<'r>)>` is not satisfied [E0277]
src/main.rs:42  Server::http("0.0.0.0:3001").unwrap().handle(move |req: Request, mut res: Response| {

Do I need to use an Arc to share the HashMap between threads? If so, what would that look like? Also, I could be totally wrong about the issue. The error message is very cryptic to me.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Please include all the necessary use declarations next time, thanks!

If you're using nightly Rust, the error message is a less cryptic:

expected a closure that implements the Fntrait, but this closure only implements FnMut

That means that Hyper needs the closure to be shared between threads, so the closure needs to use its environment only via immutable or shared methods – so the usage of &mut short_uris is the offender here. To provide shared threadsafe mutability in Rust, you should use Mutex or RwLock.

Please note that you don't need Arc here – Hyper manages the ownership of the closure itself (probably by wrapping the closure in Arc under the hood, or using something like scoped-threads).

There's also second issue with your code – you use HashMap<&str, &str>. &str is a borrowed reference. Each time when you have something borrowed in Rust, you should ask yourself?– from where? Here you try to borrow from really short-lived strings – key.to_string() and body.to_string(). It just can't work. Just make your hashmap fully owned – HashMap<String, String>. Here's the version of your code which compiles:

extern crate hyper;

use hyper::server::{Request, Response, Server};
use std::collections::HashMap;
use hyper::status::StatusCode;
use hyper::uri::RequestUri::AbsolutePath;
use hyper::header::Location;
use std::io::prelude::*;

fn post(mut req: Request, mut res: Response, short_uris: &mut HashMap<String, String>) {
    let mut body = String::new();
    match req.read_to_string(&mut body) {
        Ok(_) => {
            let key = short_uris.len();
            short_uris.insert(key.to_string(), body);
            *res.status_mut() = StatusCode::Created;
            res.start()
                .unwrap()
                .write(&key.to_string().into_bytes())
                .unwrap();
        }
        Err(_) => *res.status_mut() = StatusCode::BadRequest,
    }
}

fn get(req: Request, mut res: Response, short_uris: &HashMap<String, String>) {
    match req.uri {
        AbsolutePath(ref path) => match short_uris.get(path) {
            Some(short_uri) => {
                *res.status_mut() = StatusCode::MovedPermanently;
                res.headers_mut().set(Location(short_uri.to_string()));
            }
            None => *res.status_mut() = StatusCode::NotFound,
        },
        _ => *res.status_mut() = StatusCode::BadRequest,
    }
}

fn main() {
    let mut short_uris: HashMap<String, String> = HashMap::new();
    short_uris.insert("/example".into(), "http://www.example.com".into());
    let short_uris = std::sync::RwLock::new(short_uris);
    Server::http("0.0.0.0:3001")
        .unwrap()
        .handle(move |req: Request, mut res: Response| match req.method {
            hyper::Post => post(req, res, &mut short_uris.write().unwrap()),
            hyper::Get => get(req, res, &short_uris.read().unwrap()),
            _ => *res.status_mut() = StatusCode::MethodNotAllowed,
        })
        .unwrap();
}

I've also got rid of the unnecessary .clone() in the get function.

Please note that this code, while compiles, is not perfect yet – the RwLock locks should last shorter (get and post should take &RwLock<HashMap<String,String>> as an argument and perform the locking by themselves). The .unwrap() also may be handled in a better way. You can also consider using some lockless concurrent hashmap, there should be some crates for that, but I'm not into the topic, so I won't recommend any.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...