Store temporary value inside a Vec

fn do_something(i: i32) -> String{
    todo!()
}

fn main (){
    let mut words: Vec<&str> = Vec::new();

    for i in 0..10{
        let s = do_something(i).as_str();   //temporary value created here
        words.push(s);
    }    // temporary value will be dropped here
   
    println!("{:?}", words);  // will throw error as can't reference temporary value
} 

How can I store the value in "s" inside words: Vec<&str>?

Restrictions:

  1. I can't change the type of words: Vec<&str> to words: Vec<String>.
  2. do_something function can only return String as output type.

You can't store a reference (like &str) unless you also store the thing that it points to (the String). So if you need a vector of references, you'll also need to store all the original strings. You can use an additional vector to do this:

fn main (){
    let mut owned_words: Vec<String> = Vec::new();

    for i in 0..10 {
        let s = do_something(i);
        owned_words.push(s);
    }
    
    let words: Vec<&str> = owned_words.iter().map(String::as_str).collect();
   
    println!("{:?}", words);
}
1 Like

Thanks, @mbrubeck, for such a quick response.

Sorry, I tried to replicate my use case using the above code, but it didn't replicate properly.
Here is something which looks like an actual use case:

extern crate alloc;
use alloc::vec::Vec;
use crate::alloc::string::{ToString, String};

fn something()->Vec<&'static str>{
    let mut owned_words: Vec<String> = Vec::new();
    
    for i in 0..10{
        let s = do_something(i);
        owned_words.push(s);
    }

    let words: Vec<&str> = owned_words.iter().map(String::as_str).collect();  // owned_words` is borrowed here

    words  // Error: cannot return value referencing local variable `owned_words`
}

fn do_something(_i: i32)-> String{
    "hi".to_string()
}

Restriction
Something function can only return Vec<&'static str>

These restriction don't make sense, because together they forbid most useful programs you could write. Why do you have this restriction?

String is dynamically allocated and created at run-time, while &'static str points to strings that are built into your program at compile time. You can't transform a String into an &'static str (except by leaking memory). When you want to return a string from a function, you almost always want String rather than &str.

3 Likes

@mbrubeck
The reason for this is that I am using this trait which only support Tensor<&'static str> as output type and doesn't support Tensor<String>. It's related to my earlier post

The reason for this is if I will change its output type to &'static str, then I will run into the temporary value error inside this function.

What exactly is the problem you run into if you try to use Tensor<String> instead of Tensor<&'static str>?

1 Like

Thank you, @mbrubeck, @steffahn for your timely response

I have solved it by first converting utf8 to u8 and then sending Tensor<u8> as output.

@steffahn, it works fine with Tensor<String>, but my part of code where I'm using something function output doesn't support Tensor<String>. That's why I'm sticking with Tensor<&'static str>

1 Like

A temporary reference can't exist by itself. &str is not a string, but a temporary view into a String or other owning string type that must have a permanent storage somewhere else (with a curious case of &'static str which typically borrows from strings embedded in the program's own executable).

So if you have do_something() that returns String, but you're not keeping the String, then &str attached to it can't exist.

@kornel, how about doing it in this way?

extern crate alloc;
use crate::alloc::string::{String, ToString};
use alloc::borrow::Cow;
use alloc::vec::Vec;

fn something() -> Vec<Cow<'static, str>> {
    let mut owned_words: Vec<Cow<'static, str>> = Vec::new();

    for i in 0..10 {
        let s = do_something(i);
        owned_words.push(Cow::Owned(s));
    }

    owned_words
}

fn do_something(_i: i32) -> String {
    "hi".to_string()
}

fn main() {
    let v = something();

    println!("{:?}", v);
}

So whatever your real code is doesn't allow Tensor<String>, but does allow Tensor<Cow<'static, str>>? That seems strange.

Let's cc @Michael-F-Bryan, since they're the author of that crate, and will be better suited to guide you through your use-case.

But know that what @mbrubeck mentioned still holds:

  • &'static str is for global data, that is, hard-coded data in the binary (or runtime data that has been leaked (very rare));

  • String is for runtime data.

If do_something returns a String, then it means it's yielding runtime data, as most of the functions do, anyways. So I doubt you ought to be dealing with a &'static str tensor, that is, a tensor over hard-coded data, and, that instead, you ought to be operating with a Tensor<String>.

That's actually an interesting type you are talking about (for instance, on some of my codebases I have used a type Str = Cow<'static, str>; alias): you seem to be in a situation where you sometimes have hard-coded data, and other times runtime data, and you have this type problem of unifying those two types, of unifying String and &'static str (at this point this doesn't have that much to do with tensors, I'd say).

The classic and most frequent unification is to use the String type. Indeed, you can always perform a "runtime copy" (a strdup in C parlance) of your &'static str hardcoded data.

  • (Aside: Box<str> is a tad strictly better use for this pattern)

But I can understand your reluctance to use that: if you have many hard-coded strings, and with maybe some of them being big, it seems quite silly to be duplicating string you already had around.

That's where your Cow<'static, str> becomes very handy: as an enum, it is the classic approach to unify two types, by having a type that might either hold a String or a &'static str. So, indeed, you have something compatible with both, and which comes with a constant-time cost at construction time to build instances of it from a String or a &'static str (no duplication!).

But be wary: these juicy milked benefits might turn sour! Indeed, the cost you have saved by using this type may be lost in the long run due to the fact you are now dealing with an enum / branchy type: every time an API will want to inspect the str contents of your Cow<str>, they will have to branch over whether they are dealing with a Cow::Borrowed or a Cow::Owned variant, and this might incur in a very tiny penalty cost each time that can add up to higher penalty than that of having duplicated the initial string to begin with.

2 Likes

This Cow is something like:

enum Cow {
 Owned(String)
 Borrowed(&'static str)

and you're using only the String variant here, so you're not really taking advantage of the flexibility that you're paying for. You're returning a vector with underlying data that looks like [(0, String), (0, String), (0, String)]. In just this code it gives you zero advantage over returning just [String, String, String] (i.e. Vec<String>).

It could make sense if you need to use a lot of string literals mixed with dynamic strings. Do you need that?

1 Like

@Yandros, thanks for giving such a pretty explanation. I'm really thankful to you for explaining it in such depth. I have started coding in Rust 2 weeks ago, so all these things are a bit new to me. It has cleared my lots of doubt.

@kornel, actually, I didn't think of this earlier. It's an excellent point. Thanks for pointing it out.

I will have to look back at my approach based on all the above comments. I will revert back after brainstorming.

P.S.: I joined Rust Forum 2 days back. I am glad to see the Rust community support

3 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.