#260 Jul 15, 2026

260. str::replacen — Replace the First N Matches, Not Every Single One

replace is all-or-nothing: it rewrites every occurrence, whether you wanted that or not. replacen lets you say how many.

The trap

str::replace has no off switch — it replaces every match in the string. The moment your pattern appears somewhere you didn’t expect, it happily rewrites that too:

1
2
3
4
5
6
7
let line = "user=admin role=admin";

// demote the user, keep the role... oops
assert_eq!(
    line.replace("admin", "guest"),
    "user=guest role=guest"
);

The usual workaround is find + manual slicing — index math, an allocation, and an edge case when the pattern is missing.

The fix

replacen takes a third argument: the maximum number of replacements, counted from the left. Everything after the Nth match is left alone:

1
2
3
4
assert_eq!(
    line.replacen("admin", "guest", 1),
    "user=guest role=admin"
);

Fewer matches than n is fine — it just replaces what’s there. And like replace, the pattern can be a char, a &str, or a closure over char:

1
2
3
let csv = "a-b-c-d";
assert_eq!(csv.replacen('-', "+", 2), "a+b+c-d");
assert_eq!(csv.replacen('-', "+", 0), "a-b-c-d"); // n = 0: copy, untouched

One caveat

Counting is strictly left-to-right — there’s no rreplacen for “just the last one”. For that, reach for rfind and slice, or rsplit_once if you’re splitting anyway.

Both replace and replacen return a fresh String and leave the original untouched. If you only need to check the first match, find is cheaper — but when you need “replace the first one and stop”, replacen(pat, to, 1) says exactly that.

← Previous 259. str::lines — Split Into Lines Without Dragging \r Along