Skip to content
Select theme

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.

hello world

+ is the only one of the three that consumes anything. The left operand moves into the result, so hello is gone afterwards:

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` trait
3 | let greeting = hello + " world";
| ---------------- `hello` moved due to usage in operator
4 | 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:11
help: 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:

hello world

Forget the & and the error names the type it wanted:

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:

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 left
help: 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 appends a &str to a String you already own, in place:

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! is println! that returns a String instead of printing it:

Hello Lovelace, 1843

Every {} 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.

  • One String plus one &str, and you don’t need the String afterwards: +.
  • A String you’re building up over several steps: push_str.
  • Several values of different types, or any of them needed later: format!.