String slicing string literal

fn main()
{
    let s1 = String::from("sdasdasd");
    let s2 = &s1[0..=1];
}

With s2 how come it is a string literal and not just some pointer pointing to s1's heap data?

I thought string literals are stored in the binary of the program. But if I am declaring a variable s1 that is storing the data on the heap, how can s2 suddenly store this data in the binary of the program?

s2 is an &str that refers to a portion of s1’s heap buffer, not a literal. What makes you think otherwise?


You can verify this by trying to provide a ’static lifetime annotation to s2:

fn main()
{
    let s1 = String::from("sdasdasd");
    let s2:&'static str = &s1[0..=1];
}
error[E0597]: `s1` does not live long enough
 --> src/main.rs:4:28
  |
4 |     let s2:&'static str = &s1[0..=1];
  |            ------------    ^^ borrowed value does not live long enough
  |            |
  |            type annotation requires that `s1` is borrowed for `'static`
5 | }
  | - `s1` dropped here while still borrowed

error: aborting due to previous error; 1 warning emitted

(Playground)

2 Likes

Just because something is an &str does not mean it's stored in the binary of the program. All &str means is that it is a pointer to some string data somewhere in memory. Of course, when you get it from a string literal, it points into the binary of the program, but it can also point into the heap or even the stack.

2 Likes

Ah right I see.

The important difference between &str and String is that an &str does not have ownership of the data it points at, which means you cannot use it to modify or deallocate the string data. It also means that an &str must not be used after the owner is destroyed, e.g. if you destroy s1 in your example, you can no longer use s2, since s1 owns the string data by virtue of being a String.

Of course, the program binary stays valid for the entire duration of the execution, so a string slice pointing at static memory can stay valid forever.

fn main()
{
    let s1 = String::from("sdasdasd");
    //let s2 = &s1[0..=1];
    let s2 = &s1;
}

Lets say I do this let s2 = &s1; instead of let s2 = &s1[0..=1];, is s2 still an &str?

No, then s2 will be an &String. You can see this in the following way:

fn main() {
    let s1 = String::from("sdasdasd");
    let s2 = &s1;
    
    let () = s2;
}
error[E0308]: mismatched types
 --> src/main.rs:5:9
  |
5 |     let () = s2;
  |         ^^   -- this expression has type `&std::string::String`
  |         |
  |         expected struct `std::string::String`, found `()`

That said, Rust has a feature that automatically converts an &String into an &str when such a conversion is required by the surrounding types.

fn main() {
    let s1 = String::from("sdasdasd");
    // explicitly set the type to &str, triggering the conversion
    let s2: &str = &s1;
    
    let () = s2;
}
error[E0308]: mismatched types
 --> src/main.rs:5:9
  |
5 |     let () = s2;
  |         ^^   -- this expression has type `&str`
  |         |
  |         expected `str`, found `()`

This feature is known as Deref coercion, which you can read about in the Rust book. It is not specific to strings, e.g. it also works for &Vec<u8> → &[u8].

Note that the same conversion can be performed explicitly with as_str:

fn main() {
    let s1 = String::from("sdasdasd");
    // explicitly convert to &str
    let s2 = s1.as_str();
}
1 Like

I see

One thing though, what is the difference between &String and &str?

A &String can only point to a full String, which means that it lacks the following features compared to &str:

  1. Pointing to part of a string, e.g. &s1[3..7].
  2. Pointing to string data in the binary of the executable.
  3. Pointing to string data anywhere else not in a String.

The only thing that &String allows you to, which you can't do through &str is to check how much extra capacity there is left in the String for adding more characters to it. (but you can't actually add any characters, because the reference is immutable)

Generally you never want to use the &String type directly.

1 Like

The short answer is "they are totally different types".

The long answer is to consider first what is the difference between String and str:

  • String is a structure, consisting of three parts: pointer, length and capacity, where the pointer must point on the str, which is length bytes long and is allocated at the beginning of the block of capacity bytes.
  • str is a sequence of bytes, required to be valid UTF-8, with no other requirements.

So:

  • &String is a reference to three-words structure, essentially the pointer to the beginning of this structure.
  • &str is a "fat reference" (containing both pointer and length) to the byte sequence.

As you can see, String can be thought of as "&str with capacity" - that's why &String can be dereferenced to get &str.

2 Likes

This is already answered, but just another side note -- the difference between &Vec<T> and &[T] is analogous.
(Edit: updated image, thank you @steffahn)

2 Likes