Strings and Text Processing

remove() and retain()

String provides several in-place removal methods—.pop(), .remove(idx), .truncate(new_len), .clear(), and .retain(predicate)—that delete characters without reallocating the underlying heap buffer.

Summary of In-Place Removal Methods on String

Method Signature Time Complexity Behavior
s.pop() fn pop(&mut self) -> Option<char> $O(1)$ Removes and returns the last char, or None if empty
s.remove(idx) fn remove(&mut self, idx: usize) -> char $O(n)$ Removes and returns the char at byte offset idx, shifting trailing bytes left (panics if not on a char boundary)
s.retain(f) fn retain<F: FnMut(char) -> bool>(&mut self, f: F) $O(n)$ Keeps only chars where f(ch) returns true in a single linear pass
s.truncate(len) fn truncate(&mut self, new_len: usize) $O(1)$ Shortens string to new_len bytes (panics if new_len is not on a char boundary)
s.clear() fn clear(&mut self) $O(1)$ Sets len = 0 while keeping allocated capacity

Removing Specific Characters with .pop() and .remove()

fn main() {
    let mut code = String::from("#A9F!");

    // 1. Remove the last character in O(1):
    let last = code.pop();
    println!("Popped: {last:?}, remaining: '{code}'");

    // 2. Remove the first character at byte index 0 (shifts "A9F" left by 1 byte):
    let first = code.remove(0);
    println!("Removed at 0: '{first}', remaining: '{code}'");
}
Popped: Some('!'), remaining: '#A9F'
Removed at 0: '#', remaining: 'A9F'

Linear-Time In-Place Filtering with .retain()

Calling s.remove(idx) repeatedly in a loop is $O(n^2)$ because every removal shifts the tail of the string. By contrast, s.retain(|ch| ...) filters the entire String in place in $O(n)$ time with zero heap allocations:

fn main() {
    let mut phone = String::from("+1 (800) 555-0199");

    // Keep only ASCII digits in place (O(n) single pass, no reallocation):
    phone.retain(|ch| ch.is_ascii_digit());

    println!("Digits only: {phone}");
}
Digits only: 18005550199