Rust - String Methods

Overview

Estimated time: 35–45 minutes

Master essential string manipulation methods in Rust. Learn how to search, modify, format, and transform strings using Rust's rich string API. Understand the difference between methods that work on &str vs String.

Learning Objectives

Prerequisites

String Information Methods

Length and Capacity

Get information about string size and capacity:

fn main() {
    let s = String::from("Hello, 世界!");
    let str_slice = "Hello, world!";
    
    // Length in bytes (not characters!)
    println!("String length: {}", s.len());           // 13 (bytes)
    println!("Str slice length: {}", str_slice.len()); // 13 (bytes)
    
    // Character count (Unicode-aware)
    println!("Character count: {}", s.chars().count()); // 9 characters
    
    // String capacity (only for String, not &str)
    println!("String capacity: {}", s.capacity());     // >= 13
    
    // Check if empty
    println!("Is empty: {}", s.is_empty());            // false
    println!("Empty string: {}", "".is_empty());       // true
}

Checking String Content

Test string content with various predicates:

fn main() {
    let text = "Hello, Rust Programming!";
    
    // Check prefixes and suffixes
    println!("Starts with 'Hello': {}", text.starts_with("Hello"));     // true
    println!("Ends with '!': {}", text.ends_with("!"));                 // true
    println!("Ends with 'Rust': {}", text.ends_with("Rust"));           // false
    
    // Check if string contains substring
    println!("Contains 'Rust': {}", text.contains("Rust"));             // true
    println!("Contains 'Python': {}", text.contains("Python"));         // false
    
    // Character-based checks
    let code = "fn main() { println!(\"Hello\"); }";
    println!("Contains alphabetic: {}", code.chars().any(|c| c.is_alphabetic())); // true
    println!("Contains numeric: {}", code.chars().any(|c| c.is_numeric()));       // false
    println!("All ASCII: {}", code.is_ascii());                                   // true
}

String Searching

Finding Substrings

Search for patterns within strings:

fn main() {
    let text = "The quick brown fox jumps over the lazy dog";
    
    // Find first occurrence
    if let Some(pos) = text.find("fox") {
        println!("Found 'fox' at position: {}", pos);  // 16
    }
    
    // Find last occurrence
    if let Some(pos) = text.rfind("the") {
        println!("Last 'the' at position: {}", pos);   // 31
    }
    
    // Find with closures
    if let Some(pos) = text.find(|c: char| c.is_uppercase()) {
        println!("First uppercase at: {}", pos);       // 0 (T)
    }
    
    // Multiple searches
    for (i, word) in text.split_whitespace().enumerate() {
        if word.contains('o') {
            println!("Word {} contains 'o': {}", i, word);
        }
    }
    // Word 2 contains 'o': brown
    // Word 3 contains 'o': fox
    // Word 6 contains 'o': over
    // Word 9 contains 'o': dog
}

Pattern Matching

Use different types of patterns for searching:

fn main() {
    let text = "[email protected], [email protected]";
    
    // Character patterns
    let at_positions: Vec<_> = text.match_indices('@').collect();
    println!("@ positions: {:?}", at_positions); // [(4, "@"), (17, "@")]
    
    // String patterns
    let domains: Vec<_> = text.matches(|c: char| c.is_alphabetic()).collect();
    println!("First few letters: {:?}", domains.take(5).collect::>());
    
    // Custom patterns
    let emails: Vec<&str> = text.split(", ").collect();
    for email in emails {
        if let Some(domain_start) = email.find('@') {
            let domain = &email[domain_start + 1..];
            println!("Domain: {}", domain);
        }
    }
    // Domain: example.com
    // Domain: test.org
}

String Modification

Case Conversion

Convert string case with Unicode awareness:

fn main() {
    let text = "Hello, Wörld! 你好世界";
    
    // Basic case conversion
    println!("Uppercase: {}", text.to_uppercase());     // HELLO, WÖRLD! 你好世界
    println!("Lowercase: {}", text.to_lowercase());     // hello, wörld! 你好世界
    
    // ASCII-only case conversion (faster)
    let ascii_text = "Hello, World!";
    println!("ASCII upper: {}", ascii_text.to_ascii_uppercase()); // HELLO, WORLD!
    println!("ASCII lower: {}", ascii_text.to_ascii_lowercase()); // hello, world!
    
    // Title case (manual implementation)
    fn to_title_case(s: &str) -> String {
        s.split_whitespace()
            .map(|word| {
                let mut chars = word.chars();
                match chars.next() {
                    None => String::new(),
                    Some(first) => first.to_uppercase().chain(chars.as_str().to_lowercase().chars()).collect(),
                }
            })
            .collect::>()
            .join(" ")
    }
    
    println!("Title case: {}", to_title_case("hello world from rust")); 
    // Title case: Hello World From Rust
}

Trimming Whitespace

Remove whitespace and other characters:

fn main() {
    let messy_text = "  \n\t  Hello, World!  \n\t  ";
    
    // Basic trimming
    println!("Trimmed: '{}'", messy_text.trim());                    // 'Hello, World!'
    println!("Trim start: '{}'", messy_text.trim_start());           // 'Hello, World!  \n\t  '
    println!("Trim end: '{}'", messy_text.trim_end());               // '  \n\t  Hello, World!'
    
    // Custom trimming
    let text_with_dots = "...Hello, World...";
    println!("Trim dots: '{}'", text_with_dots.trim_matches('.'));   // 'Hello, World'
    
    let text_with_chars = "abcHello, Worldcba";
    println!("Trim abc: '{}'", text_with_chars.trim_matches(&['a', 'b', 'c'][..])); 
    // 'Hello, World'
    
    // Trim with predicates
    let text_with_nums = "123Hello, World456";
    println!("Trim digits: '{}'", text_with_nums.trim_matches(|c: char| c.is_numeric())); 
    // 'Hello, World'
}

String Splitting and Joining

Splitting Strings

Break strings into parts using various delimiters:

fn main() {
    let csv_data = "John,25,Engineer,New York";
    let text = "The quick brown fox jumps over the lazy dog";
    
    // Basic splitting
    let fields: Vec<&str> = csv_data.split(',').collect();
    println!("CSV fields: {:?}", fields); 
    // ["John", "25", "Engineer", "New York"]
    
    // Split whitespace
    let words: Vec<&str> = text.split_whitespace().collect();
    println!("Words: {:?}", words); 
    // ["The", "quick", "brown", "fox", "jumps", "over", "the", "lazy", "dog"]
    
    // Split with limit
    let limited: Vec<&str> = csv_data.splitn(3, ',').collect();
    println!("Limited split: {:?}", limited); 
    // ["John", "25", "Engineer,New York"]
    
    // Split by predicate
    let numbers_and_letters = "abc123def456ghi";
    let parts: Vec<&str> = numbers_and_letters.split(|c: char| c.is_numeric()).collect();
    println!("Split by numbers: {:?}", parts); 
    // ["abc", "", "", "def", "", "", "ghi"]
    
    // Split lines
    let multiline = "Line 1\nLine 2\r\nLine 3\n";
    let lines: Vec<&str> = multiline.lines().collect();
    println!("Lines: {:?}", lines); 
    // ["Line 1", "Line 2", "Line 3"]
}

Joining Strings

Combine string parts with separators:

fn main() {
    let words = vec!["Hello", "beautiful", "world"];
    let numbers = vec![1, 2, 3, 4, 5];
    
    // Join with separator
    let sentence = words.join(" ");
    println!("Joined: {}", sentence); // Hello beautiful world
    
    let csv = words.join(",");
    println!("CSV: {}", csv); // Hello,beautiful,world
    
    // Join numbers (convert to strings first)
    let number_string = numbers.iter()
        .map(|n| n.to_string())
        .collect::>()
        .join("-");
    println!("Numbers: {}", number_string); // 1-2-3-4-5
    
    // Join with custom formatting
    let formatted = words.iter()
        .enumerate()
        .map(|(i, word)| format!("{}. {}", i + 1, word))
        .collect::>()
        .join("\n");
    println!("Formatted:\n{}", formatted);
    // 1. Hello
    // 2. beautiful
    // 3. world
}

String Replacement

Simple Replacement

Replace parts of strings with new content:

fn main() {
    let text = "Hello world, wonderful world!";
    
    // Replace first occurrence
    let replaced_once = text.replacen("world", "Rust", 1);
    println!("Replace once: {}", replaced_once); // Hello Rust, wonderful world!
    
    // Replace all occurrences
    let replaced_all = text.replace("world", "Rust");
    println!("Replace all: {}", replaced_all); // Hello Rust, wonderful Rust!
    
    // Replace with closure (for more complex logic)
    let text_with_numbers = "I have 5 cats and 3 dogs";
    let result = text_with_numbers.split_whitespace()
        .map(|word| {
            if word.chars().all(|c| c.is_numeric()) {
                format!("[{}]", word)
            } else {
                word.to_string()
            }
        })
        .collect::>()
        .join(" ");
    println!("Numbers in brackets: {}", result); 
    // I have [5] cats and [3] dogs
}

String Parsing and Conversion

Parsing from Strings

Convert strings to other types:

fn main() {
    let number_str = "42";
    let float_str = "3.14159";
    let bool_str = "true";
    
    // Parse to numbers
    match number_str.parse::() {
        Ok(num) => println!("Parsed number: {}", num), // 42
        Err(e) => println!("Parse error: {}", e),
    }
    
    // Parse to float
    if let Ok(pi) = float_str.parse::() {
        println!("Pi approximately: {:.2}", pi); // 3.14
    }
    
    // Parse to boolean
    if let Ok(flag) = bool_str.parse::() {
        println!("Boolean value: {}", flag); // true
    }
    
    // Parse with error handling
    fn safe_parse(s: &str) -> Option {
        s.parse().ok()
    }
    
    println!("Safe parse: {:?}", safe_parse::("123"));    // Some(123)
    println!("Safe parse: {:?}", safe_parse::("abc"));    // None
}

Unicode and Character Handling

Working with Unicode

Handle Unicode characters properly in string operations:

fn main() {
    let unicode_text = "Hello, 世界! 🦀 Rust";
    
    // Iterate over characters (Unicode-aware)
    println!("Characters:");
    for (i, ch) in unicode_text.chars().enumerate() {
        println!("{}: {} ({})", i, ch, ch as u32);
    }
    
    // Iterate over bytes
    println!("\nBytes:");
    for (i, byte) in unicode_text.bytes().enumerate() {
        println!("{}: {} (0x{:02x})", i, byte, byte);
    }
    
    // Character indices (byte positions)
    println!("\nCharacter indices:");
    for (byte_idx, ch) in unicode_text.char_indices() {
        println!("Byte {}: {}", byte_idx, ch);
    }
    
    // Safe substring extraction
    fn safe_substring(s: &str, start: usize, len: usize) -> Option<&str> {
        let start_byte = s.char_indices().nth(start)?.0;
        let end_byte = s.char_indices().nth(start + len)
            .map(|(idx, _)| idx)
            .unwrap_or(s.len());
        Some(&s[start_byte..end_byte])
    }
    
    if let Some(sub) = safe_substring(unicode_text, 7, 2) {
        println!("Substring: '{}'", sub); // '世界'
    }
}

String Building and Formatting

Efficient String Building

Build strings efficiently for performance:

fn main() {
    // Using String::with_capacity for efficiency
    let mut builder = String::with_capacity(100);
    for i in 1..=10 {
        builder.push_str(&format!("Item {}\n", i));
    }
    println!("Built string:\n{}", builder);
    
    // Using format! macro
    let name = "Alice";
    let age = 30;
    let formatted = format!("Name: {}, Age: {}", name, age);
    println!("{}", formatted);
    
    // Using collect for joining
    let items = vec!["apple", "banana", "cherry"];
    let list = items.iter()
        .map(|item| format!("- {}", item))
        .collect::>()
        .join("\n");
    println!("List:\n{}", list);
}

Common Pitfalls

Mistakes to Avoid

Checks for Understanding

  1. What's the difference between len() and chars().count()?
  2. How do you safely extract a substring from a Unicode string?
  3. When should you use split() vs split_whitespace()?
  4. How can you replace all digits in a string with asterisks?
Answers
  1. len() returns byte count; chars().count() returns Unicode character count
  2. Use char_indices() to find byte positions and slice at character boundaries
  3. Use split() for specific delimiters; split_whitespace() handles all whitespace types and consecutive spaces
  4. Use chars().map(|c| if c.is_numeric() { '*' } else { c }).collect()

← PreviousNext →