ExamplesBy LevelBy TopicLearning Paths
477 Fundamental

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

  • • Remove leading and trailing whitespace with .trim()
  • • Strip only the start with .trim_start() or only the end with .trim_end()
  • • Remove custom characters or patterns with .trim_matches(pat)
  • • Understand that trim returns a &str pointing into the original bytes, not a new allocation
  • • Use .trim_start_matches and .trim_end_matches for prefix/suffix removal
  • Code 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

  • Zero-copy: Rust's trim returns a borrowed slice with no allocation; OCaml's String.trim and String.sub always allocate.
  • Pattern flexibility: Rust's trim_matches accepts chars, char arrays, and closures; OCaml's String.trim removes only ASCII whitespace, requiring custom code for other patterns.
  • One-sided trimming: Rust has 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

  • Strip CRLF: Write normalize_line_ending(s: &str) -> &str that removes a trailing \r\n or \n, using strip_suffix rather than trim_end_matches.
  • Trim custom set: Write trim_chars<'a>(s: &'a str, chars: &[char]) -> &'a str using trim_matches with a closure that checks membership in the chars slice.
  • Allocation count: Use criterion to verify that trimming a 1000-byte string with .trim() performs zero heap allocations compared to a String::from(s.trim()).
  • Open Source Repos