strings.TrimLeftFunc entfernt vom Anfang eines Strings so lange aufeinanderfolgende Runes, wie das übergebene Prädikat func(rune) bool den Wert true zurückgibt. Die Funktion ist der linksseitige Spiegel zu TrimRightFunc und arbeitet rune-aware, dekodiert den String also korrekt als UTF-8 und übergibt jede Code-Point-Rune einzeln an das Prädikat. Damit lassen sich Trim-Regeln formulieren, die weit über das statische Cutset von TrimLeft hinausgehen — etwa „alle Unicode-Whitespaces", „alle Ziffern und Punkte" oder beliebige domänenspezifische Klassen.
Die Signatur zeigt klar: ein String rein, ein Prädikat als zweiter Parameter, ein neuer String raus. Das Prädikat erhält die jeweilige Rune und entscheidet binär, ob sie zum entfernbaren Präfix gehört.
func TrimLeftFunc(s string, f func(rune) bool) stringDa f als gewöhnlicher Funktionswert übergeben wird, kann jede Funktion mit passender Signatur eingesetzt werden — Methodenwerte, Closures, Standardbibliotheks-Prädikate aus dem unicode-Paket oder eigene Helper.
TrimLeftFunc läuft vom Anfang des Strings nach rechts, dekodiert Rune für Rune und bricht beim ersten false ab. Ab diesem Punkt bleibt der Rest unangetastet, auch wenn weiter hinten erneut passende Runes folgen.
package main
import (
"fmt"
"strings"
"unicode"
)
func main() {
s := "00042abc007"
r := strings.TrimLeftFunc(s, unicode.IsDigit)
fmt.Printf("%q\n", r)
}"abc007"Die hinteren 007 bleiben erhalten, weil das Prädikat beim a zum ersten Mal false liefert und der Scan dort endet. Wer auch hintere Ziffern entfernen will, greift zu TrimFunc (beidseitig) oder zu einer Kombination aus TrimLeftFunc und TrimRightFunc.
Das unicode-Paket liefert eine ganze Sammlung fertiger Prädikate, die direkt als Argument an TrimLeftFunc reichen. So entstehen kompakte, lesbare Trim-Ausdrücke für die häufigsten Rune-Klassen.
package main
import (
"fmt"
"strings"
"unicode"
)
func main() {
fmt.Printf("%q\n", strings.TrimLeftFunc(" \tHallo", unicode.IsSpace))
fmt.Printf("%q\n", strings.TrimLeftFunc("42xyz", unicode.IsDigit))
fmt.Printf("%q\n", strings.TrimLeftFunc("!!!Achtung", unicode.IsPunct))
fmt.Printf("%q\n", strings.TrimLeftFunc("\x00\x01Daten", unicode.IsControl))
}"Hallo"
"xyz"
"Achtung"
"Daten"unicode.IsSpace deckt neben ASCII-Space und Tab auch No-Break-Space (U+00A0), Em-Space und weitere Whitespace-Code-Points ab — ein wichtiger Unterschied zu naivem TrimLeft(s, " \t"). Analog erfassen IsDigit, IsLetter und IsPunct jeweils die volle Unicode-Klasse, nicht nur ASCII.
Über eine Closure lässt sich Zustand in das Prädikat einbinden, etwa ein dynamisch zusammengestellter Zeichensatz oder ein Zähler. Das ist nützlich, wenn das erlaubte Trim-Cutset erst zur Laufzeit feststeht.
package main
import (
"fmt"
"strings"
)
func main() {
cutset := map[rune]bool{'#': true, '/': true, ' ': true}
pred := func(r rune) bool {
return cutset[r]
}
s := "### // Kommentar"
fmt.Printf("%q\n", strings.TrimLeftFunc(s, pred))
}"Kommentar"Die Closure schließt über cutset und erlaubt damit Konfiguration ohne globale Variablen. Solange der gekapselte Zustand nur gelesen wird, bleibt das Prädikat auch nebenläufig sicher einsetzbar.
Die drei verwandten Funktionen unterscheiden sich in Trim-Richtung und Art der Auswahlregel. Die Tabelle macht die Unterschiede sichtbar.
| Funktion | Richtung | Auswahl |
|---|---|---|
TrimLeft(s, cutset) | nur links | statisches Cutset (String) |
TrimLeftFunc(s, f) | nur links | Prädikat func(rune) bool |
TrimFunc(s, f) | links und rechts | Prädikat func(rune) bool |
TrimRightFunc(s, f) | nur rechts | Prädikat func(rune) bool |
Faustregel: bei festem, kleinem Zeichensatz aus ASCII-Runes reicht TrimLeft. Sobald Unicode-Klassen, Bedingungen oder dynamische Mengen ins Spiel kommen, ist TrimLeftFunc die saubere Wahl.
Klassisches TrimLeft(s, " \t") übersieht alle Whitespace-Code-Points jenseits von ASCII. TrimLeftFunc(s, unicode.IsSpace) dagegen normalisiert führenden Whitespace vollständig — wichtig für Eingaben aus Copy-Paste, Textverarbeitungen oder Mehrsprachigkeit.
package main
import (
"fmt"
"strings"
"unicode"
)
func main() {
// U+00A0 ist ein No-Break-Space, U+2003 ein Em-Space
s := " \tInhalt"
naiv := strings.TrimLeft(s, " \t")
robust := strings.TrimLeftFunc(s, unicode.IsSpace)
fmt.Printf("naiv: %q\n", naiv)
fmt.Printf("robust: %q\n", robust)
}naiv: " \tInhalt"
robust: "Inhalt"Die naive Variante lässt den Non-ASCII-Whitespace stehen, weil U+00A0 und U+2003 nicht im Cutset " \t" enthalten sind. Die robuste Variante mit unicode.IsSpace erkennt alle Whitespace-Klassen korrekt und liefert den erwarteten Reintext.
In Logs oder mathematischen Notationen taucht oft das Muster 42. 3.14159 auf — eine Zeilennummer mit Punkt und Leerraum, gefolgt vom eigentlichen Wert. TrimLeftFunc mit einem zusammengesetzten Prädikat trennt das in einem Schritt.
package main
import (
"fmt"
"strings"
"unicode"
)
func parseWert(zeile string) string {
return strings.TrimLeftFunc(zeile, func(r rune) bool {
return unicode.IsDigit(r) || r == '.' || r == ' '
})
}
func main() {
zeilen := []string{
"1. 3.14159",
"42. Hallo Welt",
"100. -273.15",
}
for _, z := range zeilen {
fmt.Printf("%-20s -> %q\n", z, parseWert(z))
}
}1. 3.14159 -> "3.14159"
42. Hallo Welt -> "Hallo Welt"
100. -273.15 -> "-273.15"Beachte den dritten Fall: -273.15 bleibt vollständig erhalten, weil das Prädikat beim - zum ersten Mal false liefert. Die folgenden Ziffern und Punkte werden nicht mehr angefasst — genau das gewünschte Verhalten, weil -273.15 zusammengehört.
Bei der Verarbeitung von Markdown-Listen sollen führende -, * und Whitespace verschwinden, der eigentliche Eintragstext aber unverändert bleiben. Ein einziges Prädikat erledigt das robust.
package main
import (
"fmt"
"strings"
"unicode"
)
func bulletText(zeile string) string {
return strings.TrimLeftFunc(zeile, func(r rune) bool {
return r == '-' || r == '*' || unicode.IsSpace(r)
})
}
func main() {
listen := []string{
"- Erster Punkt",
" * Eingerückter Punkt",
"--- Trennstrich? Nein, Text.",
}
for _, l := range listen {
fmt.Printf("%-32s -> %q\n", l, bulletText(l))
}
}- Erster Punkt -> "Erster Punkt"
* Eingerückter Punkt -> "Eingerückter Punkt"
--- Trennstrich? Nein, Text. -> "Trennstrich? Nein, Text."Auch eingerückte Einträge funktionieren, weil unicode.IsSpace den führenden Leerraum vor dem * mit abdeckt. Der Trim-Lauf wechselt frei zwischen Whitespace und Bullet-Zeichen, bis das erste „echte" Textzeichen erreicht ist.
Prädikat steuert linkes Trim
Das Verhalten wird vollständig durch func(rune) bool bestimmt — keine fest verdrahtete Zeichenliste.
Rune-aware
Die Iteration dekodiert UTF-8 sauber und übergibt vollständige Code-Points, nicht einzelne Bytes.
Ideal mit unicode-Paket
unicode.IsSpace, IsDigit, IsLetter, IsPunct und IsControl lassen sich direkt als Argument einsetzen.
Closures erlauben Capture
Über Closures kann Konfiguration (Maps, Slices, Flags) ins Prädikat einfließen, ohne globale Variablen.
Allgemeiner als TrimLeft mit Cutset
Jedes Cutset lässt sich als Prädikat ausdrücken, aber nicht jedes Prädikat als Cutset — TrimLeftFunc ist die obere Schranke.
Thread-safe, wenn das Prädikat es ist
Die Funktion selbst hat keinen geteilten Zustand; Nebenläufigkeit hängt allein am übergebenen f ab.
No-op, wenn nichts trimbar
Liefert f schon für die erste Rune false, kommt der Eingabestring unverändert zurück.
TrimRightFunc als Spiegel
Für rechtsseitiges Trim mit identischer Prädikat-Signatur steht TrimRightFunc bereit; TrimFunc deckt beide Seiten ab.