479: replace(), replacen()
Difficulty: 1 Level: Beginner Replace substrings and filter characters โ with the same flexible pattern system.The Problem This Solves
Python's `str.replace(old, new, count)` and JavaScript's `str.replace()`/`str.replaceAll()` are fundamental. Rust's `.replace()` and `.replacen()` cover the same ground. What makes Rust distinctive here is consistency: the same pattern system from `.find()`, `.split()`, and `.contains()` works for `.replace()` too. You can replace a `char`, a `&str`, or use a closure as the pattern. `s.replace(|c: char| c.is_ascii_digit(), "")` removes all digits โ without a regex crate. This composability is Rust's design philosophy: one pattern trait, used everywhere. There's also `.retain()` โ an in-place character filter that modifies the `String` directly. Python's equivalent would be `"".join(c for c in s if keep(c))`, which allocates a new string. `retain()` modifies in place, keeping the same allocation.The Intuition
`.replace(pat, replacement)` scans for all matches of `pat` and returns a new `String` with each occurrence replaced. The original is unchanged (immutable borrow). `.replacen(pat, replacement, n)` limits to the first `n` occurrences โ like Python's `s.replace(old, new, count)`. `.retain(|c: char| ...)` is different: it modifies the string in place, keeping only characters where the closure returns `true`. No new allocation. Think of it as an in-place filter for characters. Key mental model: `replace` = new string, never touches the original. `retain` = mutates in place.How It Works in Rust
let s = "Hello, World! Hello, Rust!";
// replace โ all occurrences, returns new String
s.replace("Hello", "Hi"); // "Hi, World! Hi, Rust!"
s.replace('!', "."); // char pattern โ replaces all '!'
// replacen โ first n occurrences only
s.replacen("Hello", "Hi", 1); // "Hi, World! Hello, Rust!" โ only first
// Original is unchanged
let orig = String::from("hello world");
let modified = orig.replace("world", "rust");
println!("{}", orig); // "hello world" โ untouched
println!("{}", modified); // "hello rust"
// retain โ in-place character filter (no new allocation)
let mut s2 = String::from("h3ll0 w0rld");
s2.retain(|c| !c.is_ascii_digit());
// s2 = "hll wrld"
// Pattern as closure โ remove all non-alphanumeric
let mut slug = String::from("Hello, World! 2024");
slug.retain(|c| c.is_alphanumeric() || c == '-' || c == ' ');
// "Hello World 2024"
// Chain replacements (each returns a new String)
"foo bar foo"
.replace("foo", "qux")
.replace("bar", "quux")
// "qux quux qux"
What This Unlocks
- Text sanitization โ remove or replace forbidden characters with `retain()` or `replace()`.
- Slug generation โ chain `replace` calls to normalize text for URLs or filenames.
- Template expansion โ `replace("{{name}}", actual_name)` for simple template rendering.
Key Differences
| Concept | OCaml | Rust | ||
|---|---|---|---|---|
| Replace all | Custom `replace_all` (Buffer loop) | `s.replace(pat, rep)` | ||
| Replace first N | Custom loop | `s.replacen(pat, rep, n)` | ||
| Replace char | `String.map` | `s.replace('c', "rep")` | ||
| In-place filter | `Bytes.map` (not truly in-place) | `s.retain(\ | c\ | ...)` |
| Pattern types | Char or manual | `char`, `&str`, `&[char]`, closure | ||
| Returns | New string | New `String` (original unchanged) |
// 479. replace(), replacen()
fn main() {
let s = "Hello, World! Hello, Rust!";
println!("{}", s.replace("Hello", "Hi")); // all
println!("{}", s.replacen("Hello", "Hi", 1)); // first only
println!("{}", s.replace('!', ".")); // char pattern
println!("{}", "a,,b,,c".replace(",,", ",")); // multi-char
// replace returns new String โ original unchanged
let orig = String::from("hello world");
let modified = orig.replace("world", "rust");
println!("orig: {}, modified: {}", orig, modified);
// retain โ in-place filter
let mut s2 = String::from("h3ll0 w0rld");
s2.retain(|c| !c.is_ascii_digit());
println!("retain: {}", s2);
// Chain replacements
println!("{}", "foo bar foo".replace("foo","qux").replace("bar","quux"));
}
#[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"); }
}
(* 479. String replacing โ OCaml *)
let replace_all s pat rep =
let lp=String.length pat and ls=String.length s in
let buf=Buffer.create ls in
let i=ref 0 in
while !i <= ls-lp do
if String.sub s !i lp = pat
then (Buffer.add_string buf rep; i:= !i+lp)
else (Buffer.add_char buf s.[!i]; incr i)
done;
while !i < ls do Buffer.add_char buf s.[!i]; incr i done;
Buffer.contents buf
let () =
let s = "Hello, World! Hello, OCaml!" in
Printf.printf "%s\n" (replace_all s "Hello" "Hi");
Printf.printf "%s\n" (String.map (fun c -> if c=' ' then '_' else c) s)