Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Return something that's allocated on the stack

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| | }
  | |_^
like image 853
wrongusername Avatar asked Sep 16 '15 08:09

wrongusername


People also ask

What does return do to the stack?

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 .

Where does return value go on stack?

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.

Can I return a stack from function in C++?

The C++ function std::stack::top() returns top element of the stack. This is the element which is removed after performing pop operation.

How is memory allocated on the stack?

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.


1 Answers

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 Strings 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 of Cow" 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 not str, so how can we call into() 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.

like image 127
Vladimir Matveev Avatar answered Nov 15 '22 10:11

Vladimir Matveev