How do I avoid "temporary value created here" errors when creating an array?

What is the idiomatic way to avoid "temporary value created here" errors in the context of creating an array with values you are creating during initialization?

For example:

fn main() {
    let my_array = [
        "",
        format!("{}", "testing").as_str(),
    ];
}

I get this:

error: borrowed value does not live long enough
 --> src/main.rs:5:6
  |
4 |         format!("{}", "testing").as_str(),
  |         ------------------------ temporary value created here
5 |     ];
  |      ^ temporary value dropped here while still borrowed
6 | }
  | - temporary value needs to live until here
  |
  = note: consider using a `let` binding to increase its lifetime
  = note: this error originates in a macro outside of the current crate

error: aborting due to previous error

error: Could not compile `scratchpad`.

To learn more, run the command again with --verbose.
1 Like

I think the reason you're seeing that error is that since the string created by your format! call is not bound in the scope of main but in the scope of the array, when the array goes out of scope it will be dropped leaving the result of the format! call dangling.

I think the only way to fix it is to bind the result of the format! call to the main scope.

fn main() {

    let s = format!("{}", "testing");
    let my_array = [
        "",
        s.as_str(),
    ];
}

Shoot. I was really hoping the answer would not be "Create a variable binding for each entry you want to format." Messy! :frowning:

Well, if you're willing to accept a little bit of overhead, you could do something like this:

    let arr = vec![
        String::from("foo"),
        format!("{}", 6),
        format!("{:?}", Some(5))
    ];
    
    let arr = arr.iter().map(|s| s.as_str()).collect::<Vec<_>>();

This makes one vector containing the different strings, and then converts it into a vector of &strs. Actually, a vector or array of Strings might be what you're looking for in the first place, but I can't tell unless you describe what you're trying to do some more

For my actual task it was easy enough to work around the problem by simply using an array of Strings.

let arr = [
    "first".to_string(),
    format!("{}", "second"),
]

But my goal is more to learn than accomplish a specific task in this instance. I would like to understand why I can't convert a String to a &str for a &str array, but I am fining converting a &str to a String for a String array.

I can tell there's something fundamental about the ownership model and lifetimes that I'm missing here!

2 Likes

Well, primarily it's because a &str is a borrowed object, it must be borrowed from somewhere. So in your original example, when you use format!(), you get back a String. Then you're calling a method of String, as_str(), to borrow the data in the String as a &str. But the String isn't owned by anything after you call it as_str with it, it has no variable binding. So it basically doesn't exist outside of that call to as_str, which is where the problem arises. The String will be deallocated (dropped in Rust's language) before the array is finished initializing, so you'd have an array of dangling pointers.

You're trying to borrow from something that won't exist after you initialize an array, or in the compiler's language, you're trying to get a reference to a temporary value. You can totally convert an array of Strings into an array of &str's, you just have to make sure the Strings are still alive when you're trying to use the references to them (&strs):

    let strings = [
        format!("{}", "testing")
    ];
    let my_array = [
        "",
        strings[0].as_str(),
    ];

However, the other way around is much simpler to reason about. to_string() makes a copy of the data in a &str and stores it in a String. The String owns that data, so it doesn't matter if the &str reference goes out of scope now, since the String keeps its own copy.

By the way, are you sure you really want to use arrays here? Vec<String> is pretty commonly used instead, because arrays tend to be a bit more of a hassle to mess with in Rust, especially when you're passing them around into other functions

6 Likes

No, I'm not at all sure what I want to use. Thank you for pointing out that Vec<String> would be more idiomatic in this case! :slight_smile:

I'm currently porting code from other languages to Rust as an exercise to really understand Rust. I have been a professional programmer in a variety of languages already over my career, and I see Rust as the future. (Or some language that learns the lessons Rust is teaching!)

I would like to spend a lot more time working with Rust professionally. Since it is so young (comparatively) in the systems language space, the most likely avenue I see in the short term is to teach it. To teach it, I need a deeper understanding. So I learn... :eyeglasses:

4 Likes

If you're coming from C, you might be thinking of references as pointers. That's misleading, because they're not quite the same.

In C a pointer may be to data you have to free() (which is equivalent of owned data in Rust, String) or to something else that you're not allowed to free() (that one is equivalent of Rust's reference, &str).

So format!() returning String says "now you free it". You calling as_str() on it are saying "just let me look, not my problem who frees that". And Rust want to know "who" owns the string? Who frees it? If it's supposed to be freed when the array goes away, then the array has to own it, so it has to be the owned type String.

If the array only borrows it, then there must be something else that owns the strings. It can be a variable in a scope, function argument, Vec, etc. just any one thing that is the permanent location for the String to live, so that it can be borrowed in other places that don't care about freeing it.

BTW: unrelated to ownership, plain arrays in Rust are very inflexible and most of the time you'll want to collect data in Vec (vec![…] to create a literal), and pass it around as slice &[…].

10 Likes

If you're coming from C++, it's exactly the same problem you'd have with creating a temporary std::string and leaking the c_str() pointer from it -- except Rust doesn't let you get away with that.

6 Likes

@kornol @cuviper

I went and read through the documentation (again) for Vec<T> and array to compare them.

I think I see why people don't like array. Lots of limitations!

  • Size has to be known at compile-time.
  • No support for generics of array sizes
  • Differently-sized arrays are considered different types.
  • Not possible to move values out of arrays.
  • Different behaviors for arrays <= length 32 vs > length 32.

I read through the std String documentation as well. Tons of methods, but nothing that appears to consume a String to produce a &str. Looks like Vec<String> is a much better option in general.

There is a couple of workarounds to move values out of an array though:

  • Option::take
  • std::mem::replace

These are not as pretty as Vec, but do not allocate anything.

1 Like

Look for "fn as_ref(&self) -> &str" in the String docs; this is not a direct method of the String class, but a Trait implementation, in this case of Trait AsRef<str>.
It borrows the String (&self), and returns an &str. Sounds exactly what you're looking for!

The secret to the Rust Docs is that all the really cool methods are never defined directly on the Type itself, but rather 'hidden' in the "Trait implementations" section.
In Rust, conversions, especially the really essential ones, are all abstracted into generic Traits, that a lot of Types then implement. It's the Power of Generics!
Coming from other languages (Python, Java) this really took some getting used to myself. (I was used to mostly ignoring the "interfaces implemented by this class" in java, and python doesn't really have interfaces at all, just relying on informal duck-typing conventions for direct methods)

More specifically for strings: this blog post, and the follow-up were immensely helpful to me, in explaining the difference between them, and why that difference exists.

In closing, to "consume" a variable has special meaning, and I can't tell if you're using it in that way. It means you take ownership without returning (though you're probably returning some manipulated/converted form of the contents). In syntax: 'consuming' methods take self, instead of &self.
If you didn't intend that specific usage, now you're at least aware of it :slight_smile:

P.S. This is my first post, but I've been lurking these forums for months now. Your attempt to learn strikes a chord with my own attempts :slight_smile:

1 Like

@dnsl48 Thank you for pointing that out! I think I understand Option::take ... looks like it leaves an empty Option in it's wake, which is probably the "not pretty" part of this one.

std::mem::replace looks like a power tool. I hope I can understand it well enough to use it one day.

@juleskers Wow! First post! You probably earned a few badges for it, then. :wink:

I actually saw as_ref() when I was reading the documentation (the trait list on that page is massive). As far as I can tell, it doesn't do what I hoped, because my attempt at using it hit the same error.

Of course, it could be that I'm just using it incorrectly. I don't pretend to be an expert...yet. :wink:

fn main() {
    let my_array = [
        "",
        format!("{}", "testing").as_ref(),
    ];
}
$ cargo run
   Compiling scratch v0.1.0 (file:///Users/nathan/rust/scratch)
error: borrowed value does not live long enough
 --> src/main.rs:5:6
  |
4 |         format!("{}", "testing").as_ref(),
  |         ------------------------ temporary value created here
5 |     ];
  |      ^ temporary value dropped here while still borrowed
6 | }
  | - temporary value needs to live until here
  |
  = note: consider using a `let` binding to increase its lifetime
  = note: this error originates in a macro outside of the current crate

error: aborting due to previous error

error: Could not compile `scratch`.

To learn more, run the command again with --verbose.

Option::take uses std::mem::replace internally :slight_smile:. The way to think of these is that Rust "doesn't understand" a logical replacement of a value when it's done in multiple steps. The doc for mem::replace puts this example forward:

impl<T> Buffer<T> {
    fn get_and_reset(&mut self) -> Vec<T> {
        // error: cannot move out of dereference of `&mut`-pointer
        let buf = self.buf;
        self.buf = Vec::new();
        buf
    }
}

The above doesn't work, as indicated, even though it should - you have a mutable reference, and hence are allowed to mutate the referent. But, Rust thinks you're moving the Vec out (and thus destroying it) while holding borrowed content, even though on the very next line the buf is replaced. That's where mem::replace comes in - it achieves effectively the same thing (using unsafe code underneath), but now the compiler isn't worried that anything was moved.

2 Likes

You need something to own the String if you're creating one dynamically. as_ref() on a String will give you a string slice, but the string slice is a reference to the underlying String - if that String isn't kept alive by something while you're holding that reference, it would get dropped and you'd be referencing invalid memory. That's basically what Rust is preventing you from doing :slight_smile:

As others have mentioned, format!(...) returns a fresh String instance, but unless that instance is owned by something, it's just a temporary and will go away at the end of the enclosing statement. So, you cannot get a reference to it unless you "anchor" it somewhere (i.e. make it owned by something that's alive at least for the duration/scope during which you'd like to use the reference/string slice to it).

2 Likes

I think you can understand this (pseudo-code):

fn main() {
    let my_array = [
        "",
       let s = format!("{}", "testing"); s.as_str()
    ];
}
2 Likes

You might even write it as non-pseudo code =)

fn main() {
    let my_array = [
        "",
        { let s = String::from("testing"); &s }
    ];
}
4 Likes

@juleskers Your post was so packed full of content that I forgot to respond to some of it!

I was not aware that "consume" has a special defined meaning in Rust. My google-fu is failing me, though. If you have a link for some further documentation I could read, that would be great. You are right in that I just meant "take the string value and get rid of it and return a &str in its place", which sounds like it doesn't match the definition.

Thank you for the blog posts. I had read at least one of them before, but they bear reading again.

Ah! That makes it crystal clear what the error is. Thank you all.

So then the question is, is it possible to generate a formatted &str without a containing String? This is all academic at this point -- in the actual port I'm working on just switched to an array of Strings. But I am curious as to whether it's possible to generate a formatted &str inside of an array without using a placeholder that you std::mem::replace later.

The interaction between String and &str seems simultaneously brilliant, and as frustrating as heck.