Dumb question #2: is there any time you want a variable to be mutable, but borrowed?

Strings are probably the most common variable in programming languages
and having played around in Rust for a bit, I notice that I never see this line:

let mut s: &str = "Hello  world!";

Is that specific to &str/String or all throughout programming?

that's a type error, a string literal has type &'static str, in rust shared reference (i.e. immutable borrow) cannot be casted to exclusive reference (i.e. mutable borrow).

it's possible to get a &mut str, for example, see String::as_mut_str(), but &mut str has very limited use case. if you don't know about it, you probably don't need it.

2 Likes

You never see that line because it isn't valid Rust syntax.

If you mean

let mut s: &str = "Hello  world!";

then that isn't written because it isn't terribly useful. You can replace the string in s with a new &str, but not much else.

If you mean

let s: &mut str = "Hello world";

then that is a type error, since string literals have type &str because they are stored in static memory, which is usually read-only.

1 Like

My apologies for my bad code.
I meant the former.

Well what if I want to use it like this?
Where I have a child struct that only has one method
where the parent String object is given a non-empty value?

fn main() {
   let p :Parent = Parent { 
       field: String::new(), 
   };
   p.get_child().method();
   
   assert_eq!(p.field, "new_text");
}

struct Parent {
field: String
} 

impl Parent {
    fn get_child(&mut self) -> Child<'_> {
        Child {
            (&mut self.field)
        }
    }
}

struct Child<'a> {
     field: &'a mut str 
}

impl Child {
    fn method(&self) {
       self.field = "new_text";
    }
}

You can do that, but why? Most algorithms that are usable with strings don't work with fixed-length str, especially if we are talking Unicode where 𝒜 is four-bytes string and ℬ is three-bytes string.

Most of the time you want mutable String, not &mut str.

1 Like

fn get_child(&self) … &mut self.field is not going to work as you cannot produce an exclusive reference given only a shared one. Aside from that &'a mut str in Child is not terribly useful due to the issue pointed to by @khimru. There is one case when you might actually want not very useful &'a mut str: it is when what you care is not mutability, but exclusivity: i.e. when you want existence of Child make Parent inaccessible.

1 Like

Does that mean that the moment the method becomes like this:

impl Child {
    fn method(text: &str) {
       self.field = text;
    }
}

I would need to use String for the field because text won't be fixed length anymore,
except for in the method itself and so it would need to rewritten to this:

struct Child<'a> {
     field: &'a mut String
}

impl Child {
    fn method(text: &str) {
       self.field = String::from(text);
    }
}

Which means that the time you want a variable to be mutable, but borrowed,
is when you know that your variable will change in value,
but that it's memory allocated size remains fixed?

And because Strings often change in memory allocated size,
not just in string length, but even within characters themselves,
it really rarely happens with Strings?

Assignment like self.field = text will not mutate string behind self.field reference, it will change the reference. Your code should look like

struct Child<'a> {
  field: &'a mut String
}
impl<'a> Child<'a> {
  fn method(&mut self, text: &str) {
    *self.field = text.into()
  }
}

(note the asterisk). If you have &'a mut str you can replace string behind the reference with string which has equal length in bytes, and you will have to do that using more complicated code then just assignment to *self.field: unsized types like str are pain to work with.

Which means that the time one want a variable to be mutable, but borrowed,
is when you know that your variable will change,
but that the memory allocated size remains fixed?

You want that when you want the ability to change or when you want to prevent access: mutable references are exclusive references. Size of data directly behind reference being fixed is more of a technical limitation that you might not really want, but can’t ignore.

And because Strings often change in memory allocated size,
not just in string length, but even within characters themselves,
it really rarely happens with Strings?

I would say that fixed-size strings are uncommon enough to have no convenient options for mutating them.

1 Like

After fixing your code your method would look like this

impl Parent {
    fn get_child(&self) -> Child<'_> {
        Child {
            field:&mut self.field
        }
    }
}

This code will not work because you use &self. This means you are borrowing self immutably so self cannot change. In this line you are borrowing field of parent mutably and then putting that in child. This means field of parent can change though child.
When field of parent changes it means that parent also changes but remember you used &self guaranteeing to compiler that parent is not going to change. Due to this compiler throws a error.

1 Like

Ah yes, it needs &mut self in the method for it to work
as I'm mutating the value of the Parent instance,
by mutating the field instance within it.
Thanks for pointing that out.

impl Parent {
    fn get_child(&mut self) -> Child<'_> {
        Child {
            field: &mut self.field
        }
    }
}

Mutating &str only allows you to set it to other pre-existing strings (with compatible lifetime), or parts of those.

You could for example swap between two strings

let mut s: &str = "Hello  world!";
let mut t: &str = "Bye  world!";
for _ in 0..10 {
    std::mem::swap(&mut s, &mut t);
    println!("{s} .. {t}");
}

If there’s more than a handful of strings you want to operate between, you won’t see your line anymore though, because they would be in a data structure. E.g. a lot of &strs could be in a vector, which would reasonably be mutable if you wanted to do things like sorting it

let mut v: Vec<&str> = …;
v.sort();

which is an indirect way of having something like (a lot of) mutable &str.


If you only have a single string s, then all you can mutate it is to substrings of itself. Also, mutable variables in Rust for single transformation steps aren’t all that common. For example if you have

let mut s: &str = "  Hello World!  \n\n";

then you could do

let mut s: &str = "  Hello World!  \n\n";
s = s.trim();
println!("{s}");

but this would be more commonly written using shadowing and a new immutable variable of the same bname

let s: &str = "  Hello World!  \n\n";
let s = s.trim();
println!("{s}");

Where mutable variables are super useful is for loops. But when do you ever have a single (of few) strings, and want to make them shorter and shorter subsequences in a loop? Not so common, so you don’t see it. We can of course make a toy example nonetheless:

fn print_shrinking_initial_substrings(full_string: &str) {
    let mut s: &str = full_string;
    while !s.is_empty() {
        println!("{}", s);
        s = &s[..s.char_indices().next_back().unwrap().0];
    }
}

print_shrinking_initial_substrings("Hello, 世界!");
3 Likes

Parsers have a use for repeatedly removing the beginning of a string as they split it into tokens. (I don't know how common it is to use an actual &str vs. just storing a current index as a usize, though.)

2 Likes