Hi. I have a need to create objects of a generic type, and that generic type sometimes has an embedded lifetime. Along these lines:
use core::hash::Hash;
use std::collections::HashSet;
trait Key<'a> : Hash + Eq {
fn from_owned_key<O : OwnedKey>(owned_key : &'a O) -> Self;
}
impl <'a>Key<'a> for &'a str {
fn from_owned_key<O : OwnedKey>(owned_key : &'a O) -> Self {
owned_key.borrow_str().unwrap()
}
}
impl Key<'static> for String {
fn from_owned_key<O : OwnedKey>(owned_key : &O) -> Self {
owned_key.to_string().unwrap()
}
}
trait OwnedKey : Key<'static> {
fn borrow_str(&self) -> Option<&str>;
fn to_string(&self) -> Option<String>;
}
impl OwnedKey for String {
fn borrow_str(&self) -> Option<&str> {
Some(&self)
}
fn to_string(&self) -> Option<String> {
Some(self.clone())
}
}
fn check_container<'a, R : Key<'a>>(container : &HashSet<R>) -> bool
{
let owned_string = "test_key".to_string();
let query_key = R::from_owned_key(&owned_string); //This is the problem.
// query_key is created with the lifetime of 'a, which outlives this
// function. I need a way to make something that is the *Type* of
// R, but with another lifetime.
container.contains(&query_key)
}
fn main() {
let container : HashSet<&str> = HashSet::new();
let _ = check_container(&container);
}
Is there another way to create a new Key but with a more limited lifetime, or an alternative way to structure these traits so that it is possible to borrow from a more restrictive generic (OwnedKey) to a less restrictive generic (Key)?
What happens here is that the borrow checker can't know that .contains() doesn't take advantage of the potentially-longer lifetime of Key that HashMap has, and only uses it temporarily. Borrow checker treats this call the same as if you called insert. Lifetimes of Key are the same for both, and you can probably see it would be an error to call insert, because the HashMap of longer-lived strings would end up with a short-lived one that was temporarily borrowed for a single function call.
This problem is why HashSet by itself already uses the Borrow trait to allow more flexible keys. You're re-inventing the Borrow trait on top of it, but that won't work. R::from_owned_key is supposed to create a key with R's lifetime, and HashSet doesn't support flexibility of your Key trait, it has its own Borrow trait.
You can just force it through with mem::transmute to give contains the lifetime it wants. It will be safe in practice, because you know contains won't keep the value longer than the function call.
Or use the Borrow trait instead. Copy the function signature from contains, and it will give you the flexibility to query by either owned or borrowed keys.
You can comment out the contains (or change it to a println! so it doesn't disappear during optimization) and you'll get the same error, 'owned_string' does not live long enough. The die is cast (probably because of the Drop trait) when the from_owned_key method creates type with a built-in lifetime exceeding the function scope, that is borrowing the temporary within the function.
You're 100% right that I'm trying to conceptually get a Borrow<> trait, but I need a Borrow<> trait that sometimes makes a clone and sometimes does a format conversion depending on what's needed. I definitely tried make things work with Borrow<> but eventually gave up. Maybe I'll give that approach another go - I just got discouraged.
The transmute appears safe in this case, but I was trying to avoid it because I felt like this problem must not be unique to me in this case so perhaps there was a better solution.
I wasn't thinking clearly before. I have a solution with transmute that keeps all the unsafe nicely wrapped up in one spot. The key was to transmute inside the trait's impl when I know the concrete types. (so technically one spot per impl) Still not ideal to need unsafe in this situation.