strings.Cut schneidet einen String s am ersten Vorkommen eines Trenners sep in zwei Teile und liefert zusätzlich ein Erfolgs-Flag zurück. Die Signatur (before, after string, found bool) macht zwei Dinge gleichzeitig sichtbar: das Ergebnis und die Frage, ob der Trenner überhaupt vorkam — beides früher nur über mehrere Aufrufe von strings.Index und manuelles Slicing zu erreichen.

Eingeführt wurde die Funktion mit Go 1.18 und gilt seitdem als der idiomatische Ersatz für das alte Drei-Zeilen-Muster aus Index, Längenprüfung und zwei Slice-Ausdrücken. Genau in diesem Muster steckten klassische Bugs: vergessene -1-Prüfung, falsche Offset-Addition für die sep-Länge, off-by-one beim Slice. Cut macht aus dieser fehleranfälligen Sequenz einen einzigen Aufruf, der Key-Value-Parsing, URL-Schema-Trennung, Header-Splits und ähnliche Aufgaben in einer Zeile abbildet.

Die Signatur ist bewusst kompakt gehalten und besteht aus zwei Eingabe-Strings und drei Rückgabewerten. Der erste String ist die Quelle, der zweite der gesuchte Trenner — die Rückgabe liefert die beiden Teile sowie einen bool, der den Trefferstatus dokumentiert.

Go signatur.go
package main

import (
	"fmt"
	"strings"
)

// func Cut(s, sep string) (before, after string, found bool)

func main() {
	before, after, found := strings.Cut("host:8080", ":")
	fmt.Printf("before=%q after=%q found=%v\n", before, after, found)
}
Output
before="host" after="8080" found=true

Der Aufruf liest sich wie eine Behauptung: „Zerschneide s an sep — und sag mir, ob das geklappt hat." Genau diese Lesbarkeit ist der Grund, warum Cut in modernem Go-Code fast immer dem älteren SplitN(s, sep, 2) vorgezogen wird.

Die drei Rückgaben sind klar voneinander getrennt: before enthält alles vor dem ersten Treffer von sep, after alles dahinter, und found zeigt an, ob sep überhaupt gefunden wurde. Wichtig ist das Verhalten bei einem Nicht-Treffer: before ist dann der komplette Quell-String, after ist leer, found ist false — eine Konvention, die das Default-Branching besonders bequem macht.

Go rueckgaben.go
package main

import (
	"fmt"
	"strings"
)

func main() {
	// Treffer
	b, a, ok := strings.Cut("key=value", "=")
	fmt.Printf("hit:  before=%q after=%q found=%v\n", b, a, ok)

	// Kein Treffer
	b, a, ok = strings.Cut("nokeyvalue", "=")
	fmt.Printf("miss: before=%q after=%q found=%v\n", b, a, ok)
}
Output
hit:  before="key" after="value" found=true
miss: before="nokeyvalue" after="" found=false

Der zweite Fall illustriert eine bewusste Designentscheidung: Wer den Trenner nicht findet, hat in before weiterhin den vollständigen Input — kein Sonderfall, kein nil, kein Panic. Eine typische Verwendung ist if !found { return s, defaultValue }, die ohne weitere Slice-Logik auskommt.

Cut ist kein Mehrfach-Splitter. Es sucht nur das erste Vorkommen von sep und packt alles Weitere unverändert in after. Wer einen String an allen Vorkommen zerlegen möchte, greift zu strings.Split oder strings.SplitNCut hingegen ist die richtige Wahl, wenn der Trenner semantisch genau einmal auftauchen soll, etwa beim ersten = einer Konfigurationszeile.

Go erster_treffer.go
package main

import (
	"fmt"
	"strings"
)

func main() {
	// Mehrere "=" — nur das erste trennt
	b, a, _ := strings.Cut("path=/usr/local/bin=fallback", "=")
	fmt.Printf("before=%q\nafter =%q\n", b, a)
}
Output
before="path"
after ="/usr/local/bin=fallback"

Das Verhalten ist exakt das, was beim Parsen von KEY=VALUE-Zeilen gewünscht ist: ein Gleichheitszeichen im Wert darf den Schlüssel nicht verstümmeln. Wer dagegen ein letztes Vorkommen braucht — etwa für Dateiendungen — kombiniert strings.LastIndex mit manuellem Slicing oder nutzt eine eigene Helper-Funktion.

Zwei Randfälle lohnen sich, einmal explizit zu sehen: ein leerer Quell-String und ein leerer Trenner. Beide Fälle liefern wohldefinierte Werte und werfen keine Fehler — Cut ist dafür ausgelegt, ohne Vorab-Validierung sicher aufgerufen zu werden.

Go leere_eingaben.go
package main

import (
	"fmt"
	"strings"
)

func main() {
	// Leerer Quell-String
	b1, a1, f1 := strings.Cut("", "=")
	fmt.Printf("empty s:   before=%q after=%q found=%v\n", b1, a1, f1)

	// Leerer Trenner — matcht an Position 0
	b2, a2, f2 := strings.Cut("hallo", "")
	fmt.Printf("empty sep: before=%q after=%q found=%v\n", b2, a2, f2)
}
Output
empty s:   before="" after="" found=false
empty sep: before="" after="hallo" found=true

Der leere Trenner ist der interessante Fall: Da der leere String per Definition an jeder Position vorkommt (auch an Position 0), liefert Cut before="", after=s und found=true. Das ist konsistent mit strings.Index(s, "") == 0, wirkt beim ersten Lesen aber überraschend — bei nutzergesteuerten Trennern lohnt sich eine if sep == ""-Vorprüfung.

Vor Go 1.18 war das übliche Muster entweder strings.SplitN(s, sep, 2) mit anschließendem Längen-Check oder ein manuelles strings.Index plus Slicing. Beide Wege funktionieren weiterhin, sind aber weniger lesbar und in der Index-Variante deutlich fehleranfälliger.

Aspektstrings.CutSplitN(s, sep, 2)Index + Slice
Rückgabebefore, after, found[]string (1 oder 2 Elemente)int + manuelles Slicing
Erfolgsprüfungdirekt via foundlen(parts) == 2i != -1
Allokationkeine Slice-Allokation[]string-Headerkeine
Lesbarkeithoch — Absicht klarmittelniedrig
Bug-Risikominimalgeringhoch (Offset, len(sep))
Verfügbar seitGo 1.18seit jeherseit jeher

In neuem Code ist Cut praktisch immer die richtige Wahl, sobald genau ein Trenner relevant ist. SplitN(2) bleibt nützlich, wenn der Code generisch über n parametrisiert ist; das Index+Slice-Muster sollte man nur noch sehen, wenn zusätzlich der numerische Offset gebraucht wird.

Das Lesen von KEY=VALUE-Zeilen aus .env-Dateien, HTTP-Cookies oder Konfig-Snippets ist der Lehrbuchfall für Cut. Der erste = trennt Schlüssel und Wert, alle weiteren Gleichheitszeichen sind Teil des Werts — exakt die Semantik, die Cut mitbringt.

Go env_parse.go
package main

import (
	"fmt"
	"strings"
)

func parseEnv(line string) (key, value string, ok bool) {
	key, value, ok = strings.Cut(line, "=")
	if !ok {
		return "", "", false
	}
	return strings.TrimSpace(key), strings.TrimSpace(value), true
}

func main() {
	lines := []string{
		"DATABASE_URL=postgres://user:pw@host/db",
		"DEBUG=true",
		"BROKEN_LINE_OHNE_GLEICH",
		"EQUATION=a=b+c",
	}

	for _, l := range lines {
		k, v, ok := parseEnv(l)
		fmt.Printf("ok=%-5v key=%-13q value=%q\n", ok, k, v)
	}
}
Output
ok=true  key="DATABASE_URL" value="postgres://user:pw@host/db"
ok=true  key="DEBUG"        value="true"
ok=false key=""             value=""
ok=true  key="EQUATION"     value="a=b+c"

Beachtenswert ist die vierte Zeile: EQUATION=a=b+c wird korrekt aufgeteilt, weil Cut nur am ersten = schneidet und alle weiteren Zeichen im Wert belässt. Genau dieses Verhalten ist beim Parsen von Datenbank-URLs, Base64-Werten oder URL-Encoded-Strings unverzichtbar — naive Split-Aufrufe würden den Wert hier zerreißen.

Eine zweite typische Aufgabe ist das Abspalten des URL-Schemas vom Rest der URL. Der Trenner :// ist klar definiert und kommt im Restpfad nicht vor — ideal für Cut, das hier in einer Zeile erledigt, wofür sonst strings.Index plus Längenrechnung mit len("://") nötig wäre.

Go url_schema.go
package main

import (
	"fmt"
	"strings"
)

func splitScheme(url string) (scheme, rest string) {
	scheme, rest, ok := strings.Cut(url, "://")
	if !ok {
		return "", url // kein Schema — alles ist "rest"
	}
	return scheme, rest
}

func main() {
	urls := []string{
		"https://mibeon.de/docs/go",
		"postgres://user@host/db",
		"ftp://files.example.org/pub",
		"mibeon.de/ohne-schema",
	}

	for _, u := range urls {
		s, r := splitScheme(u)
		fmt.Printf("scheme=%-9q rest=%q\n", s, r)
	}
}
Output
scheme="https"    rest="mibeon.de/docs/go"
scheme="postgres" rest="user@host/db"
scheme="ftp"      rest="files.example.org/pub"
scheme=""         rest="mibeon.de/ohne-schema"

Der letzte Fall zeigt, wie sauber der found-Bool das Default-Verhalten steuert: Ohne :// wird die gesamte Eingabe als rest durchgereicht, das scheme bleibt leer. Für vollwertiges URL-Parsing greift man später zu net/url — für den schnellen Schema-Check oder Routing-Entscheidungen reicht Cut aber vollkommen aus.

Seit Go 1.18

strings.Cut wurde mit Go 1.18 eingeführt und ist seitdem fester Bestandteil der Standardbibliothek — Code mit älteren Go-Versionen muss auf Index+Slice oder SplitN zurückgreifen.

Drei Rückgaben: before, after, found

Die Signatur (before, after string, found bool) liefert Ergebnis und Trefferstatus in einem Aufruf — kein separater Index-Aufruf zur Erfolgsprüfung mehr nötig.

Nur erster Treffer

Cut schneidet ausschließlich am ersten Vorkommen von sep; weitere Trenner bleiben unverändert in after enthalten — entscheidend für KEY=VALUE mit = im Wert.

Ersetzt das alte Index+Slice-Muster

Drei fehleranfällige Zeilen (i := Index, if i == -1, zwei Slices mit +len(sep)) werden zu einem klar lesbaren Aufruf — etwa 90 Prozent dieser Muster lassen sich direkt ersetzen.

Idiomatischer als SplitN(s, sep, 2)

SplitN mit n=2 löst dieselbe Aufgabe, alloziert aber einen Slice und braucht eine len(parts) == 2-Prüfung — Cut ist kompakter und drückt die Absicht direkter aus.

Nicht-Treffer: found=false, before=s

Findet Cut den Trenner nicht, ist before der komplette Eingabestring und after leer — bequem für Default-Branching ohne zusätzliche Sonderfall-Logik.

Leerer Trenner ergibt found=true

Cut(s, "") liefert before="", after=s, found=true, weil der leere String an Position 0 matcht — bei nutzergesteuerten Trennern lohnt sich eine explizite if sep == ""-Prüfung.

Threadsafe und allokationsfrei

Wie alle strings-Funktionen ist Cut rein lesend und alloziert keine neuen Strings — die Rückgaben sind Substring-Header auf dem Original-Backing-Array und gefahrlos aus mehreren Goroutinen aufrufbar.

Weiterführende Ressourcen

Externe Quellen

/ Weiter

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

Zur Übersicht