Problem statement:
Right now rust treats all owned types like String as having 'static lifetime, meaning rust in terms of borrow checking sees &'static str and String as same. This creates issues when we try to make a structure that is polymorphic over some type parameter S that could be a string slice reference or owned String.
Example:
use std::marker::PhantomData;
trait AsStr<'s> {
fn to_str(self: &Self) -> &str;
fn from_str(s: &'s str) -> Self;
}
impl<'s> AsStr<'s> for &'s str {
fn to_str(self: &Self) -> &str {
*self
}
fn from_str(s: &'s str) -> Self {
s
}
}
impl<'s> AsStr<'s> for String {
fn to_str(self: &Self) -> &str {
self.as_str()
}
fn from_str(s: &'s str) -> Self {
s.to_string()
}
}
struct Check<'s, S: AsStr<'s>> {
s: S,
_v: PhantomData<&'s ()>
}
impl<'s1, S: AsStr<'s1>> Check<'s1, S> {
pub fn clone_to_owned(
self: &'s1 Self,
) -> Check<'static, String>
{
Check {
s: self.s.to_str().to_string(),
_v: PhantomData::default()
}
}
}
fn example<'s, S: AsStr<'s>>(check: Check<'s, S>) -> Check<'static, String> {
check.clone_to_owned()
}
Lsp diagnostic errors:
Diagnostics:
1. `check` does not live long enough
borrowed value does not live long enough [E0597]
2. argument requires that `check` is borrowed for `'s` [E0597]
3. the parameter type `S` may not live long enough
...so that the type `S` will meet its required lifetime bounds [E0309]
Proposed solution is owned lifetime that is allowed only for non-reference types, therefore any reference outlives 'owned, because we know that structure with 'owned type does not hold any references to initial data, making a copy.
How so? Cow<'static, str> works fine as far as I remember.
3 Likes
True, but Cow is runtime solution. Each time you want to touch field in structure that is Cow you have to check whatever it is borrowed or owned. In my case I want to have functions that are generated once for specific case: one for some reference type like string slice and then another one for owned String. This way we don`t have branching or some vtable dispatch on each value access.
you are constructing an &'a Foo<'a> which is a common mistake, that causes self to be "borrowed forever".
if you change it to just self: &Self it compiles: Rust Playground
In this particular case yes, however consider this example with general clone_with_string_type function that accepts another generic parameter for new string type Rust Playground, in this case we have to specify lifetimes because new string type (NS) can potentially also be a reference. So we can have multiple conversions: &'static str -> String, &'buffer str -> String, &'static str -> &'buffer str, etc.
I'm not sure why you're trying to write this contrived example, but you can fix it again by:
- not having
clone_with_string_type take a &'s1 Check<'s1, S>; there is no need to link the lifetime of the self reference to the lifetime that can be used to create an instance of S, simply because you are not creating a new instance of S. Use a separate lifetime untied to 's1 instead.
- removing the
's lifetime parameter from Check: its usage along with the PhantomData signals to the borrow checker that Check holds some unknown reference with lifetime 's possibly unrelated to S, and this prevents any way to extend such lifetime when you know that S: AsStr<'longer_lifetime>.
therefore any reference outlives 'owned
This doesn't make sense to me. Something that is owned can live forever, and if any reference can outlive it then you get that any reference can live forever, which is obviously false.
It would allow "fixing" your code above only because it would make the type system unsound. I don't see how any sound implementation of this 'owned lifetime concept would fix your code.
What I`m trying to do is to parametrize certain structures, so that I could create one static instance of structure and one runtime instance of structure and have some code that can accept either one.
Also I need ability to go from static structure to runtime structure, have ability to clone it into runtime one, that I can pass around freely. Your fix indeed works however I encountered another problem when I tried to fit it with Sequence parameterized type Rust Playground.
I replaced phf::OrderedSet with &'static [T] because playground does not support dependencies, but idea stays the same.
In runtime I use VecSequence, but certain structures are initialized as static using PhfOrderedSetSequence, and at certain point I need to clone it into VecSequence.
You are using the lifetime 's3 both for the lifetime of the strings that can be used to create a NS and as the lifetime somehow connected to V (I'm not sure why you need that lifetime parameter on Sequence though). The latter forces the lifetime to be attached to the type, leading to the same issue as before. However there's no reason for these lifetimes to be the same, just use a different lifetime: Rust Playground