Why doesn’t modifying b_str affect str_literal in this Rust code?

&str is copyable but also a reference; hence, c_str, although copied, still references str_literal. But what about b_str? It is also a reference to the same slice, yet modifying b_str does not affect str_literal or c_str. Is there a specific concept that explains this behavior?

fn main() {
        let str_literal: &str = "abc";
        
        let b_str: &str = &*str_literal;
        
        let c_str : &str = str_literal;
    
        let b_str = "xyz";
    
        println!("b_str: {}", b_str);
        println!("c_str: {}", c_str);
        println!("str_literal: {}", str_literal);


       
        // b_str: xyz
        // c_str: abc
        // str_literal: abc
    }

Does the above code essentially mean this?


fn main() {
        let str_literal: &str = "abc";
        
        let b_str: &str = "abc";
        
        let c_str : &str = str_literal;
    
        let b_str = "xyz";
    
        println!("b_str: {}", b_str);
        println!("c_str: {}", c_str);
        println!("str_literal: {}", str_literal);


       
        // b_str: xyz
        // c_str: abc
        // str_literal: abc
    }

you only modified the reference (i.e. pointers), not the underlying string data.

strings in rust are immutable, you cannot mutate strings in place, you must convert it to bytes to make changes, but convertion the modified bytes back to string needs to be validated for utf8 encoding.

You aren’t even modifying the reference b_str; you’re introducing a completely new variable that just happens to shadow the existing variable of the same name.

This is essentially the same as

 let b_str: &str = &*str_literal;
        
 let c_str : &str = str_literal;
    
 let foo_bar = "xyz";

as far as the compiler is concerned.

5 Likes

You can, though it's limited and rare. And doesn't apply to literals, which are &str, not &mut str.

2 Likes

A good way to see this difference is to run this: https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=1ac5a8efe1fbaf856385ad6319df7ada

fn main() {
    let str_literal: &str = "abc";

    let b_str: &str = &*str_literal;

    let c_str: &str = str_literal;

    {
        let b_str = "xyz";

        println!("b_str: {}", b_str);
        println!("c_str: {}", c_str);
        println!("str_literal: {}", str_literal);
    }

    println!("b_str: {}", b_str);
}

You'll see

b_str: xyz
c_str: abc
str_literal: abc
b_str: abc

because that original b_str still exists, you just hid it in the original example.

1 Like

nice!

Don't know if it helps; you can print out the addresses of the different things:

fn main() {
    let a = "Hello, Rust!";
    let a_adr = &raw const a;
    let a_str_adr = &raw const *a;
    println!("a_adr: {a_adr:?}, a_str_adr: {a_str_adr:?}");

    let b = a;
    let b_adr = &raw const b;
    let b_str_adr = &raw const *b;
    println!("b_adr: {b_adr:?}, b_str_adr: {b_str_adr:?}");

    let a = "Hello, World";
    let a_adr = &raw const a;
    let a_str_adr = &raw const *a;
    println!("a_adr: {a_adr:?}, a_str_adr: {a_str_adr:?}");

    let a = b;
    let a_adr = &raw const a;
    let a_str_adr = &raw const *a;
    println!("a_adr: {a_adr:?}, a_str_adr: {a_str_adr:?}");
}
  • x_adr - The address of the variable
  • x_str_adr - The address of the string data (characters)

You can even see two different memory regions, where things are stored.

1 Like

Be cautious to not make too many assumptions about what you see from this though, in particular anything about optimization or memory layout!