Joining strings with the plus operator
Concatenation means joining two pieces of text into one longer piece. In Rust you can do it with the + operator, but with a rule that surprises many beginners: the left side must be an owned String, and the right side must be a string slice (&str, such as a literal in double quotes).
let message = String::from("I love coding. ") + "It is fun!";
println!("{}", message);
This prints I love coding. It is fun!. String::from(...) creates the owned string on the left, and "It is fun!" is appended to it.
Why the left side must be a String
A string literal like "Hello " is a &str: a read-only view of text stored in the program. It can't grow. A String owns a buffer that can be extended. The + operator takes the String on its left, appends the text on its right to that buffer and hands the same String back as the result. So two literals can't be added directly:
let a = "Hello ";
let b = "world";
let c = a + b;
This fails with error[E0369]: cannot add `&str` to `&str`, and the compiler notes that concatenation requires an owned String on the left. Starting from String::from("Hello ") or a.to_string() fixes it.
The right side is a &str, not a String
The reverse also trips people up. When both values are Strings, the right one needs a & in front:
let a = String::from("Hello ");
let b = String::from("world");
let c = a + &b;
println!("{} {}", c, b);
This prints Hello world world. Without the &, the compiler reports mismatched types with expected `&str`, found `String`. With it, b is only borrowed, so it can still be used afterwards, as the last print shows.
The left side is moved
The left String is consumed by the operation. Its buffer becomes part of the result, so the original variable can't be used afterwards:
let a = String::from("Hello ");
let c = a + "world";
println!("{}", a);
This does not compile: error[E0382]: borrow of moved value: `a`. Use c from then on, or write a.clone() + "world" if you really need both. Ownership is explained in more detail in a later lesson.
Concatenating with variables
Joining string literals is pointless, since you could write the long string directly. Concatenation becomes useful when part of the text comes from a variable:
let favorite_animal = "parrot";
let message = String::from("My favorite animal is the ") + favorite_animal;
println!("{}", message);
It prints My favorite animal is the parrot. Change the value of favorite_animal and the sentence changes with it.
Chaining several pieces
Each + returns a String, which is exactly what the next + needs on its left. So you can chain as many pieces as you like:
let animal = "parrot";
let name = "Andy";
println!("{}", name.to_string() + ", that's a nice " + animal + " you got there!");
It prints Andy, that's a nice parrot you got there!. Here name.to_string() turns the &str into the owned String that must start the chain. Spaces are part of the text and Rust adds none, so put them inside the literals yourself.
Solving the exercise
The exercise gives you name and language, both &str, and asks for a sentence of the form My name is John Doe and I code in Python. Work from left to right:
- Start with an owned string.
String::from("My name is "), with the trailing space inside the quotes. - Add
name. It is a&str, so it can go on the right of+as it is. - Add the middle text.
" and I code in "has a space on both sides, because nothing else will supply them. - Add
language. Then give the whole expression toprintln!as the single argument for"{}".
let name: &str = "John Doe";
let language: &str = "Python";
println!("{}", String::from("My name is ") + name + " and I code in " + language);
The output is My name is John Doe and I code in Python.
Joining numbers into text
The right side of + must be text, so a number has to be converted first. 5.to_string() makes the string "5", and String::from("x=") + &5.to_string() gives x=5. The & is needed because to_string() returns a String, and the right side must be a &str. Rust accepts &String there because it automatically converts a reference to a String into a &str.
Other ways to build text
A long chain of + gets hard to read. Two common alternatives produce the same text:
format!("My name is {} and I code in {}", name, language)builds a newStringfrom a template and doesn't move anything.push_strand+=append to an existingmut Stringin place. Afterlet mut s = String::from("ab");, boths.push_str("cd")ands += "ef"grows, which ends up asabcdef.
Key takeaways
String + &strgives aString. Two&strvalues can't be added.- Start a chain with
String::from(...),.to_string()or.to_owned(). - Put
&before aStringon the right side. - The left
Stringis moved into the result and can't be used again. - Spaces and punctuation must be written into the text by you.
