The Error Scenario
If you have experience with JavaScript, Python, or Java, joining two strings with a + operator feels like second nature. However, Rust is much more protective of how memory is handled. You will likely hit a wall the first time you try to concatenate two string literals (&str) using that familiar syntax.
Consider this common snippet that triggers the compiler's wrath:
fn main() {
let hello = "Hello, ";
let world = "world!";
// This will fail to compile
let greeting = hello + world;
println!("{}", greeting);
}
Running cargo build results in this specific error message:
error[E0369]: binary operation `+` cannot be applied to type `&str`
--> src/main.rs:6:26
|
6 | let greeting = hello + world;
| ----- ^ ----- &str
| |
| &str
Why Rust Rejects This
To understand the fix, you need to understand what &str actually is. In Rust, &str is a string slice. It is essentially a 16-byte structure on 64-bit systems, consisting of a pointer to some UTF-8 bytes and a length. Because it is just a view into memory—often read-only memory—it cannot grow.
The + operator in Rust is powered by the Add trait. The standard library only implements this for String + &str, not &str + &str. Here is a simplified look at how that implementation works:
impl Add<&str> for String {
type Output = String;
fn add(mut self, other: &str) -> String {
self.push_str(other);
self
}
}
This reveals a crucial detail. The left-hand side must be an owned String. The operation actually consumes that String, appends the new text to its buffer, and returns it. Since a slice (&str) doesn't own its buffer, it has nowhere to store the extra characters.
Practical Solutions
1. Convert the first string to an owned String
The quickest fix is to turn the first slice into a heap-allocated String. You can do this using .to_string() or String::from(). This creates a buffer that is allowed to grow.
fn main() {
let hello = "Hello, ";
let world = "world!";
// Convert the first part to a String; the second can stay a slice
let greeting = hello.to_string() + world;
println!("{}", greeting);
}
Keep in mind that hello here was a literal. If hello was already an owned String variable, using + would move it, meaning you couldn't use the original variable later in your code.
2. Use the format! macro (The Cleanest Approach)
When you need to combine multiple variables or mix text with numbers, format! is your best friend. It is much more readable than chaining + operators and handles the conversions for you.
fn main() {
let user = "Alice";
let id = 42;
// format! handles &str and integers effortlessly
let message = format!("User: {}, ID: {}", user, id);
println!("{}", message);
}
While format! is slightly slower than manual concatenation due to runtime template parsing, the difference is negligible for most applications.
3. Use push_str for better performance
If you already have a mutable String, don't create new ones with +. Use push_str. This modifies the existing buffer in place, which is significantly more efficient in loops.
fn main() {
let mut buffer = String::with_capacity(50);
buffer.push_str("First ");
buffer.push_str("Second");
println!("{}", buffer);
}
4. Join a collection of strings
If you are dealing with a list or a vector of strings, the join method is the idiomatic choice. It handles the separators between items automatically.
fn main() {
let words = vec!["Rust", "is", "fast"];
let sentence = words.join(" ");
assert_eq!(sentence, "Rust is fast");
}
How to Verify the Fix
After applying one of these methods, check your work with cargo check. It is much faster than a full build. If the compiler is happy, your types are correctly aligned. If you used Method 1, double-check that you don't try to use the first variable again if it was an owned String, as the + operator takes ownership of it.
Performance Considerations
- **Pre-allocation:** If you know you are building a 1MB string, use `String::with_capacity(1_000_000)`. This prevents the CPU from having to re-copy the data every time the buffer gets full.
- **Memory Slices:** Whenever possible, keep data as `&str`. Only convert to `String` when you actually need to modify the text or store it in a struct that requires ownership.
- **Avoid Loops:** Never use `s = s + "more"` inside a large loop. This creates a brand new allocation on every iteration, leading to O(n²) complexity.

