strings.Replace ersetzt im Eingabestring s bis zu n Vorkommen des Substrings old durch new und gibt das Ergebnis als neuen String zurück. Der vierte Parameter n steuert die Anzahl der Ersetzungen: ein negativer Wert (idiomatisch -1) ersetzt alle Treffer, n = 0 ist ein No-op und liefert s unverändert, ein positiver Wert begrenzt die Ersetzung auf die ersten n Treffer von links.

Die Funktion arbeitet auf Byte-Ebene, ist aber UTF-8-sicher, weil sie nach old als kompletter Byte-Sequenz sucht — Multi-Byte-Runen werden nie in der Mitte zerteilt. Seit Go 1.12 existiert mit strings.ReplaceAll ein expliziter Shortcut für den häufigsten Fall n = -1. Für mehrere parallele Ersetzungspaare ist strings.NewReplacer die bessere Wahl, weil dort ein einziger Pass über den String genügt.

Die Signatur ist eine der wenigen im strings-Paket mit vier Parametern. Reihenfolge merken: Quelle, Suchmuster, Ersatzmuster, Limit — also „in s, ersetze old durch new, maximal n mal".

Go signature.go
func Replace(s, old, new string, n int) string

Da string in Go immutabel ist, gibt Replace immer einen Wert zurück und mutiert nichts. Wenn nichts zu ersetzen ist — etwa weil old in s nicht vorkommt oder weil n = 0 ist — liefert die Funktion intern den ursprünglichen String zurück, ohne neuen Speicher zu allokieren.

Der n-Parameter ist der eigentliche Charakter dieser Funktion und unterscheidet sie von ReplaceAll. Drei Wertebereiche, drei Verhaltensweisen:

nVerhalten
n < 0Alle Vorkommen ersetzen (idiomatisch -1)
n = 0Keine Ersetzung, s wird unverändert zurückgegeben
n > 0Bis zu n Vorkommen von links nach rechts ersetzen

Wichtig: bei n > 0 wird bei jedem ersetzten Treffer der Suchcursor hinter new weitergeschoben, nicht hinter old. Überlappende Treffer sind also kein Thema — Replace("aaaa", "aa", "b", -1) ergibt "bb", nicht "bba".

Go n_param.go
package main

import (
	"fmt"
	"strings"
)

func main() {
	s := "foo foo foo foo"

	fmt.Println(strings.Replace(s, "foo", "bar", 2))  // nur erste 2
	fmt.Println(strings.Replace(s, "foo", "bar", -1)) // alle
	fmt.Println(strings.Replace(s, "foo", "bar", 0))  // nichts
	fmt.Println(strings.Replace(s, "xyz", "bar", -1)) // kein Treffer
}
Output
bar bar foo foo
bar bar bar bar
foo foo foo foo
foo foo foo foo

Der erste Aufruf zeigt das Limit deutlich: zwei Treffer ersetzt, zwei bleiben stehen. Die letzten beiden Aufrufe demonstrieren, dass n = 0 und „kein Treffer vorhanden" beide dasselbe Ergebnis liefern — den Originalstring.

Ein leerer old-String ist ein Sonderfall, der oft überrascht: Replace(s, "", new, n) fügt new zwischen jeder UTF-8-Rune ein, einmal am Anfang und einmal am Ende. Das ist nicht etwa ein Bug, sondern die konsistente Fortsetzung der Regel „nach jeder Rune kommt eine leere Position".

Go empty_old.go
package main

import (
	"fmt"
	"strings"
)

func main() {
	fmt.Println(strings.Replace("abc", "", "-", -1))
	fmt.Println(strings.Replace("abc", "", "-", 2))
	fmt.Println(strings.Replace("äöü", "", "·", -1))
}
Output
-a-b-c-
-a-bc
·ä·ö·ü·

Das letzte Beispiel zeigt die Rune-Awareness: trotz interner Byte-Sicht setzt die Funktion das Trennzeichen korrekt zwischen die Multi-Byte-Umlaute und nicht zwischen einzelne Bytes. Das macht das Pattern brauchbar für sichtbare Trenner zwischen Zeichen, ohne Code-Point-Iteration von Hand schreiben zu müssen.

Seit Go 1.12 existiert strings.ReplaceAll(s, old, new) als reiner Shortcut für strings.Replace(s, old, new, -1). Funktional sind beide identisch — der Aufruf-Stil unterscheidet sich nur in Lesbarkeit und Intent.

Go replace_vs_replaceall.go
package main

import (
	"fmt"
	"strings"
)

func main() {
	s := "foo foo foo"

	a := strings.Replace(s, "foo", "bar", -1)
	b := strings.ReplaceAll(s, "foo", "bar")

	fmt.Println(a == b) // identisches Ergebnis
	fmt.Println(b)
}
Output
true
bar bar bar

Faustregel: sobald n ins Spiel kommt (Limit, No-op, dynamische Zählung), Replace mit explizitem n. Für den klassischen „ersetze alle"-Fall ist ReplaceAll die idiomatischere Form, weil die Absicht im Funktionsnamen steht und nicht in einer magischen -1.

Sobald mehrere unterschiedliche Substitutionen am selben String laufen sollen, wird strings.NewReplacer interessant. Ein Replacer kompiliert die Paare einmalig in einen Automaten und ersetzt in einem einzigen Pass — das ist nicht nur schneller, sondern verhindert auch Folgefehler durch kaskadierende Replace-Aufrufe.

Go replacer.go
package main

import (
	"fmt"
	"strings"
)

func main() {
	s := "Hallo {{user}}, dein Status ist {{status}}."

	// Naiv: mehrere Replace-Aufrufe
	out1 := strings.Replace(s, "{{user}}", "Anna", -1)
	out1 = strings.Replace(out1, "{{status}}", "aktiv", -1)
	fmt.Println(out1)

	// Idiomatisch: ein Replacer
	r := strings.NewReplacer(
		"{{user}}", "Anna",
		"{{status}}", "aktiv",
	)
	fmt.Println(r.Replace(s))
}
Output
Hallo Anna, dein Status ist aktiv.
Hallo Anna, dein Status ist aktiv.

Der Unterschied wird relevant, wenn ein Ersatzwert selbst wieder ein Suchmuster eines anderen Paares enthalten könnte: bei verketteten Replace-Aufrufen würde der zweite Aufruf das Ergebnis des ersten erneut anfassen, der Replacer macht das nie. Ein Replacer ist außerdem threadsafe und kann ohne weitere Synchronisation mehrfach parallel benutzt werden.

Replace allokiert nur dann einen neuen String, wenn tatsächlich etwas zu ersetzen war. Bei n = 0, bei old ohne Treffer in s oder bei old == new liefert die Funktion intern den ursprünglichen String zurück, ohne den Heap zu belasten.

Für Hot Paths, in denen Ersetzungen häufig leer ausgehen, ist das ein wichtiges Detail: ein einfacher strings.Contains-Vorabcheck bringt keine Ersparnis, weil Replace ohne Treffer ohnehin nichts alloziert. Erst wenn mindestens ein Treffer existiert, entsteht ein neuer Backing-Array.

Für einfache Platzhalter-Templates ohne Logik ist Replace mit n = -1 (oder direkt ReplaceAll) eine pragmatische Lösung. Sobald Bedingungen, Schleifen oder Escaping ins Spiel kommen, ist text/template bzw. html/template die richtige Wahl — aber für eine Mail-Anrede oder eine Log-Zeile sind drei Replaces schneller geschrieben als ein Template geparst.

Go template.go
package main

import (
	"fmt"
	"strings"
)

func renderMail(tmpl string, vars map[string]string) string {
	out := tmpl
	for k, v := range vars {
		out = strings.Replace(out, "{{"+k+"}}", v, -1)
	}
	return out
}

func main() {
	tmpl := "Hallo {{name}},\n\nIhre Rechnung über {{betrag}} EUR liegt bereit."
	vars := map[string]string{
		"name":   "Frau Müller",
		"betrag": "129,90",
	}
	fmt.Println(renderMail(tmpl, vars))
}
Output
Hallo Frau Müller,

Ihre Rechnung über 129,90 EUR liegt bereit.

Für Produktivcode mit mehreren Variablen lohnt sich der Wechsel auf strings.NewReplacer: ein einziger Pass statt einer Schleife mit N Allokationen. Die Replace-Variante ist trotzdem nützlich, wenn Platzhalter dynamisch erst zur Laufzeit zusammengesetzt werden und kein vorab bekanntes Paar-Set existiert.

Ein klassischer Anwendungsfall ist das Vereinheitlichen von Zeilenenden, etwa beim Lesen von Dateien aus gemischten Quellen. Windows liefert \r\n, alte Mac-Systeme \r, Unix \n — für die meisten Verarbeitungs-Pipelines ist \n der gewünschte Normalfall.

Go normalize.go
package main

import (
	"fmt"
	"strings"
)

func normalizeNewlines(s string) string {
	// Erst CRLF -> LF, dann verbleibende CR -> LF
	s = strings.Replace(s, "\r\n", "\n", -1)
	s = strings.Replace(s, "\r", "\n", -1)
	return s
}

func main() {
	raw := "zeile1\r\nzeile2\rzeile3\nzeile4"
	clean := normalizeNewlines(raw)
	fmt.Printf("%q\n", clean)
}
Output
"zeile1\nzeile2\nzeile3\nzeile4"

Die Reihenfolge ist wichtig: erst \r\n als Ganzes ersetzen, sonst würde der zweite Schritt das \r aus einem CRLF in ein \n verwandeln und der erste Schritt sähe nur noch ein doppeltes \n\n. Für die idiomatische Schreibweise mit NewReplacer würde ein einziger Pass beide Fälle in einem Rutsch korrekt abdecken.

n = -1 für alle

Idiomatischer Wert, wenn keine Begrenzung gewünscht ist — seit Go 1.12 besser durch ReplaceAll ausgedrückt.

n = 0 ist No-op

Liefert den Eingabestring unverändert zurück, ohne neue Allokation — nützlich für konditionale Pipelines.

Leeres old fügt überall ein

Replace(s, "", new, -1) setzt new zwischen jede Rune und an beide Enden — Rune-aware, nicht byte-naiv.

ReplaceAll seit Go 1.12

Reiner Shortcut für Replace(..., -1) — gleicher Code, klarere Absicht.

Replacer für parallele Paare

Mehrere Ersetzungen in einem Pass, threadsafe und ohne Kaskadierungs-Bugs zwischen den Paaren.

Keine Allokation ohne Treffer

Wenn old nicht in s vorkommt, gibt Replace den ursprünglichen String-Header zurück.

UTF-8-sicher trotz Byte-Suche

Die Funktion sucht byte-orientiert, trifft aber dank UTF-8-Selbstsynchronisation nie in der Mitte einer Rune.

Threadsafe

Da Strings in Go immutabel sind und Replace keinen geteilten Zustand hat, ist die Funktion ohne Locks parallel nutzbar.

Weiterführende Ressourcen

Externe Quellen

/ Weiter

Zurück zu Das strings-Paket — String-Manipulation

Zur Übersicht