String vs &str β The Two Faces of Strings Ruby Doesn't Have
The difference between owned and borrowed strings
In Ruby, strings are simple. One String. Create, modify, pass around. In Rust, there are two kinds.
String β owned string
let s = String::from("hello");
let s = "hello".to_string(); // same result
Heap-allocated. Growable. Closest to Ruby's regular String.
&str β borrowed string
let s: &str = "hello"; // string literals are always &str
A reference (slice) to string data somewhere. Can't resize. Closer to Ruby's frozen string.
When to use which
Function parameters β &str
// β
good β accepts both String and &str
fn greet(name: &str) {
println!("Hello, {}!", name);
}
greet("world"); // pass &str
greet(&my_string); // String auto-converts to &str
// β unnecessarily restrictive
fn greet(name: String) { // only accepts String
println!("Hello, {}!", name);
}
Struct fields β String
struct User {
name: String, // needs to own it
}
Conversion
// &str β String
let owned: String = "hello".to_string();
let owned: String = String::from("hello");
let owned: String = "hello".to_owned();
// String β &str
let borrowed: &str = &owned;
let borrowed: &str = owned.as_str();
String concatenation β more verbose than Ruby
Ruby's "Hello, " + name or "Hello, #{name}" is simple. Rust is different.
// format! β equivalent to Ruby's "#{}" / sprintf
let msg = format!("Hello, {}!", name);
// + operator β left side must be String
let msg = String::from("Hello, ") + name;
// push_str β append &str to String
let mut msg = String::from("Hello, ");
msg.push_str(name);
format! works most of the time. Closest to Ruby's string interpolation.
Why two kinds
Performance and ownership. &str points to existing string data without copying. When passing strings to functions, you pass a reference without heap allocation. This is one reason Rust is fast.
Key Points
String = heap allocated, growable / &str = reference, fixed
Convention: take &str in function args β String auto-converts
Struct fields use String β ownership needed
format!() is closest to Ruby string interpolation (#{})
Pros
- ✓ Avoid unnecessary string copies with &str β performance benefit
- ✓ String/&str distinction is a good entry point for understanding ownership
Cons
- ✗ \"hello\".to_string() conversions feel tedious
- ✗ Putting &str in structs requires lifetime annotations β beginner killer