strings.ReplaceAll ersetzt alle nicht-überlappenden Vorkommen eines Substrings old durch einen neuen Substring new und gibt das Ergebnis als frischen String zurück. Die Funktion wurde mit Go 1.12 eingeführt und ist nichts anderes als ein lesbarerer Shortcut für den Aufruf strings.Replace(s, old, new, -1) — die -1 als Limit hat in der Praxis fast jede Codebase irgendwann verwirrt, deshalb gibt es seit Go 1.12 diese explizite Variante.
In modernem Go-Code gilt ReplaceAll als idiomatische Wahl, wenn wirklich alle Treffer ersetzt werden sollen. Trotzdem lohnt ein Blick auf die Mechanik: ein leerer old-String hat eine spezielle Semantik, Ersetzungsketten können sich gegenseitig sabotieren, und für mehrere parallele Substitutionen ist strings.Replacer meist die bessere Wahl.
Die Signatur ist bewusst minimal: drei Strings rein, ein String raus. Es gibt keinen Fehlerrückgabewert und kein Limit, da die Semantik global gilt.
func ReplaceAll(s, old, new string) stringAlle Argumente sind string und damit immutable; das Ergebnis ist ein neu allozierter String, sobald mindestens ein Treffer vorhanden ist. Bei null Treffern darf die Implementierung den Eingabestring unverändert weiterreichen — das spart Allokationen.
Intern ruft ReplaceAll schlicht Replace mit n = -1 auf. Die -1 bedeutet bei Replace: kein Limit, alle Treffer ersetzen. Das folgende Beispiel zeigt die Identität beider Aufrufe — sowohl Ergebnis als auch Verhalten sind exakt gleich.
package main
import (
"fmt"
"strings"
)
func main() {
s := "foo bar foo baz foo"
a := strings.ReplaceAll(s, "foo", "QUX")
b := strings.Replace(s, "foo", "QUX", -1)
fmt.Println(a)
fmt.Println(b)
fmt.Println("gleich?", a == b)
}QUX bar QUX baz QUX
QUX bar QUX baz QUX
gleich? trueFunktional gibt es keinen Unterschied — der Vorteil liegt rein in der Lesbarkeit. Wer beim Lesen über -1 stolpert und kurz überlegen muss, was das bedeutet, profitiert von der expliziten ReplaceAll-Variante.
Ein leerer old-String ist ein Sonderfall, den viele Entwickler unterschätzen. ReplaceAll(s, "", new) fügt den new-String an jeder Position zwischen den Runen und zusätzlich am Anfang und Ende ein. Bei einem dreizeichigen String entstehen damit vier Einfügepunkte.
package main
import (
"fmt"
"strings"
)
func main() {
s := "abc"
fmt.Println(strings.ReplaceAll(s, "", "X"))
// Auch bei UTF-8 wird zwischen Runen, nicht zwischen Bytes eingefügt
u := "äöü"
fmt.Println(strings.ReplaceAll(u, "", "-"))
}XaXbXcX
-ä-ö-ü-Wichtig: die Einfügepunkte liegen zwischen Runen, nicht zwischen Bytes. Das macht die Funktion UTF-8-sicher und vermeidet kaputte Mehrbyte-Sequenzen. In der Praxis ist dieser Sonderfall selten gewollt — meistens entsteht er durch einen leer initialisierten Parameter, weshalb der old-Wert vor dem Aufruf geprüft werden sollte.
Eine klassische Falle entsteht, wenn mehrere ReplaceAll-Aufrufe nacheinander angewandt werden und der neue Wert eines Schritts zufällig dem alten Pattern eines späteren Schritts entspricht. Das Ergebnis ist dann nicht idempotent und führt zu schwer reproduzierbaren Bugs.
package main
import (
"fmt"
"strings"
)
func main() {
s := "a b"
// Falsch: nacheinander ersetzen
r1 := strings.ReplaceAll(s, "a", "b")
r1 = strings.ReplaceAll(r1, "b", "a")
fmt.Println("kette: ", r1) // "a a" — beide wurden zu "a"!
// Richtig: Replacer ersetzt parallel in einem Durchlauf
rep := strings.NewReplacer("a", "b", "b", "a")
fmt.Println("replacer:", rep.Replace(s)) // "b a"
}kette: a a
replacer: b astrings.Replacer löst dieses Problem, weil er in einem einzigen Scan-Durchlauf arbeitet und Ersetzungen nicht aufeinander anwendet. Faustregel: sobald zwei oder mehr ReplaceAll-Aufrufe in Reihe stehen und die Werte sich überlappen könnten, ist Replacer die robustere Wahl.
Die drei Werkzeuge decken unterschiedliche Bedürfnisse ab. Die folgende Tabelle fasst zusammen, wann welches Werkzeug die richtige Wahl ist.
| Werkzeug | Zweck | Wann verwenden |
|---|---|---|
strings.ReplaceAll | Alle Treffer eines Patterns ersetzen | Standardfall, ein Pattern, idiomatisch ab Go 1.12 |
strings.Replace | Maximal n Treffer ersetzen | Nur die ersten n Vorkommen oder explizites -1 |
strings.NewReplacer | Mehrere Patterns parallel ersetzen | Mehrfach-Ersetzung, Vermeidung von Kreuz-Replacement, wiederverwendbar |
Replacer ist außerdem vorteilhaft, wenn die gleichen Ersetzungsregeln viele Male auf unterschiedliche Strings angewandt werden — die interne Trie-Struktur wird einmal aufgebaut und dann wiederverwendet.
Wenn old im Eingabestring nicht vorkommt, gibt ReplaceAll den Originalstring zurück, ohne neuen Speicher zu allozieren. Sobald jedoch mindestens ein Treffer existiert, wird ein neuer String mit passender Kapazität gebaut — der Aufwand ist linear in der Länge des Inputs.
Für große Strings mit vielen Treffern ist ReplaceAll deutlich effizienter als eine handgeschriebene Schleife, weil intern strings.Builder zum Einsatz kommt. Bei Hot-Path-Code mit mehreren parallelen Substitutionen lohnt der Wechsel zu einem einmal vorbereiteten Replacer, da dieser zusätzlich die Pattern-Suche bündelt.
Mini-Templates mit {{var}}-Syntax lassen sich ohne externe Library bauen, solange die Anzahl der Variablen klein ist. Das folgende Beispiel zeigt eine simple Substitution für eine Mail-Vorlage, wie sie etwa für Benachrichtigungs-Templates praktikabel ist.
package main
import (
"fmt"
"strings"
)
func main() {
tmpl := "Hallo {{name}}, dein Konto {{account}} ist aktiv."
out := strings.ReplaceAll(tmpl, "{{name}}", "Anna")
out = strings.ReplaceAll(out, "{{account}}", "acc-4711")
fmt.Println(out)
}Hallo Anna, dein Konto acc-4711 ist aktiv.Für mehrere Platzhalter ist strings.NewReplacer die effizientere und sicherere Variante — vor allem dann, wenn ein Variablenwert wieder einen {{...}}-Ausdruck enthalten könnte. Für ernsthafte Templates sollte ohnehin text/template zum Einsatz kommen.
Beim CSV-Schreiben verlangt RFC 4180, dass innerhalb gequoteter Felder ein doppeltes Anführungszeichen durch zwei Anführungszeichen escaped wird. Diese Transformation ist ein Lehrbuchfall für ReplaceAll.
package main
import (
"fmt"
"strings"
)
func csvField(v string) string {
escaped := strings.ReplaceAll(v, `"`, `""`)
return `"` + escaped + `"`
}
func main() {
fmt.Println(csvField(`Hallo "Welt"`))
fmt.Println(csvField(`ohne Quote`))
fmt.Println(csvField(`mehrere "" Quotes`))
}"Hallo ""Welt"""
"ohne Quote"
"mehrere """" Quotes"In produktivem Code sollte natürlich encoding/csv aus der Standardbibliothek bevorzugt werden — diese Funktion kümmert sich um Quoting, Delimiter und Zeilenumbrüche zuverlässig. Das Beispiel zeigt aber, warum ReplaceAll für einfache Escape-Aufgaben so attraktiv ist: ein Aufruf, kein State, kein Allokationsoverhead bei Strings ohne Quote.
Shortcut für n=-1
strings.ReplaceAll(s, old, new) ist exakt identisch zu strings.Replace(s, old, new, -1) — keine versteckte Magie, nur lesbarer.
Seit Go 1.12 idiomatisch
Vor Go 1.12 war Replace(s, old, new, -1) Standard; seit 1.12 gilt ReplaceAll als bevorzugte Schreibweise für „alle Treffer ersetzen".
Leeres old fügt new überall ein
Mit old == "" landet new zwischen jeder Rune sowie am Anfang und Ende — fast nie das, was wirklich gewollt ist.
Kreuz-Replacement-Falle bei Ketten
Mehrere ReplaceAll-Aufrufe hintereinander können sich gegenseitig „kannibalisieren", wenn new-Werte dem old-Pattern eines späteren Schritts entsprechen.
Replacer für parallele Ersetzungen
strings.NewReplacer ersetzt mehrere Patterns in einem Scan-Durchlauf und vermeidet die Kreuz-Replacement-Falle vollständig.
Ohne Match = identischer String
Findet ReplaceAll keinen Treffer, wird der Originalstring ohne neue Allokation zurückgegeben — günstig im No-Op-Fall.
Byte-orientiert, aber UTF-8-sicher
Die Suche arbeitet auf Byte-Ebene, der Sonderfall old == "" orientiert sich aber an Rune-Grenzen — gültige UTF-8-Sequenzen bleiben intakt.
Threadsafe
Strings sind in Go immutable; ReplaceAll mutiert nichts und ist damit ohne weiteres Locking aus mehreren Goroutinen aufrufbar.