ExamplesBy LevelBy TopicLearning Paths
479 Fundamental

String Replacing

Functional Programming

Tutorial

The Problem

Sanitising input, normalising separators, redacting sensitive data, and applying text patches all require replacing substrings. The key design decisions are: replace all occurrences vs. a fixed count; return a new string vs. modify in-place; match on exact substring vs. a predicate. Rust's replace API covers all these cases with allocating (replace/replacen) and in-place (retain) variants.

🎯 Learning Outcomes

  • • Replace all occurrences of a pattern with .replace(from, to) returning a new String
  • • Limit replacements with .replacen(from, to, n)
  • • Filter characters in-place with String::retain(|c| predicate)
  • • Understand that replace always allocates a new string even if no substitution occurs
  • • Recognise the pattern pat can be a char, &str, or closure in both replace and retain
  • Code Example

    #![allow(clippy::all)]
    // 479. replace(), replacen()
    
    #[cfg(test)]
    mod tests {
        #[test]
        fn test_replace_all() {
            assert_eq!("aabaa".replace('a', "x"), "xxbxx");
        }
        #[test]
        fn test_replacen() {
            assert_eq!("aabaa".replacen('a', "x", 2), "xxbaa");
        }
        #[test]
        fn test_no_match() {
            assert_eq!("hello".replace("xyz", "abc"), "hello");
        }
        #[test]
        fn test_retain() {
            let mut s = String::from("h3llo");
            s.retain(|c| c.is_alphabetic());
            assert_eq!(s, "hllo");
        }
    }

    Key Differences

  • Standard library: Rust's replace/replacen are on str with no imports; OCaml requires Str (a separate library) or Re (third-party).
  • **In-place retain**: Rust's retain modifies a String without reallocating (if possible); OCaml always allocates a new string for filtered results.
  • **replacen**: Rust has a n-replacement limit built in; OCaml's Str has replace_first (one) and global_replace (all) but no direct n-replacement.
  • Pattern types: Rust's replace accepts char, &str, &[char], and closures; Str.global_replace requires a compiled Str.regexp.
  • OCaml Approach

    OCaml's standard library has no replace; the Str module provides:

    Str.global_replace (Str.regexp_string "a") "x" "aabaa"  (* "xxbxx" *)
    Str.replace_first  (Str.regexp_string "a") "x" "aabaa"  (* "xabaa" *)
    

    For replacen behaviour, manual recursion or String.concat is needed. In-place filtering:

    let retain pred s =
      String.concat "" (List.filter_map
        (fun c -> if pred c then Some (String.make 1 c) else None)
        (List.of_seq (String.to_seq s)))
    

    Full Source

    #![allow(clippy::all)]
    // 479. replace(), replacen()
    
    #[cfg(test)]
    mod tests {
        #[test]
        fn test_replace_all() {
            assert_eq!("aabaa".replace('a', "x"), "xxbxx");
        }
        #[test]
        fn test_replacen() {
            assert_eq!("aabaa".replacen('a', "x", 2), "xxbaa");
        }
        #[test]
        fn test_no_match() {
            assert_eq!("hello".replace("xyz", "abc"), "hello");
        }
        #[test]
        fn test_retain() {
            let mut s = String::from("h3llo");
            s.retain(|c| c.is_alphabetic());
            assert_eq!(s, "hllo");
        }
    }
    ✓ Tests Rust test suite
    #[cfg(test)]
    mod tests {
        #[test]
        fn test_replace_all() {
            assert_eq!("aabaa".replace('a', "x"), "xxbxx");
        }
        #[test]
        fn test_replacen() {
            assert_eq!("aabaa".replacen('a', "x", 2), "xxbaa");
        }
        #[test]
        fn test_no_match() {
            assert_eq!("hello".replace("xyz", "abc"), "hello");
        }
        #[test]
        fn test_retain() {
            let mut s = String::from("h3llo");
            s.retain(|c| c.is_alphabetic());
            assert_eq!(s, "hllo");
        }
    }

    Exercises

  • Template engine: Write render(template: &str, vars: &HashMap<&str, &str>) -> String that replaces {{key}} placeholders using replace in a loop over the map.
  • Redact emails: Write redact_emails(text: &str) -> String using a Regex (from the regex crate) to replace email addresses with [REDACTED].
  • Benchmark retain vs. filter+collect: Use criterion to compare s.retain(|c| c.is_alphanumeric()) against s.chars().filter(|c| c.is_alphanumeric()).collect::<String>() on a 10KB string.
  • Open Source Repos