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.

Go signatur.go
func TrimLeftFunc(s string, f func(rune) bool) string

Da 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.

Go iteration.go
package main

import (
	"fmt"
	"strings"
	"unicode"
)

func main() {
	s := "00042abc007"
	r := strings.TrimLeftFunc(s, unicode.IsDigit)
	fmt.Printf("%q\n", r)
}
Output
"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.

Go unicode_helpers.go
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))
}
Output
"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.

Go closure.go
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))
}
Output
"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.

FunktionRichtungAuswahl
TrimLeft(s, cutset)nur linksstatisches Cutset (String)
TrimLeftFunc(s, f)nur linksPrädikat func(rune) bool
TrimFunc(s, f)links und rechtsPrädikat func(rune) bool
TrimRightFunc(s, f)nur rechtsPrä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.

Go indent.go
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)
}
Output
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.

Go praxis_zeile.go
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))
	}
}
Output
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.

Go praxis_markdown.go
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))
	}
}
Output
- 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.

Weiterführende Ressourcen

Externe Quellen

/ Weiter

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

Zur Übersicht