What are String and str?

Definitions

Let's start with definitions

String is a growable buffer that owns it's data.
&String is a shared borrow of a growable buffer, this borrow does not own any data
&mut String is a unique borrow of a growable buffer, this borrow does not own any data

str is a view into some other string that is already in memory.
&str is a shared borrow of a string view.
&mut str is a unique borrow of a string view.

String literals

String literals, like "hello world" or "bob" are stored directly into the program binary, and we interact with a shared borrow of a string view to that string. Which, if we look at are definitions above is &str. Only thing missing is the lifetime, because the literals are stored in the program binary, they have a 'static lifetime. Put all of this together and we get &'static str.

Creating a String

You can create a string using some traits that str and String implement ToString::to_string or From<&str>. ToString is implemented on every type that implements Display, so that you can easily create String from those types. Because str and Srring implement Display you get to use ToString. String also implements From<&str> because it can be losslessly converted from a &str in an obvious way.

Less commonly, you may see people use ToOwned to create a String. str: ToOwned<Owned = String>, so you can use string_view.to_owned() to get a String.

Because String: From<&str> you also get Into<String> for &str for free, so you can do string_view.into() to get a string. But this isn't all that great because Rust has a hard time doing type inference with the Into trait, so I would stick to to_string, to_owned, or String::from.

Indexing

String and str can be indexed to produce a string view into their contents using ranges. Like so

let string: String = String::from("Hello World");

let view: str = string[..];

Oh no, this doesn't compile, why?

Well, if we look at our definitions from before we can see that str is a view into some other data. But how big is that view? Can't be known until run-time. So str does not implement Sized. This means that it must always be behind some indirection, like a borrow (& or &mut).

playground

let string: String = String::from("Hello World");

let view: &str = &string[..]; // fixed

Note about your println example, you added a & on your own, so it works. Not magic on the println macro's end.

Deref Coercions

Before we dive into this with String and str, lets look at a simpler example

playground

use std::ops::Deref;

struct Foo;
struct Bar(Foo);

impl Deref for Bar {
    type Target = Foo;
    
    fn deref(&self) -> &Foo {
        &self.0
    }
}

fn main() {
    let bar        :  Bar = Bar(Foo);
    let bar_borrow : &Bar = &bar;
    let foo_borrow : &Foo = bar_borrow;
    let foo_borrow : &Foo = &bar;
}

Witchcraft. How are we converting between types! This has to do with the so called deref coercions. Basically if Rust thinks that you have mismatched borrow types, Rust will try and apply a special coercion, which depends on the Deref and DerefMut traits. This conversion will convert the borrows to borrows of Deref::Target.
String implements Deref<Target = str>, so it can participate in these coercions.

playground

fn main() {
    let string        :  String = String::from("Hello World");
    let string_borrow : &String = &string;
    let view_borrow   : &str    = string_borrow;
    let view_borrow   : &str    = &string;
}

Book Suggestion

The book suggests always taking &str for function arguments because it gives you more ergonomics are flexibility, and I will add only one caveat, if you are going to be storing a String then take a String to avoid unnecessary allocations.

Cow<'a, str>

On the topic of functions, if you are unsure that you will allocate (due to conditional allocations or something similar), and you would like to avoid unnecessary allocations you can use the Cow<'a, str> type. This type can represent both a string view and an owned string.

C++

This is similar to the distinction between string_view and string in C++, which correspond to &str and String in Rust respectively.

24 Likes