How to build a string from parts
Short answer: Three ways, and the difference is what each one consumes. + takes an owned
String on the left and a &str on the right, and moves the left one into the result. push_str
appends to a String you keep. format! builds a fresh String from any values that implement
Display, and consumes none of them.
From a fresh project — cargo new string-demo, then replace src/main.rs with the whole of each
block below. The project name doesn’t matter; nothing in the output depends on it.
The + operator
Section titled “The + operator”fn main() { let hello = String::from("hello"); let greeting = hello + " world";
println!("{greeting}");}hello world+ is the only one of the three that consumes anything. The left operand moves into the result,
so hello is gone afterwards:
fn main() { let hello = String::from("hello"); let greeting = hello + " world"; println!("{greeting} {hello}");}error[E0382]: borrow of moved value: `hello` --> src/main.rs:4:27 |2 | let hello = String::from("hello"); | ----- move occurs because `hello` has type `String`, which does not implement the `Copy` trait3 | let greeting = hello + " world"; | ---------------- `hello` moved due to usage in operator4 | println!("{greeting} {hello}"); | ^^^^^ value borrowed here after move |note: calling this operator moves the left-hand side --> /rustc/b940084d7eb6a299eb4bfeb8e34901bc051e7ac4/library/core/src/ops/arith.rs:91:11help: consider cloning the value if the performance cost is acceptable |3 | let greeting = hello.clone() + " world"; | ++++++++
For more information about this error, try `rustc --explain E0382`.The right side is a &str. A literal already is one; an owned String needs a borrow:
fn main() { let hello = String::from("hello"); let world = String::from(" world"); let greeting = hello + &world;
println!("{greeting}");}hello worldForget the & and the error names the type it wanted:
fn main() { let hello = String::from("hello"); let world = String::from(" world"); let greeting = hello + world; println!("{greeting}");}error[E0308]: mismatched types --> src/main.rs:4:28 |4 | let greeting = hello + world; | ^^^^^ expected `&str`, found `String` |help: consider borrowing here |4 | let greeting = hello + &world; | +
For more information about this error, try `rustc --explain E0308`.And two &str slices never concatenate, because there is no owned buffer on the left to grow:
fn main() { let greeting = "hello" + " world"; println!("{greeting}");}error[E0369]: cannot add `&str` to `&str` --> src/main.rs:2:28 |2 | let greeting = "hello" + " world"; | ------- ^ -------- &str | | | | | `+` cannot be used to concatenate two `&str` strings | &str | = note: string concatenation requires an owned `String` on the lefthelp: create an owned `String` from a string reference |2 | let greeting = "hello".to_owned() + " world"; | +++++++++++
For more information about this error, try `rustc --explain E0369`.That last error is the whole rule of + in the compiler’s own words: owned on the left, borrowed
on the right.
push_str
Section titled “push_str”push_str appends a &str to a String you already own, in place:
fn main() { let mut greeting = String::from("hello"); greeting.push_str(" world"); greeting.push('!');
println!("{greeting}");}hello world!The binding needs mut, and the method borrows the String, so it’s still yours afterwards — as
many appends as you like, in a loop or not. push takes a single char. How the buffer grows, and
how to avoid the reallocations, is the next article in this chapter.
format!
Section titled “format!”format! is println! that returns a String instead of printing it:
fn main() { let first = String::from("Hello"); let last = "Lovelace"; let year = 1843; let line = format!("{first} {last}, {year}");
println!("{line}");}Hello Lovelace, 1843Every {} in the format string borrows its value, so nothing is consumed and first is still a
String afterwards. Any type that implements Display can go in a {}, numbers and bools
included — the same set to_string works on, compared
here. Each call allocates a fresh String,
so building text one piece at a time in a loop belongs to push_str. Named arguments and inline
captures are the formatting mini-language’s own article, later in this chapter.
Which one to reach for
Section titled “Which one to reach for”- One
Stringplus one&str, and you don’t need theStringafterwards:+. - A
Stringyou’re building up over several steps:push_str. - Several values of different types, or any of them needed later:
format!.
Related
Section titled “Related”- What is the difference between String and &str? — the owned and borrowed split behind all three spellings
- When to use String::from, to_string, or into? —
the other ways to get a
String, and whatDisplayhas to do with it - Why can’t you use a value after moving it? —
the move
+performs on its left operand - Working with text — the rest of this chapter