Introduction: Why Can't You Just Add Strings in Rust?
\n\nIf you switched to Rust from Python or JavaScript, the first thing that probably shocked you was trying to write "Hello, " + "world!". The compiler greets you with a red squiggly line and a mysterious error message. In the Python world, this would work instantly, but Rust is a different beast. The question "How do I concatenate strings?" on Stack Overflow has garnered hundreds of thousands of views, and this is no coincidence.
The problem is that string concatenation in Rust is not just syntactic sugar. It's a matter of memory management, ownership, and data types. If you are a beginner, you can easily step on classic rakes: lose ownership of a string, accidentally copy gigabytes of data, or simply get confused between String and &str.
In this article, we will break down the 5 most common mistakes that beginners make when joining strings in Rust and show you how to avoid them. You will learn when to use +, when to use format!, and when to use push_str. Let's go!
Mistake #1: Forgetting About Ownership
\n\nWhat It Looks Like
\n\nImagine you write this code:
\n\nlet s1 = String::from("Hello");\nlet s2 = String::from(", world!");\nlet s3 = s1 + &s2; // Error? Or not?\nprintln!("{}", s1); // Compilation error!\n\nYou expect s1 to remain available, but the compiler says: "No, s1 has been moved."
The Essence of the Mistake
\n\nIn Rust, the + operator for strings uses the signature fn add(self, s: &str) -> String. Note the self — this means the first string is consumed and you can no longer use it. You literally "give away" s1 to the add function, and it becomes unavailable.
How to Fix It
\n\nThere are two ways:
\n\n- \n
- Clone the string (if you need the original):
let s3 = s1.clone() + &s2;\n - Change the order or use
format!: \n
let s1 = String::from("Hello");\nlet s2 = String::from(", world!");\nlet s3 = format!("{}{}", s1, s2); // s1 and s2 remain available\nprintln!("{}", s1); // Works!\n\nTip: If you don't plan to use s1 after concatenation, feel free to use +. It is faster than format! because it doesn't create a new buffer from scratch.
Mistake #2: Confusion Between String and &str
\n\nWhat It Looks Like
\n\nYou try to add a String with a literal:
let s = String::from("Hello");\nlet result = s + " world"; // Works!\n\nBut if you try the opposite:
\n\nlet s = "Hello";\nlet result = s + String::from(" world"); // Error!\n\nThe Essence of the Mistake
\n\nThe + operator only works when the left side is a String and the right side is a &str. The literal " world" is automatically a &str, so the first example works. In the second example, s is a &str, which does not have an add method.
How to Fix It
\n\nConvert the &str to a String or use format!:
let s = "Hello";\nlet result = format!("{} {}", s, "world"); // Universal way\n// Or\nlet result = s.to_string() + " world"; // to_string() creates a String\n\nGolden rule: String is an owned type, &str is a borrow. Concatenation always requires ownership on the left.