8

I'm trying to build a string by appending to it inside a for loop. For some reason the string gets moved into the for loop and I can't get it to work. I'm obviously missing something. Here's a short code snippet that exhibits this behaviour:

fn main() {
  let s = format!("some string");
  for x in vec!(1,2).move_iter() {
    s.append("some other string");
  }
}

I get the following error from the compiler (rustc 0.11.0-pre (c0a6f72 2014-06-12 14:17:13 +0000)):

test.rs:4:9: 4:10 error: use of moved value: `s`
test.rs:4         s.append("some other string");
                  ^
test.rs:4:9: 4:10 note: `s` moved here because it has type `collections::string::String`, which is non-copyable (perhaps you meant to use clone()?)
test.rs:4         s.append("some other string");
                  ^
error: aborting due to previous error
ujh
  • 4,023
  • 3
  • 27
  • 31

2 Answers2

12

Update: In the latest Rust (as of 1.0.0-alpha) append() method does no longer exist. However, both String and Vec have + method overloaded, and it behaves exactly like the old append():

let s = String::new() + "some part" + "next part";

Operators always take their operands by value, so this does not cause unnecessary reallocations. The right operand must be &str for String or &[T] for Vec<T>; in the latter case, T must be Clone.

Anyway, push_str() is still available.


The reason you're getting this error is that you're using the wrong method :)

String::append() method is builder-like; it is supposed to be used like this:

let s = String::new()
    .append("some part")
    .append("next part");

In fact, you will be able to make use of it in your code:

let mut s = "some string".to_string();
for &x in [1, 2].iter() {
    s = s.append("some other string");  // note the reassignment
}

This happens because append() has this signature:

fn append(self, other: &str) -> String { ... }

That is, it takes the receiver by value, and it is moved into the call. It allows easy chaining, but is somewhat inconvenient if you need to modify existing variable.

The method you're looking for is called push_str():

let mut s = "some string".to_string();
for &x in [1, 2].iter() {
    s.push_str("some other string");
}

It just appends passed string slice to the existing String. Note that you also have to mark s as mutable. You also don't need to allocate new vector using vec!(), static array will suffice here.

That said, it is better to avoid mutation completely if possible. @A.B.'s suggestion to use fold() is absolutely right.

Sebastián Palma
  • 32,692
  • 6
  • 40
  • 59
Vladimir Matveev
  • 120,085
  • 34
  • 287
  • 296
  • Thank you. I will go with push_str for now and see if I can convert the code to a more functional style to avoid the use of mut later on. – ujh Jun 13 '14 at 09:42
7

The function signature of append is

fn append(self, second: &str) -> String

self is passed by-value, which means that if self implemented the Copy trait, it would be copied, otherwise it would be moved. String doesn't implement Copy, therefore it will be moved.

A fold operation can hold the string between the appends:

let s = format!("some string");
let s = vec!(1,2).iter()
    .fold(s, |s, num| s.append("some other string"));
println!("{}",s);

I'm assuming that ultimately you want to read strings from the vector. If you're just trying to repeat an operation several times, you should use range(0, n) or std::iter::Repeat::new("some other string").take(n) as iterator to do the folding on.

A.B.
  • 15,364
  • 3
  • 61
  • 64
  • I will see if I can adapt the code to use fold as that looks neater than a normal for loop. – ujh Jun 13 '14 at 09:39