strings.TrimFunc ist das flexibelste Trim-Werkzeug der Standardbibliothek: statt Whitespace oder einem fixen Cutset entfernt die Funktion vom Anfang und Ende eines Strings solange Runes, für die ein selbst definiertes Prädikat func(rune) bool den Wert true liefert. Damit lässt sich jede beliebige Klassifikation von Zeichen als Trim-Kriterium nutzen — von Unicode-Kategorien über numerische Bereiche bis hin zu fachlich motivierten Filtern.

Im Unterschied zu byteweisen Lösungen arbeitet TrimFunc rune-aware, das heißt UTF-8 wird korrekt dekodiert und das Prädikat sieht echte Codepoints. In Kombination mit dem unicode-Paket (IsSpace, IsDigit, IsPunct, IsControl) entstehen sehr ausdrucksstarke Einzeiler, die auch mit nicht-ASCII-Eingaben zuverlässig funktionieren.

Die Signatur ist denkbar schlank: ein String plus eine Prädikatfunktion, Rückgabe ist der getrimmte String. Da Strings in Go immutable sind, liefert TrimFunc einen neuen String — der Slice-Header kann jedoch denselben Backing-Array referenzieren, sofern lediglich am Rand abgeschnitten wurde.

Go signature.go
func TrimFunc(s string, f func(rune) bool) string

Das Prädikat f wird für jede Rune am Rand aufgerufen. Sobald f(r) den Wert false liefert, stoppt das Trimmen auf dieser Seite — anschließend wird das gleiche Spiel von der anderen Seite gespielt.

Das Prädikat fungiert als „darf entfernt werden"-Test: Liefert es true, wird die jeweilige Rune am Rand abgeschnitten und die Iteration wandert eine Position weiter nach innen. Liefert es false, ist das jeweilige Ende fixiert. Diese asymmetrische Logik („true heißt weg") ist wichtig zu verinnerlichen, weil intuitive Formulierungen wie „behalte Buchstaben" invertiert werden müssen.

Go praedikat.go
package main

import (
	"fmt"
	"strings"
)

func main() {
	istKlammer := func(r rune) bool {
		return r == '(' || r == ')' || r == '[' || r == ']'
	}

	s := "[[(hallo welt)]]"
	fmt.Printf("%q\n", strings.TrimFunc(s, istKlammer))
}
Output
"hallo welt"

Beachte, dass das Prädikat ausschließlich am Rand greift — Klammern in der Mitte bleiben unangetastet. Wer auch innere Vorkommen entfernen will, braucht strings.Map oder strings.ReplaceAll.

Das unicode-Paket liefert eine ganze Familie passender Prädikate, die direkt als zweites Argument übergeben werden können. Da Funktionen in Go Werte sind, entfällt die Wrapper-Syntax komplett — der Aufruf wird zum lesbaren Einzeiler.

Go unicode_predicates.go
package main

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

func main() {
	s := "   \t\nHallo Welt\n\r  "
	fmt.Printf("%q\n", strings.TrimFunc(s, unicode.IsSpace))

	id := "0042-Bestellung-9981"
	fmt.Printf("%q\n", strings.TrimFunc(id, unicode.IsDigit))

	noisy := "!!!Wichtig???"
	fmt.Printf("%q\n", strings.TrimFunc(noisy, unicode.IsPunct))
}
Output
"Hallo Welt"
"-Bestellung-"
"Wichtig"

unicode.IsSpace erkennt nicht nur ASCII-Whitespace, sondern auch Tabs, Zeilenumbrüche, geschützte Leerzeichen (U+00A0) und weitere Unicode-Whitespace-Codepoints. Genau diese Breite macht die Kombination so robust gegenüber realen Eingaben aus Webformularen oder Copy-Paste.

Sobald mehrere Kriterien kombiniert oder externe Konfiguration eingebunden werden soll, lohnt sich eine Closure. Sie kann Variablen aus dem umgebenden Scope einfangen und mehrere unicode-Checks per logischem ODER verknüpfen.

Go closure.go
package main

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

func main() {
	zusaetzlich := "#*"

	trimme := func(r rune) bool {
		if unicode.IsSpace(r) || unicode.IsPunct(r) {
			return true
		}
		return strings.ContainsRune(zusaetzlich, r)
	}

	s := "  ##*Wichtiger Hinweis!*##  "
	fmt.Printf("%q\n", strings.TrimFunc(s, trimme))
}
Output
"Wichtiger Hinweis"

Die Closure fängt zusaetzlich ein und prüft pro Rune drei Bedingungen. Dadurch entsteht ein wiederverwendbarer Trim-Filter, der ohne globalen Zustand auskommt und sich pro Aufrufkontext anders konfigurieren lässt.

Die drei Trim-Varianten überlappen sich semantisch, unterscheiden sich aber in Eingabeformat und Performance. Die folgende Übersicht ordnet sie ein.

FunktionKriteriumEingabeBemerkung
TrimFuncbeliebiges Prädikatfunc(rune) boolflexibelste Variante, rune-aware
TrimSpaceUnicode-Whitespacekeineoptimierter ASCII-Fast-Path
TrimCutset von Runesstringeinfach, wenn fixe Zeichenmenge

Faustregel: Wenn die Trim-Menge als Aufzählung beschreibbar ist, nimm Trim. Geht es um Whitespace, nimm TrimSpace. Für alles andere — Kategorien, Bereiche, dynamische Regeln — ist TrimFunc das Mittel der Wahl.

Semantisch entspricht strings.TrimFunc(s, unicode.IsSpace) exakt strings.TrimSpace(s) — beide entfernen Unicode-Whitespace beidseitig. In der Praxis ist TrimSpace aber deutlich schneller, weil es einen ASCII-Fast-Path besitzt und für reine ASCII-Eingaben gar keine UTF-8-Dekodierung anstößt.

Go equivalence.go
package main

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

func main() {
	s := "   Hallo Welt   "
	a := strings.TrimFunc(s, unicode.IsSpace)
	b := strings.TrimSpace(s)
	fmt.Println(a == b, a)
}
Output
true Hallo Welt

Für Hot-Paths mit überwiegend ASCII-Strings ist TrimSpace daher die bessere Wahl. TrimFunc mit IsSpace lohnt sich dann, wenn das gleiche Prädikat noch um weitere Bedingungen ergänzt werden soll.

Ein typischer Fall aus dem Daten-Cleanup: IDs werden aus heterogenen Quellen importiert und tragen führende Versions-Präfixe oder nachgestellte Sequenznummern. Ein einziges Prädikat räumt beides ab, ohne dass die fachliche Mitte angetastet wird.

Go praxis_id.go
package main

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

func main() {
	rohIDs := []string{
		"2024.Bestellung-A17.001",
		"99.Rechnung-B42.7",
		"...Kunde-K9...",
	}

	randPraedikat := func(r rune) bool {
		return unicode.IsDigit(r) || r == '.'
	}

	for _, id := range rohIDs {
		fmt.Printf("%-30q -> %q\n", id, strings.TrimFunc(id, randPraedikat))
	}
}
Output
"2024.Bestellung-A17.001"      -> "Bestellung-A17"
"99.Rechnung-B42.7"            -> "Rechnung-B42"
"...Kunde-K9..."               -> "Kunde-K"

Das Ergebnis zeigt schön die rune-weise Iteration: am rechten Rand wird Kunde-K9... zu Kunde-K, weil nach den Punkten direkt die 9 fällt und erst beim K gestoppt wird. Wer das nicht möchte, muss das Prädikat enger fassen oder eine Reihenfolge per TrimRightFunc und TrimLeftFunc modellieren.

Eingaben aus Netzwerk-Streams oder Terminal-Ausgaben enthalten gelegentlich unsichtbare Steuerzeichen wie \x00, \x07 oder ANSI-Escape-Reste. Diese stören Logs, Vergleiche und Persistenz — TrimFunc mit unicode.IsControl entfernt sie zuverlässig vom Rand.

Go praxis_control.go
package main

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

func main() {
	dreckig := "\x00\x07Login erfolgreich\x1b\x00"
	sauber := strings.TrimFunc(dreckig, unicode.IsControl)
	fmt.Printf("vor:  %q\nnach: %q\n", dreckig, sauber)
}
Output
vor:  "\x00\x07Login erfolgreich\x1b\x00"
nach: "Login erfolgreich"

Da unicode.IsControl auch Codepoints jenseits von ASCII abdeckt (etwa U+0080U+009F), funktioniert dieser Filter auch dann, wenn die Daten aus mehrsprachigen Quellen stammen. Für ein vollständiges Säubern auch innerhalb des Strings ist anschließend strings.Map der nächste Baustein.

Flexibelstes Trim-Werkzeug

TrimFunc deckt jeden Anwendungsfall ab, den Trim oder TrimSpace nicht direkt unterstützen — vom Prädikat über Bereiche bis zu Closures mit eingefangenem Zustand.

Rune-weise Iteration

Die Iteration läuft codepoint-genau, nicht byteweise — UTF-8-Sequenzen werden korrekt dekodiert, bevor das Prädikat aufgerufen wird.

Synergie mit unicode-Paket

Die Prädikate unicode.IsSpace, IsDigit, IsPunct, IsControl und IsLetter lassen sich ohne Wrapper direkt übergeben.

Closures erlauben Capture

Eine Closure kann externe Konfiguration einfangen und mehrere Checks kombinieren — wiederverwendbar ohne globalen Zustand.

TrimSpace bleibt der ASCII-King

TrimFunc(s, unicode.IsSpace) ist semantisch gleich zu TrimSpace, letzteres besitzt aber einen optimierten ASCII-Fast-Path.

Beidseitig, aber nur am Rand

Getrimmt wird ausschließlich vom Anfang und Ende — Treffer in der Stringmitte bleiben unverändert, dafür gibt es strings.Map.

Thread-Safety hängt am Prädikat

Die Funktion selbst ist nebenläufigkeitssicher; ob ein Aufruf threadsafe ist, entscheidet sich am übergebenen Prädikat und seinen eingefangenen Variablen.

No-op bleibt günstig

Trifft das Prädikat an beiden Enden sofort false, gibt TrimFunc denselben String zurück, ohne neuen Speicher zu allokieren.

Weiterführende Ressourcen

Externe Quellen

/ Weiter

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

Zur Übersicht