String literals and "from"

As I've been working through the Rust book (and other learning resources) I've seen two ways to initialize a string literal. The most straightforward method looks like this:

let words = "this is the day";

while the second method looks like this:

let words = String::from("this is the day");

There seems to be a preference toward the second method, but both seem to work equally well for me so far. Can someone clarify this for me and maybe point out when one should use one method over the other? Thanks.

The string literal is simply "this is the day" in both cases, with type &'static str. The second one converts that literal to a heap-allocated String, which you may need if you're going to modify that string at runtime, or if you're using it somewhere that otherwise wants an owned string. For example, if you have a string field that is often set dynamically, you probably want a String, but you might initialize that from a static "..." literal.

4 Likes

One trick that can help for things like this is to annotate them with intentionally-wrong types so the compiler says what they are: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=9a68f0c38b731c9bd0e9cbe85ebfeb34

error[E0308]: mismatched types
 --> src/main.rs:2:21
  |
2 |     let words: () = "this is the day";
  |                --   ^^^^^^^^^^^^^^^^^ expected `()`, found `&str`
  |                |
  |                expected due to this

error[E0308]: mismatched types
 --> src/main.rs:3:21
  |
3 |     let words: () = String::from("this is the day");
  |                --   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `()`, found struct `String`
  |                |
  |                expected due to this

That can help you notice the different types that cuviper mentions in their post.

2 Likes

So what it really boils down to is whether or not I want to modify the string (in which case I would want the type String so it would be placed in the heap). Right?

Strings created at run time, e.g. from format!() also need to be heap-allocated, even if you don't modify them later. There's Cow<'static, str> that can hold either compile-time literal or a heap-allocated string.

3 Likes

Mostly. However, you could also have a data structure which contains a String to store dynamic content (e.g. received through the network). There may be cases where you want to set that String field to a constant value. In that case, you also need String::from (or .to_string()), even if none of these Strings ever get modified.


You can use Cow to avoid that, but a given data structure might not do that for whichever reasons (e.g. because setting it to a constant or borrowed value is an exception which doesn't justify the extra runtime distinction).

No. Mutability has nothing to do with heap allocation. The essential difference is that a &str is borrowing, while a String is an owning type. So if you want to hang on to some data yourself, you'll need a String. If you only need a temporary view into some other string, then you can use &str.

1 Like

I would like to give an example where you cannot change a String (at least not from outside a module) but where it may be necessary to use a String rather than &'static str:

mod m {
    pub struct Person {
        name: String,
        age: u16,
    }
    impl Person {
        pub fn new(name: String, age: u16) -> Person {
            Person { name, age }
        }
        pub fn increase_age(&mut self) {
            self.age += 1;
        }
        pub fn name(&self) -> &str {
            &self.name
        }
        pub fn age(&self) -> u16 {
            self.age
        }
    }
}

use m::Person;

fn main() {
    let mut p = Person::new(String::from("Johnny"), 59);
    p.increase_age();
    // Note we cannot change Johnny's name here without creating a new `Person`.
    println!("{} is {} years old now.", p.name(), p.age());
}

(Playground)

Output:

Johnny is 60 years old now.

In the given example above, m::String::new expects the name as a String (and stores it as String). This makes sense if we sometimes read strings from I/O (e.g. file system, text console, or network). Yet we can set the name to a preset constant ("Johnny") in that example. But when we call m::String::new, we have to convert "Johnny" (which is a &str) to a String as that's what we store (and which is what the new function expects to get).

If we would instead have written:

    pub struct Person {
        name: &'static str,
        age: u16,
    }

then we would only be able to store string literals, but not arbitrary input from the network or an external database, for example.

If we would have written:

    pub struct Person<'a> {
        name: &'a str,
        age: u16,
    }

then we introduce a lifetime argument for Person, which means that the Person<'a> can only exist as long as the reference to that stored string is valid. Depending on the application case, this can be very unhandy.

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.