In Go, what is the difference between Regexp.ReplaceAllString and ReplaceAllLiteralString?
answer
- one interprets the replacement, one does not
- a dollar sign is a variable in only one of them
- the name runs as long as it can
- braces disambiguate group one from group 1x
- a reference to a group that is not there expands to nothing
basics
~10 sReplaceAllString interprets dollar signs in the replacement text, expanding $1 and ${name} to captured groups. ReplaceAllLiteralString inserts the replacement byte for byte with no expansion, so a dollar sign stays a dollar sign.
solid answer
~50 sBoth replace every match of the pattern, but they treat the replacement string differently. `ReplaceAllString` runs it through the same expansion rules as `Expand`: `$1` is capture group one, `${key}` is the group named `key`, `$$` emits a literal dollar, and a name or number that does not exist expands to nothing at all rather than erroring. The trap is that a bare `$name` takes the longest possible name, so `"$1x"` means the group called `1x` — almost certainly absent, so it silently vanishes — and you must write `"${1}x"`. `ReplaceAllLiteralString` skips all of that and substitutes the replacement exactly as written, which is what you want when the replacement is user-supplied or contains dollars of its own. If the replacement has to be computed per match, `ReplaceAllStringFunc` calls you with the matched text, but note that it hands you only the whole match, not the groups.
code
go · 9 linesre := regexp.MustCompile(`(?P<key>\w+)=(?P<val>\d+)`)
src := "retries=5"
fmt.Println(re.ReplaceAllString(src, "${key}: ${val}")) // prints: retries: 5
fmt.Println(re.ReplaceAllLiteralString(src, "${key}: ${val}")) // prints: ${key}: ${val}
fmt.Println(re.ReplaceAllString(src, "cost $$5")) // prints: cost $5
// The name is taken as long as possible, so this looks for a group "key_new":
fmt.Println(re.ReplaceAllString(src, "$key_new")) // prints an empty linego deeper
Remember that one of the pair treats dollar signs in the replacement as group references and the other does not, and that $1 and ${name} are how you put a captured value back into the output.
Explain the expansion rules precisely: $$ for a literal dollar, longest-possible names so ${1} is needed before a letter, and a missing reference expanding to empty rather than raising an error.
Show that you pick the literal form for any replacement that arrives as data, and that you know the Func variant sees only the whole match — so recovering groups there is a deliberate second step, not something the API gives you.
Treat replacement templates as an interface: once users can supply one, its expansion syntax becomes a contract you support, and you decide up front whether that surface exists at all.
## The two calls ```go func (re *Regexp) ReplaceAllString(src, repl string) string func (re *Regexp) ReplaceAllLiteralString(src, repl string) string ``` Both return a copy of `src` with every non-overlapping match of the pattern replaced. They differ only in how `repl` is interpreted, and if the pattern matches nothing both return `src` unchanged — a useful property, because a replace can never panic on an input that fails to match. ## Expansion rules in ReplaceAllString `ReplaceAllString` treats `repl` as a template and expands variables in it, following the rules documented on `Expand`: - `$1`, `$2`, … refer to capture groups by number, with `$0` meaning the whole match. - `${name}` refers to a named group declared as `(?P<name>…)`. - `$$` produces a single literal `$`. - A reference to a group that does not exist, or that took part in no match, expands to **the empty string**. There is no error and no panic — the text just disappears. The rule that catches everybody is how a bare `$name` is delimited: **the name is taken to be as long as possible**. `"$1x"` is read as the group named `1x`, not as group 1 followed by the letter `x`; `"$10"` is group 10, not group 1 then a zero. Since a group named `1x` almost never exists, the whole thing expands to nothing and the output silently loses text. The fix is braces: `"${1}x"`. Because of this, bracing every reference — `${1}`, `${key}` — is a good habit even when it is not strictly required. ```go re := regexp.MustCompile(`(?P<key>\w+)=(?P<val>\d+)`) re.ReplaceAllString("retries=5", "${key}: ${val}") // "retries: 5" re.ReplaceAllString("retries=5", "$key_new") // "" — no such group ``` ## When you want no expansion at all `ReplaceAllLiteralString` inserts `repl` verbatim. Every `$` in it is just a dollar sign. This is the right call in two situations. First, when the replacement legitimately contains dollars — currency, shell variables, template placeholders you are inserting rather than resolving. Second, and more importantly, **when the replacement comes from outside the program**: a flag, a config file, a request field. Feeding untrusted text to `ReplaceAllString` means the user's `$1` quietly pulls captured input into the output, which at best produces baffling results and at worst leaks part of the matched text somewhere it should not go. If the replacement is data rather than a template, use the literal form. ## Computing the replacement per match ```go func (re *Regexp) ReplaceAllStringFunc(src string, repl func(string) string) string ``` The callback receives the **whole matched text** and returns whatever should stand in its place. Its return value is used literally — no `$` expansion is applied to it. The important limitation is that the callback does not receive the capture groups. Two ways out: run `FindStringSubmatch` on the matched text inside the callback (cheap and readable, at the cost of matching twice), or drop to the index-based API and build the output yourself. ## Expand, the primitive underneath ```go func (re *Regexp) ExpandString(dst []byte, template string, src string, match []int) []byte ``` `Expand` and `ExpandString` are the machinery `ReplaceAllString` uses, exposed for the case where you drive the loop. You collect matches with `FindAllStringSubmatchIndex(src, -1)` — a `[]int` of byte-offset pairs per match — and pass each one, together with the original `src` and your template, to `ExpandString`, which appends the expanded result to `dst` and returns the grown slice. That gives you full control: you can decide per match whether to expand at all, use a different template for different matches, or copy the untouched spans between matches yourself. For the ordinary case, `ReplaceAllString` is the same thing with the loop already written. ## Choosing between them Ask one question: is the replacement a **template** the program author wrote, or **data** that arrived from somewhere? Author-written templates want `ReplaceAllString` and its group references. Data wants `ReplaceAllLiteralString`. Anything that needs to look at the captured values to decide wants `ReplaceAllStringFunc` or the `Expand` loop.
- What does the replacement text $1x expand to in ReplaceAllString?The group named `1x`. A bare `$name` consumes as many name characters as it can, so `1x` is read as one name rather than as group 1 followed by `x`. That group almost certainly does not exist, and an unknown reference expands to the empty string, so the text disappears with no error. Write `${1}x`.
- How do you use the capture groups inside ReplaceAllStringFunc?You cannot directly — the callback is handed only the whole matched text. Either re-run `FindStringSubmatch` on that text inside the callback, or step down to `FindAllStringSubmatchIndex` plus `ExpandString` and assemble the output yourself. The callback's return value is also used literally, so no `$` expansion happens to it.
- Which call would you use when the replacement text comes from a user-supplied flag?`ReplaceAllLiteralString`. Otherwise a `$1` or `${name}` in the user's string is expanded against your capture groups, pulling matched input into the output where nobody expects it. Replacement text that is data rather than an author-written template should never go through the expanding form.
saying these in an interview costs you the question
- Thinks both calls expand $1 the same way
- Reads $1x as group one followed by the letter x
- Expects an error when the replacement names a missing group
- Believes ReplaceAllStringFunc receives the capture groups
- Passes user-supplied replacement text to the expanding form