strings.CutPrefix prüft in einem einzigen Aufruf, ob ein String mit einem bestimmten Präfix beginnt, und liefert gleichzeitig den Rest ohne dieses Präfix zurück. Die Funktion existiert seit Go 1.20 und schließt eine lange offene Lücke in der Standardbibliothek: Wer früher ein Präfix bedingt entfernen wollte, brauchte das Duo aus HasPrefix und TrimPrefix — zwei Aufrufe, die intern beide den gleichen Vergleich durchführen.
Die Signatur (after string, found bool) folgt dem Comma-Ok-Idiom, das Go-Entwickler von Map-Lookups und Type-Assertions kennen. Das macht CutPrefix zur idiomatischen Wahl überall dort, wo das Entfernen eines Präfixes und die Information „war das Präfix überhaupt da?" beide gebraucht werden — typisch bei Authentifizierungs-Headern, Routern und Konfigurations-Schaltern.
Die Signatur ist bewusst minimal gehalten und spiegelt das Comma-Ok-Muster der Sprache. Beide Rückgabewerte sind immer gesetzt — der zweite Wert ist der entscheidende Indikator, ob das Präfix wirklich entfernt wurde oder ob after einfach den Original-String enthält.
func CutPrefix(s, prefix string) (after string, found bool)Wichtig zu verstehen: after ist niemals nil oder leer als Fehler-Signal — bei einem Miss bekommt der Aufrufer schlicht den ursprünglichen s zurück. Erst das found-Flag macht aus dem Rückgabewert eine sichere Entscheidungsgrundlage.
Der zentrale Unterschied zu TrimPrefix liegt im zweiten Rückgabewert. Während TrimPrefix schweigend den Original-String zurückgibt, wenn kein Match vorliegt, macht CutPrefix diesen Fall explizit. Genau das ist in Routing- und Parsing-Code wertvoll: Man kann die Entscheidung „Präfix war da, also gehört dieser Pfad zu Modul X" direkt am found-Wert festmachen.
package main
import (
"fmt"
"strings"
)
func main() {
// Match: Präfix vorhanden
rest, ok := strings.CutPrefix("https://mibeon.de", "https://")
fmt.Printf("rest=%q ok=%v\n", rest, ok)
// Miss: Präfix fehlt, after = Original
rest2, ok2 := strings.CutPrefix("http://mibeon.de", "https://")
fmt.Printf("rest=%q ok=%v\n", rest2, ok2)
}rest="mibeon.de" ok=true
rest="http://mibeon.de" ok=falseIm Miss-Fall ist rest2 identisch mit dem Input — das ist wichtig zu wissen, weil so kein zusätzlicher String alloziert wird. Der Rückgabewert ist ein Slice des Originals, kein neuer Speicherbereich.
Ein leeres Präfix gilt definitionsgemäß als „in jedem String enthalten" — CutPrefix(s, "") liefert daher (s, true). Dieses Verhalten ist konsistent mit HasPrefix(s, ""), das ebenfalls true zurückgibt, und folgt der mathematischen Konvention, dass die leere Sequenz Präfix jeder Sequenz ist.
package main
import (
"fmt"
"strings"
)
func main() {
after, found := strings.CutPrefix("mibeon", "")
fmt.Printf("after=%q found=%v\n", after, found)
// Auch leerer Input + leeres Präfix = (s, true)
after2, found2 := strings.CutPrefix("", "")
fmt.Printf("after=%q found=%v\n", after2, found2)
}after="mibeon" found=true
after="" found=trueIn der Praxis bedeutet das: Wer dynamisch konfigurierte Präfixe weiterreicht, sollte den leeren String explizit abfangen, falls „kein Präfix" eine andere Semantik haben soll als „triviales Match".
Die drei Funktionen lösen verwandte, aber unterschiedliche Probleme. Die Tabelle fasst zusammen, wann welche Variante die richtige Wahl ist.
| Funktion | Rückgabe | Anwendungsfall |
|---|---|---|
HasPrefix(s, p) | bool | Nur prüfen, nichts ändern |
TrimPrefix(s, p) | string (Original bei Miss) | Entfernen, Miss ist egal |
CutPrefix(s, p) | (after, found) | Entfernen UND Miss erkennen |
Faustregel: Sobald nach dem Entfernen noch eine Verzweigung folgt — etwa „nur wenn Präfix vorhanden war, behandle den Rest als Token" — ist CutPrefix der idiomatische Weg. Für reine Boolean-Checks ohne Folgearbeit reicht HasPrefix, für unbedingte Bereinigung genügt TrimPrefix.
Authentifizierungs-Header der Form Authorization: Bearer <token> sind das Lehrbuch-Beispiel für CutPrefix. Vor Go 1.20 war der Standard-Code dafür unschön: erst HasPrefix, dann TrimPrefix, oder ein manuelles s[len("Bearer "):] mit eigenem Längen-Check. CutPrefix macht aus diesem Boilerplate eine einzige Zeile im if-Initial-Statement.
package main
import (
"errors"
"fmt"
"strings"
)
func extractBearer(header string) (string, error) {
if token, ok := strings.CutPrefix(header, "Bearer "); ok {
return token, nil
}
return "", errors.New("kein Bearer-Token")
}
func main() {
cases := []string{
"Bearer abc123xyz",
"Basic dXNlcjpwYXNz",
"Bearer ",
"",
}
for _, h := range cases {
tok, err := extractBearer(h)
if err != nil {
fmt.Printf("%-30q -> Fehler: %v\n", h, err)
continue
}
fmt.Printf("%-30q -> Token=%q\n", h, tok)
}
}"Bearer abc123xyz" -> Token="abc123xyz"
"Basic dXNlcjpwYXNz" -> Fehler: kein Bearer-Token
"Bearer " -> Token=""
"" -> Fehler: kein Bearer-TokenDer Code zeigt eine wichtige Eigenschaft: Bearer mit Leerstring danach ergibt einen leeren, aber gültigen Token-Treffer — die Funktion prüft nur das Präfix, nicht den Inhalt dahinter. Wer leere Tokens ablehnen will, braucht einen zusätzlichen len(token) > 0-Check.
Ein zweites typisches Szenario ist das Routen von HTTP-Pfaden auf API-Versionen oder Sub-Router. Statt jeden Pfad doppelt zu lesen (einmal für die Erkennung, einmal für das Abschneiden des Präfixes), erledigt CutPrefix beides — der Rest steht dann direkt für die nächste Routing-Stufe bereit.
package main
import (
"fmt"
"strings"
)
func route(path string) string {
if rest, ok := strings.CutPrefix(path, "/api/v1/"); ok {
return fmt.Sprintf("v1-handler -> %q", rest)
}
if rest, ok := strings.CutPrefix(path, "/api/v2/"); ok {
return fmt.Sprintf("v2-handler -> %q", rest)
}
if rest, ok := strings.CutPrefix(path, "/static/"); ok {
return fmt.Sprintf("static-server -> %q", rest)
}
return "404"
}
func main() {
paths := []string{
"/api/v1/users",
"/api/v2/orders/42",
"/static/logo.svg",
"/admin",
}
for _, p := range paths {
fmt.Printf("%-25s -> %s\n", p, route(p))
}
}/api/v1/users -> v1-handler -> "users"
/api/v2/orders/42 -> v2-handler -> "orders/42"
/static/logo.svg -> static-server -> "logo.svg"
/admin -> 404Das Muster skaliert: Jede Route ist ein eigener if-Block mit Initial-Statement, der Rest wird sofort an den passenden Sub-Handler übergeben. Im Vergleich zu Regex-basierten Routern ist diese Variante ausdrucksstark und null-allokationsfrei — rest ist ein Slice des Original-Pfades.
Verfügbar ab Go 1.20
CutPrefix wurde in Go 1.20 (Februar 2023) eingeführt — auf älteren Go-Versionen muss das HasPrefix+TrimPrefix-Muster manuell verwendet werden.
Kombiniert HasPrefix und TrimPrefix
Ein Aufruf ersetzt die typische Sequenz if HasPrefix(s,p) { s = TrimPrefix(s,p); ... } und vermeidet den doppelten Präfix-Vergleich intern.
Leeres Präfix liefert (s, true)
CutPrefix(s, "") gibt immer (s, true) zurück — konsistent mit der mathematischen Konvention, dass die leere Sequenz Präfix jeder Sequenz ist.
CutSuffix als Spiegel-Funktion
Für das andere Ende existiert seit Go 1.20 die Spiegel-Funktion strings.CutSuffix(s, suffix) mit gleicher (before, found)-Signatur.
Threadsafe
Strings sind in Go immutabel, daher ist CutPrefix ohne weitere Synchronisation aus mehreren Goroutines aufrufbar.
Byte-orientiert, aber UTF-8-sicher
Der Vergleich läuft byteweise, was bei gültigem UTF-8 trotzdem zeichengrenzenkonform funktioniert — Präfixe aus mehrbyte-Runen werden korrekt erkannt.
Idiomatisch im if-Initial-Statement
Die (value, ok)-Signatur passt direkt in if rest, ok := strings.CutPrefix(s, p); ok { ... } — ein zentrales Go-Muster, das Scope und Lesbarkeit verbessert.
Allokationsfrei
Der zurückgegebene after-String ist ein Slice des Originals — es wird kein neuer Speicher alloziert, weder bei Match noch bei Miss.