String Trimming
Functional Programming
Tutorial
The Problem
User input, file contents, and network data almost always arrive with unwanted whitespace: leading spaces, trailing newlines, carriage returns from Windows line endings (\r\n). Stripping this noise before parsing or comparison is a universal preprocessing step. A well-designed trim API should be zero-copy (return a slice, not a new allocation), support both sides independently, and allow custom character matching.
🎯 Learning Outcomes
.trim().trim_start() or only the end with .trim_end().trim_matches(pat)&str pointing into the original bytes, not a new allocation.trim_start_matches and .trim_end_matches for prefix/suffix removalCode Example
#![allow(clippy::all)]
// 477. trim(), trim_start(), trim_end()
#[cfg(test)]
mod tests {
#[test]
fn test_trim() {
assert_eq!(" hi ".trim(), "hi");
}
#[test]
fn test_trim_start() {
assert_eq!(" hi ".trim_start(), "hi ");
}
#[test]
fn test_trim_end() {
assert_eq!(" hi ".trim_end(), " hi");
}
#[test]
fn test_trim_matches() {
assert_eq!("##hi##".trim_matches('#'), "hi");
}
#[test]
fn test_trim_slice() {
let s = " hi ";
let t = s.trim();
assert!(t.as_ptr() >= s.as_ptr());
}
}Key Differences
String.trim and String.sub always allocate.trim_matches accepts chars, char arrays, and closures; OCaml's String.trim removes only ASCII whitespace, requiring custom code for other patterns.trim_start/trim_end in the standard library; OCaml requires astring or manual implementation.trim_start_matches vs. strip_prefix**: Rust additionally provides strip_prefix(pat) which removes the prefix exactly once (not greedily), returning Option<&str>.OCaml Approach
OCaml's standard library does not include a trim function until OCaml 4.00's String.trim, which removes ASCII whitespace from both ends:
String.trim " hi " (* "hi" *)
For one-sided trimming, the astring library provides Astring.String.trim_left/trim_right, or you can write it manually:
let ltrim s =
let i = ref 0 in
while !i < String.length s && s.[!i] = ' ' do incr i done;
String.sub s !i (String.length s - !i)
OCaml's String.sub allocates a new string; there is no zero-copy slice type.
Full Source
#![allow(clippy::all)]
// 477. trim(), trim_start(), trim_end()
#[cfg(test)]
mod tests {
#[test]
fn test_trim() {
assert_eq!(" hi ".trim(), "hi");
}
#[test]
fn test_trim_start() {
assert_eq!(" hi ".trim_start(), "hi ");
}
#[test]
fn test_trim_end() {
assert_eq!(" hi ".trim_end(), " hi");
}
#[test]
fn test_trim_matches() {
assert_eq!("##hi##".trim_matches('#'), "hi");
}
#[test]
fn test_trim_slice() {
let s = " hi ";
let t = s.trim();
assert!(t.as_ptr() >= s.as_ptr());
}
}
✓ Tests
Rust test suite
#[cfg(test)]
mod tests {
#[test]
fn test_trim() {
assert_eq!(" hi ".trim(), "hi");
}
#[test]
fn test_trim_start() {
assert_eq!(" hi ".trim_start(), "hi ");
}
#[test]
fn test_trim_end() {
assert_eq!(" hi ".trim_end(), " hi");
}
#[test]
fn test_trim_matches() {
assert_eq!("##hi##".trim_matches('#'), "hi");
}
#[test]
fn test_trim_slice() {
let s = " hi ";
let t = s.trim();
assert!(t.as_ptr() >= s.as_ptr());
}
}
Exercises
normalize_line_ending(s: &str) -> &str that removes a trailing \r\n or \n, using strip_suffix rather than trim_end_matches.trim_chars<'a>(s: &'a str, chars: &[char]) -> &'a str using trim_matches with a closure that checks membership in the chars slice.criterion to verify that trimming a 1000-byte string with .trim() performs zero heap allocations compared to a String::from(s.trim()).