Strings#

Source:

src/rust/strings

Rust has two main string types: String (owned, heap-allocated, growable) and &str (borrowed string slice). This is similar to C++’s std::string and std::string_view, but Rust strings are always valid UTF-8.

String vs &str#

C++:

#include <string>
#include <string_view>

void print(std::string_view s) {  // borrowed, no copy
  std::cout << s << "\n";
}

int main() {
  std::string owned = "hello";       // owned string
  owned += " world";                 // mutable
  print(owned);                      // implicit conversion
  print("literal");                  // works with literals
}

Rust:

fn print(s: &str) {  // borrowed string slice
    println!("{}", s);
}

fn main() {
    let owned = String::from("hello");  // owned String
    let owned = owned + " world";       // concatenation
    print(&owned);                      // borrow as &str
    print("literal");                   // &str literal
}

Creating Strings#

// From literal
let s1 = String::from("hello");
let s2 = "hello".to_string();
let s3: String = "hello".into();

// Empty string
let s4 = String::new();

// With capacity
let s5 = String::with_capacity(100);

// From format
let name = "world";
let s6 = format!("Hello, {}!", name);

String Operations#

Concatenation:

let s1 = String::from("Hello");
let s2 = String::from(" World");

// Using + (takes ownership of s1)
let s3 = s1 + &s2;  // s1 moved, s2 borrowed

// Using format! (doesn't take ownership)
let s1 = String::from("Hello");
let s4 = format!("{}{}", s1, s2);  // s1 and s2 still valid

// Using push_str
let mut s5 = String::from("Hello");
s5.push_str(" World");

Slicing:

let s = String::from("hello world");

let hello = &s[0..5];   // "hello"
let world = &s[6..];    // "world"
let full = &s[..];      // "hello world"

// Warning: indices are byte positions, not char positions
// Slicing in middle of UTF-8 char will panic

Iteration:

let s = "hello";

// By characters
for c in s.chars() {
    println!("{}", c);
}

// By bytes
for b in s.bytes() {
    println!("{}", b);
}

Common Methods#

let s = String::from("  Hello World  ");

// Trimming
let trimmed = s.trim();           // "Hello World"
let left = s.trim_start();        // "Hello World  "
let right = s.trim_end();         // "  Hello World"

// Case conversion
let upper = s.to_uppercase();     // "  HELLO WORLD  "
let lower = s.to_lowercase();     // "  hello world  "

// Searching
let contains = s.contains("World");           // true
let starts = s.starts_with("  Hello");        // true
let pos = s.find("World");                    // Some(8)

// Replacing
let replaced = s.replace("World", "Rust");    // "  Hello Rust  "

// Splitting
let parts: Vec<&str> = "a,b,c".split(',').collect();  // ["a", "b", "c"]

String vs &str in Function Parameters#

Prefer &str for function parameters when you don’t need ownership:

// Good: accepts both String and &str
fn greet(name: &str) {
    println!("Hello, {}!", name);
}

fn main() {
    let owned = String::from("Alice");
    greet(&owned);     // borrow String as &str
    greet("Bob");      // &str literal directly
}

// Less flexible: only accepts String
fn greet_owned(name: String) {
    println!("Hello, {}!", name);
}

UTF-8 Handling#

Rust strings are always valid UTF-8. This differs from C++ where strings are byte sequences:

let s = "Hello, 世界!";

// Length in bytes vs characters
println!("Bytes: {}", s.len());        // 14 bytes
println!("Chars: {}", s.chars().count()); // 10 characters

// Accessing by index doesn't work directly
// let c = s[0];  // error: cannot index String

// Use chars() or bytes()
let first_char = s.chars().next();     // Some('H')
let first_byte = s.bytes().next();     // Some(72)

Converting Between Types#

// &str to String
let s: &str = "hello";
let owned: String = s.to_string();
let owned: String = s.to_owned();
let owned: String = String::from(s);

// String to &str
let owned = String::from("hello");
let slice: &str = &owned;
let slice: &str = owned.as_str();

// Numbers to String
let n = 42;
let s = n.to_string();
let s = format!("{}", n);

// String to numbers
let s = "42";
let n: i32 = s.parse().unwrap();
let n: i32 = s.parse().expect("not a number");

See Also#