String slice (string literal) and `mut` keyword

Hello Rustaceans,

In the rust programming language book, on string slices and string literal, it is said that

   let s = "hello";
  • s is a string literal, where the value of the string is hardcoded directly into the final executable, more specifically into the .text section of our program. (Rust book)
  • The type of s here is &str: it’s a slice pointing to that specific point of the binary. This is also why string literals are immutable; &str is an immutable reference. (Rust Book ch 4.3)

Now one beg the question, how does rustc determine how to move value/data into that memory location associated with a string slice variable if it is marked as mutable?

Imagine you have the following code snippet:

         fn main() {
             let greeting: &'static str = "Hello there";  // string literal
             println!("{greeting}");
             println!("address of greeting {:p}", &greeting);
             // greeting = "Hello there, earthlings"; // ILLEGAL since it's immutable 

             // is it still a string literal when it is mutable?
             let mut s: &'static str  = "hello"; // type is `&'static str`
             println!("s = {s}");
             println!("address of s {:p}", &s);
             // does the compiler coerce the type be &str or String?
             s = "Salut le monde!"; // is this heap-allocated or not? there is no `let` so not shadowing
             println!("s after updating its value: {s}"); // Compiler will not complain
             println!("address of s {:p}", &s);
             // Why does the code above work? since a string literal is a reference. 
             // A string literal is a string slice that is statically allocated, meaning 
             // that it’s saved inside our compiled program, and exists for the entire 
            // duration it runs. (MIT Rust book)

            let mut s1: &str = "mutable string slice";
            println!("string slice s1 ={s1}");
            s1 = "s1 value is updated here";
            println!("string slice after update s1 ={s1}");
         }

if you run this snippet say on Windows 11, x86 machine you can get an output similar to this

         $ cargo run
            Compiling tut-005_strings_2 v0.1.0 (Examples\tut-005_strings_2)
             Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.42s
              Running `target\debug\tut-005_strings_2.exe`
         Hello there
         address of greeting 0xc39b52f410
         s = hello
         address of s 0xc39b52f4c8
         s after updating its value: Salut le monde!
         address of s 0xc39b52f4c8
         string slice s1 =mutable string slice
         string slice after update s1 =s1 value is updated here
  • Why does this code run without any compiler issue?

  • is the variable s, s1 still consider a string literal in that example?

    • if s is a literal, how come at run time, the value in the address binded to s stay the same?

      • maybe the variable of type &str is an immutable reference, is that's why the address stays the same? How about the value to that address? Why does the value/the data content in s or s1 is allowed to change? Does that mean that this string is no longer statically "allocated" into the binary anymore?
  • How are values moved in Rust?

Help, I'm confused.

You have to distinguish the mutability of the variable (which is a reference) from the mutability of the data (the actual string slice). Problem is, everyone including official sources frequently use the term "slice" for convenience when they actually mean "reference to a slice". More confusion is caused by the term "mutable reference" which in fact means "reference through which the referred-to data can be mutated" rather than "reference that can be mutated" (ie. changed to refer to some other data somewhere else).

     let greeting: &'static str = "Hello there";  // string literal
     println!("{greeting}");
     println!("address of greeting {:p}", &greeting);

greeting is an immutable reference that points to immutable data stored in the constant data section of the executable.

     // greeting = "Hello there, earthlings"; // ILLEGAL since it's immutable 

Yes, but perhaps not in the sense that you think. This line would not modify the content of the "Hello there" string; it would only change greeting to point to another string literal stored in the binary. [1]

     // is it still a string literal when it is mutable?
     let mut s: &'static str  = "hello"; // type is `&'static str`

This is still a reference to immutable data; you cannot modify the string "hello" through it. But because the variable (binding) s is mut, you can change where it points to by assigning to it.

    s = "Salut le monde!"; // is this heap-allocated or not? there is no `let` so not shadowing

This changed s to point to yet another statically stored string literal. The string "hello" that it originally pointed to is intact. No heap allocation is involved.

Now, if you try to make an actual reference-to-mutable that points to a string literal:

    let bar: &'static mut str = "Hello there, earthlings"
    //                ^^^--------- note the position of the `mut`

you'll find that the compiler will complain:

3 |     let bar: &'static mut str = "Hello there, earthlings";
  |              ----------------   ^^^^^^^^^^^^^^^^^^^^^^^^^ types differ in mutability
  |              |
  |              expected due to this
  |
  = note: expected mutable reference `&'static mut _`
                     found reference `&'static _`

String literals are intrinsically immutable and must not be touched, and Rust guarantees this by making it impossible to get your hands on a non-mut reference to a string literal.


  1. Theoretically, because "Hello there" is a prefix of "Hello there, earthlings", the compiler could be clever and store only the latter. But it does not, as of now anyway. ↩︎

3 Likes

OMG thank you so much. I like the distinction you make between slice meaning "reference to slice" and "mutable reference". I have a much better understanding of that topic now.

Now I want to gain clarity in one more thing.

You said:

so in chapter 4.1 of Rust programming language dealing with ownership and variable scope, when they show that example:

let s = "hello";

and said:

The variable s refers to a string literal, where the value of the string is hardcoded into the text of our program.

is it hardcoded into the .text or .rodata section? Maybe i misinterpreted the book sentence wrong. (English is my 3rd language).

To my understanding .text and .data or .rodata are different section of a program. Where exactly will the rust compiler save the string literal data ("hello" or "Hello there") in the example I refer to?

Is there a dev tool that will allow me to see and dissect the output of the compiler, the program executable in that case?

To my understanding after your clarification: It my appear the compiler take the immutable string literal data "Hello" might assign an address say 0x1000. The other immutable string literal "Salut le monde!" might be put at address 0x2000 .

.text or .rodata
0x1000: hello
0x2000: Salut le monde!

mutable reference s is an alias to mem address 0xc39b52f4c8 in my output:

then when the code is

.data
0xc39b52f4c8: 0x1000

therefore s = "hello"

and when the code is

.data
0xc39b52f4c8: 0x2000

so the local variable s (stored in stack) is updated to refer to the string "salut le monde!", which lives at the hypothetical address 0x2000.

It will probably be hardcoded into the .rodata section, but the details depend on the architecture-specific compiler backend (usually LLVM).

The literal may be placed into the .text section if that allows for shorter load instructions for the target architecture. If the string is all null bytes it may even be placed into the .bss section to save space in the executable. Or the compiler backend may for other reasons decide to place it into the .data section. You mostly don't need to think about where exactly it's placed. Only that it will be at some address in your program's memory and you're not allowed to modify the contents of the literal without invoking undefined behaviour.

3 Likes

Thank you ! Got it.