wayne
wayne

Reputation: 702

What is the difference between &str and &String

I'm walking through Microsoft's Rust tutorial here, which is about

implement the copy_and_return function so that it returns a reference to the value inserted in the vector

Solution is given here, but it's different from mine in that it used &String as return type while I used &str.

// standard solution
fn copy_and_return<'a>(vector: &'a mut Vec<String>, value: &'a str) -> &'a String {
    vector.push(String::from(value));
    vector.get(vector.len() - 1).unwrap()
}
// my solution
fn copy_and_return<'a>(vector: &'a mut Vec<String>, value: &'a str) -> &'a str {
    vector.push(String::from(value));
    return value; // simply return value
}

fn main() {
    let name1 = "Joe";
    let name2 = "Chris";
    let name3 = "Anne";

    let mut names = Vec::new();

    assert_eq!("Joe", copy_and_return(&mut names, &name1));
    assert_eq!("Chris", copy_and_return(&mut names, &name2));
    assert_eq!("Anne", copy_and_return(&mut names, &name3));

    assert_eq!(
        names,
        vec!["Joe".to_string(), "Chris".to_string(), "Anne".to_string()]
    )
}

In addition to the return type, another difference between mine and the standard solution is that I simply returned the parameter value, while the standard solution used complicated way vector.get(vector.len() - 1).unwrap().

I'm wondering if there's anything wrong with my solution that the tutorial takes another way?


While @Masklinn provided a great answer to my question, it's a bit specific to the example I gave but not directly addressing the title What is the difference between &str and &String.
I found this discussion to be quite informative:

Basically a String wraps and manages a dynamically allocated str as backing storage. Since str cannot be resized, String will dynamically allocate/deallocate memory. A &str is thus a reference directly into the backing storage of the String, while &String is a reference to the “wrapper” object. Additionaly, &str can be used for substrings, i.e. they are slices. A &String references always the whole string.

Chapter 4.3 of the Rust book also helps

Upvotes: 2

Views: 764

Answers (2)

s123
s123

Reputation: 171

There really isn't much of a practical difference. You didn't do what they asked, but it doesn't matter. There's no reason why you would want to use Microsoft's solution instead of yours. Theirs is doing more work for no reason when you can just do what you did.

But it's a bit more complex than that. Having the same lifetime means Rust enforces those things all live as long as each other. If you have a lifetime parameter where you don't need to, then you are making there be an unnecessary restriction. In Microsoft's solution, the lifetime for the value parameter is unnecessary because you are returning something related to the vector and nothing to do with the &str. In certain cases it will prevent you from doing things for no reason. In your solution it's the exact opposite. You're returning the &str, but the vector has nothing to do with that. In certain cases it will also prevent you from doing things for no reason. You won't be able to access the returned &str if you have done anything mutable with the vec. Your function should look like this if you don't care about Microsoft's very stupid solution and just want it to be practical:

fn copy_and_return<'a>(vec: &mut Vec<String>, str: &'a str) -> &'a str {
    vector.push(String::from(value));
    value
}

Other improvements:

  • no need for return keyword (shown above)
  • renamed vector to vec and value to str to fit naming conventions (shown above)
  • don't return the &str at all because it's pointless. that means you can also remove all the lifetimes
  • in real code, don't even use a function. but it being a function makes it better for learning
  • it's more complex and not necessary (but more performant) if you use &str in your vec instead of String. not relevant to the function though, just thought I'd mention it

Upvotes: 0

Masklinn
Masklinn

Reputation: 42197

I'm wondering if there's anything wrong with my solution that the tutorial takes another way?

I don't think there's anything wrong per se, yours might even match the function name better depending how its interpreted: are you supposed to copy and return the original, or copy and return the copy? Yours is the first pick, theirs is the second.

It's made irrelevant by the lifetimes, but this does make a difference to program behaviour: in the "official" solution, the result is a reference to the value inserted into the Vec, meaning it will be "live" for as long as the vector is (at least assuming the vector is not otherwise modified).

You can see the difference if you replace value: &'a str by value: &'_ str (aka "a lifetime you don't care about but is different from 'a): the official solution still compiles, yours does not.

Though note that the official could just as well return &'a str regardless:

fn copy_and_return<'a>(vector: &'a mut Vec<String>, value: &'_ str) -> &'a str {
    vector.push(String::from(value));
    vector.get(vector.len() - 1).unwrap()
}

The official solution is hardly great through e.g.

vector.get(vector.len() - 1)

is a complicated way of writing

vector.last()

but it might be that they just don't want to overwhelm the reader with advanced APIs or something, I could not say.

Upvotes: 7

Related Questions