I have the following simplified code, where a struct A
contains a certain attribute. I'd like to create new instances of A
from an existing version of that attribute, but how do I make the lifetime of the attribute's new value last past the function call?
pub struct A<'a> {
some_attr: &'a str,
}
impl<'a> A<'a> {
fn combine(orig: &'a str) -> A<'a> {
let attr = &*(orig.to_string() + "suffix");
A { some_attr: attr }
}
}
fn main() {
println!("{}", A::combine("blah").some_attr);
}
The above code produces
error[E0597]: borrowed value does not live long enough
--> src/main.rs:7:22
|
7 | let attr = &*(orig.to_string() + "suffix");
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ does not live long enough
8 | A { some_attr: attr }
9 | }
| - temporary value only lives until here
|
note: borrowed value must be valid for the lifetime 'a as defined on the impl at 5:1...
--> src/main.rs:5:1
|
5 | / impl<'a> A<'a> {
6 | | fn combine(orig: &'a str) -> A<'a> {
7 | | let attr = &*(orig.to_string() + "suffix");
8 | | A { some_attr: attr }
9 | | }
10| | }
| |_^
The ret instruction transfers control to the return address located on the stack. This address is usually placed on the stack by a call instruction. Issue the ret instruction within the called procedure to resume execution flow at the instruction following the call .
A typical Go function call The return value is passed through the stack, and the stack space of the return value precedes the parameter, that is, the return value is closer to the bottom of the caller stack.
The C++ function std::stack::top() returns top element of the stack. This is the element which is removed after performing pop operation.
Stack memory is allocated in a contiguous block whereas Heap memory is allocated in any random order. Stack doesn't require to de-allocate variables whereas in Heap de-allocation is needed.
This question most certainly was answered before, but I'm not closing it as a duplicate because the code here is somewhat different and I think it is important.
Note how you defined your function:
fn combine(orig: &'a str) -> A<'a>
It says that it will return a value of type A
whose insides live exactly as long as the provided string. However, the body of the function violates this declaration:
let attr = &*(orig.to_string() + "suffix");
A {
some_attr: attr
}
Here you construct a new String
obtained from orig
, take a slice of it and try to return it inside A
. However, the lifetime of the implicit variable created for orig.to_string() + "suffix"
is strictly smaller than the lifetime of the input parameter. Therefore, your program is rejected.
Another, more practical way to look at this is consider that the string created by to_string()
and concatenation has to live somewhere. However, you only return a borrowed slice of it. Thus when the function exits, the string is destroyed, and the returned slice becomes invalid. This is exactly the situation which Rust prevents.
To overcome this you can either store a String
inside A
:
pub struct A {
some_attr: String
}
or you can use std::borrow::Cow
to store either a slice or an owned string:
pub struct A<'a> {
some_attr: Cow<'a, str>
}
In the last case your function could look like this:
fn combine(orig: &str) -> A<'static> {
let attr = orig.to_owned() + "suffix";
A {
some_attr: attr.into()
}
}
Note that because you construct the string inside the function, it is represented as an owned variant of Cow
and so you can use 'static
lifetime parameter for the resulting value. Tying it to orig
is also possible but there is no reason to do so.
With Cow
it is also possible to create values of A
directly out of slices without allocations:
fn new(orig: &str) -> A {
A { some_attr: orig.into() }
}
Here the lifetime parameter of A
will be tied (through lifetime elision) to the lifetime of the input string slice. In this case the borrowed variant of Cow
is used, and no allocation is done.
Also note that it is better to use to_owned()
or into()
to convert string slices to String
s because these methods do not require formatting code to run and so they are more efficient.
how can you return an
A
of lifetime'static
when you're creating it on the fly? Not sure what "owned variant ofCow
" means and why that makes'static
possible.
Here is the definition of Cow
:
pub enum Cow<'a, B> where B: 'a + ToOwned + ?Sized {
Borrowed(&'a B),
Owned(B::Owned),
}
It looks complex but it is in fact simple. An instance of Cow
may either contain a reference to some type B
or an owned value which could be derived from B
via the ToOwned
trait. Because str
implements ToOwned
where Owned
associated type equals to String
(written as ToOwned<Owned = String>
, when this enum is specialized for str
, it looks like this:
pub enum Cow<'a, str> {
Borrowed(&'a str),
Owned(String)
}
Therefore, Cow<str>
may represent either a string slice or an owned string - and while Cow
does indeed provide methods for clone-on-write functionality, it is just as often used to hold a value which can be either borrowed or owned in order to avoid extra allocations. Because Cow<'a, B>
implements Deref<Target = B>
, you can get &B
from Cow<'a, B>
with simple reborrowing: if x
is Cow<str>
, then &*x
is &str
, regardless of what is contained inside of x
- naturally, you can get a slice out of both variants of Cow
.
You can see that the Cow::Owned
variant does not contain any references inside it, only String
. Therefore, when a value of Cow
is created using Owned
variant, you can choose any lifetime you want (remember, lifetime parameters are much like generic type parameters; in particular, it is the caller who gets to choose them) - there are no restrictions on it. So it makes sense to choose 'static
as the greatest lifetime possible.
Does
orig.to_owned
remove ownership from whoever's calling this function? That sounds like it would be inconvenient.
The to_owned()
method belongs to ToOwned
trait:
pub trait ToOwned {
type Owned: Borrow<Self>;
fn to_owned(&self) -> Self::Owned;
}
This trait is implemented by str
with Owned
equal to String
. to_owned()
method returns an owned variant of whatever value it is called on. In this particular case, it creates a String
out of &str
, effectively copying contents of the string slice into a new allocation. Therefore no, to_owned()
does not imply ownership transfer, it's more like it implies a "smart" clone.
As far as I can tell String implements
Into<Vec<u8>>
but notstr
, so how can we callinto()
in the 2nd example?
The Into
trait is very versatile and it is implemented for lots of types in the standard library. Into
is usually implemented through the From
trait: if T: From<U>
, then U: Into<T>
. There are two important implementations of From
in the standard library:
impl<'a> From<&'a str> for Cow<'a, str>
impl<'a> From<String> for Cow<'a, str>
These implementations are very simple - they just return Cow::Borrowed(value)
if value
is &str
and Cow::Owned(value)
if value
is String
.
This means that &'a str
and String
implement Into<Cow<'a, str>>
, and so they can be converted to Cow
with into()
method. That's exactly what happens in my example - I'm using into()
to convert String
or &str
to Cow<str>
. Without this explicit conversion you will get an error about mismatched types.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With