#263 Jul 16, 2026

263. String::replace_range — Swap a Span In Place, No Rebuild

Swapping one piece of a String by slicing and re-format!-ing rebuilds the whole thing. replace_range splices the new text straight into the buffer you already own.

The trap

You need to change one segment in the middle of a String. The slice-and-rebuild reflex kicks in:

1
2
3
4
5
let mut path = String::from("/api/v1/users");

// new allocation, three copies, old String dropped
path = format!("{}{}{}", &path[..5], "v2", &path[7..]);
assert_eq!(path, "/api/v2/users");

A fresh allocation and three copies to change two bytes. Bite 262’s insert_str covers inserting at a point — but here you want to replace a span.

The fix

String::replace_range takes a byte range and splices the replacement in, in place:

1
2
3
4
let mut path = String::from("/api/v1/users");

path.replace_range(5..7, "v2");
assert_eq!(path, "/api/v2/users");

The replacement doesn’t have to match the range’s length — the tail shifts to fit:

1
2
3
4
let mut greet = String::from("Hello, world!");

greet.replace_range(7..12, "rustbites");
assert_eq!(greet, "Hello, rustbites!");

And any range form works. An empty replacement deletes the span — no drain iterator to throw away:

1
2
3
4
let mut line = String::from("DEBUG: cache miss");

line.replace_range(..7, "");
assert_eq!(line, "cache miss");

Together with insert_str (bite 262) you get the full in-place toolkit: insert at a point, replace a span, delete a range — all without touching the allocator when capacity allows.

One caveat

Same rules as insert_str: the range is in bytes and both ends must land on char boundaries, or it panics — mid-emoji is a runtime error, not a compile error. And because the tail may shift, it’s O(n) per call: fine for a targeted splice, wrong as the workhorse of a text editor’s inner loop.

← Previous 262. String::insert_str — Prepend or Splice Text Without Rebuilding the String Next → 264. Path::join — One Absolute Path and Your Base Directory Is Gone