Mutating a reference in a loop


#1

I have a situation similar to the following -

fn print_length(x: &mut str) {
  while x.len() < 9 {
    x = &mut format!("{}{}", x, "ook"); // temporary value only lives until here
  }
  
  print!("{}", x);
}

I have a function that gets a reference, which a may or may not want to mutate before doing some stuff to it. Rust gives me the “temporary value only lives until here” error message at the marked line.

I can see why it is getting the error. Because it is creating the value and then only saving a reference to it in x there is nothing to fix the actual data in memory - so it gets cleaned up, but then x is just referencing duff memory. What I can’t work out is how to get around the situation. Because x is only a reference, it must always remain a reference. Various attempts to deref it with a new let binding don’t seem to work either…

Any ideas? Thanks.


#2

You can store your formatted string in a binding that you define before the while loop, and then store a reference to that binding in x:

  let mut s;
  while x.len() < 9 {
    s = format!("{}{}", x, "ook"); // temporary value only lives until here
    x = &mut s;
  }

#3

If you may or may not mutate, consider using Cow:

fn print_length(x: &str) -> Cow<str> {
    let mut x = Cow::from(x);
    while x.len() < 9 {
        x = format!("{}{}", x, "ook").into();
    }
    println!("{}", x);
    x // if you want to return it back to the caller as well
}

#4

Hmm… This still gives me an error -

7  |     x = &mut s;
   |              ^ borrowed value does not live long enough
... 

I think because x is a parameter that lives outside this function, but s only has scope within this function… A quick read of the docs implies that I can’t really do what I am trying to do here.


#5

Aha, so using Cow does work. (Great name for a type - I want to use it just so I can talk about returning cows!) I was just reading about Cow earlier today, I’ll have to dig up the reference again.

So I can see the trick here is to wrap in in an object that is not a reference and mutate that. Presumably the x parameter does not get changed, so if the caller needed the longer string they would have to look in the returned Cow and discard x.

Could this get quite expensive though? Supposing the parameter was 1gb in size? Suddenly I would have 2gb strings in memory, and then a 3rd gb string following the format!.. I think… I need to squint a little to get my head around what is happening.


#6

You may need to reborrow x after your definition of s to shorten its lifetime: let x = &mut*x;.


#7

That’s right - x is not changed (and hence can be an immutable borrow).

Caller can discard the Cow, but not without potentially forcing an allocation (in the case when the fn did no mutation).

Parameter stays alive in this function, yes. After the first mutation, the Cow has an owned String. Each subsequent iteration creates a new String and then replaces the one in the Cow - that’ll drop (deallocate) the previous one. In real code, I don’t think you’d build up a String like that. You’d allocate a String, ideally presized to exact or good estimate of final size, and use that. Then you’d return a Cow wrapping it to the caller.

If you want to modify caller’s String, you’d need to take a &mut String parameter, of course. Otherwise, you can’t grow a mutable str slice as it has a fixed length.


#8

@jethrogb’s suggestion is the following but it requires the NLL feature to be enabled (so a nightly compiler):

fn print_length(x: &str) {
    let mut s;
    let mut x = &*x;
    while x.len() < 9 {
        s = format!("{}{}", x, "ook");
        x = &mut s;
    }
    println!("{}", x);
}

#9

Ah ok! I was just composing a reply to say that it was giving me errors.

Yes that does seem to work. I will now go and read about what NLL actually means.

Many thanks for your help!