I'm trying to implement a method which looks for dictionary value in local HashMap and if it doesn't find than goes to the database. I use async mysql with tokio and futures. And it's so hard to implement it because I'm receiving many compiler errors about lifetime
Here's my current code:
#[derive(Debug)]
pub struct Dictionary {
pub name: String,
pub values: HashMap<String, usize>,
}
#[derive(Debug)]
pub struct Dictionaries {
values: HashMap<String, Dictionary>,
pool: Option<mysql_async::Pool>,
}
impl Dictionaries {
...
pub fn get_id(&self, dict_name: String, key_name: String)
-> Either<impl Future<Item=usize, Error=DictionaryError>, impl Future<Item=usize, Error=DictionaryError>> {
//look for id in the local cache
let id_in_cache = self.values.get(&dict_name)
.and_then(|dict| dict.values.get(&key_name))
.map(|id| *id);
match id_in_cache {
Some(id) => Either::A(future::ok(id)),
None => {
match &self.pool {
Some(p) => {
info!("Key: {:?} in dictionary {:?} not found. trying to get from DB", &dict_name, &key_name);
let res = p.get_conn()
.and_then(move |conn| {
let insert_sql = format!("INSERT IGNORE INTO {} (NAME, created_date) VALUES('{}', now())", &dict_name, &key_name);
conn.drop_exec(insert_sql, ()).map(|c|(c, dict_name, key_name))
})
.and_then(|(conn, d, k)| {
let select_sql = format!("SELECT id FROM {} WHERE name = '{}'", &d, &k);
conn.prep_exec(select_sql, ())
.and_then(move |result| {
match result.is_empty() {
true => panic!("Id should be there"),
false => {
result.collect::<usize>()
.map(move |(_, v)| v[0])
.map(move |new_id| {
info!("Creating for dict {:?} key {:?} with id: {:?}", &d, &k, &new_id);
//let dict_opt = &self.values.get(&d);
new_id
})
}
}
})
})
.map_err(|e| {
panic!("Can not use dictionaries: {:?}", e);
});
return Either::B(res);
//future::ok(10)
}
None => Either::A(future::err(DictionaryError::PoolIsNotExisted))
}
}
}
}
}
First of all, I can not use refs in method params like dict_name: &String, key_name: &String
Because I receive that lifetime of these references is too short to be used in and_then
futures.
Next, if I have and_then(#1)...and_then(#2)
and I need to use dict_name
in both of them then I should make clone
or pass them as params for the next future:
.and_then(move |conn| {
...
conn.drop_exec(insert_sql, ()).map(|c| (c, dict_name, key_name))
})
How should I work in Rust way for such use cases?
And the last thing I couldn't win is &self
reference.
After I inserted the new value to database I have to put in self->HashMap
But if I try to make let dict_opt = &self.values.get(&d);
inside my future then I receive lifetime error
Is there a solution?
It's quite hard to understand the right way in Rust after Java or JS where you can easily use local variables in closures.
Thanks