strings.TrimRightFunc entfernt vom Ende eines Strings solange Runes, wie das übergebene Prädikat func(rune) bool den Wert true zurückgibt. Sobald die erste Rune von rechts auftaucht, für die das Prädikat false liefert, stoppt die Funktion und liefert den Rest. Der linke Teil des Strings bleibt dabei vollständig unangetastet — egal welche Runen dort stehen.
Im Gegensatz zu TrimRight, das mit einem festen Cutset arbeitet, kann das Prädikat hier beliebig komplex sein: Klassenprüfungen aus dem unicode-Paket, eigene Logik mit Closures, Bereichsabfragen. Die Iteration ist rune-aware und läuft rückwärts; Mehrbyte-UTF-8-Sequenzen werden korrekt als eine Rune behandelt. TrimRightFunc ist damit das spiegelbildliche Gegenstück zu TrimLeftFunc und das Werkzeug der Wahl, wenn nur das rechte Ende eines Strings nach individuellen Kriterien beschnitten werden soll.
Die Signatur erwartet den Eingabe-String und das Prädikat als zweites Argument. Funktionen in Go sind first-class Werte, daher lässt sich jede passende Lambda, Methode oder benannte Funktion einsetzen.
func TrimRightFunc(s string, f func(rune) bool) stringDas Prädikat wird auf jede Rune von rechts nach links angewandt. Sobald f(r) erstmals false liefert, endet die Trim-Schleife und der String wird an dieser Stelle abgeschnitten.
Intern dekodiert TrimRightFunc die UTF-8-Bytes von hinten und ruft das Prädikat solange auf, bis es false zurückgibt oder der String leer ist. Das ist effizient und benötigt keinen Zwischen-Slice, da nur ein neuer String-Header mit verkürzter Länge entsteht.
package main
import (
"fmt"
"strings"
)
func main() {
s := "Hallo Welt!!!"
out := strings.TrimRightFunc(s, func(r rune) bool {
return r == '!'
})
fmt.Printf("%q\n", out)
}"Hallo Welt"Die drei Ausrufezeichen am Ende fallen weg, alles davor — inklusive Leerzeichen und Umlaut-fähigen Runen — bleibt unverändert. Hätte ein ! mitten im String gestanden, wäre es nicht betroffen, denn die Schleife stoppt beim ersten Nicht-Treffer von rechts.
In der Praxis kombiniert man TrimRightFunc meist mit den Prädikaten aus dem unicode-Paket. Diese sind Unicode-vollständig und decken weit mehr ab als die ASCII-Welt: IsSpace erkennt z. B. auch das No-Break Space (U+00A0), IsPunct deckt typografische Anführungszeichen ab.
package main
import (
"fmt"
"strings"
"unicode"
)
func main() {
a := strings.TrimRightFunc("Zeile\t\n ", unicode.IsSpace)
b := strings.TrimRightFunc("Artikel-12345", unicode.IsDigit)
c := strings.TrimRightFunc("Frage???", unicode.IsPunct)
d := strings.TrimRightFunc("Daten\x00\x01", unicode.IsControl)
fmt.Printf("%q\n%q\n%q\n%q\n", a, b, c, d)
}"Zeile"
"Artikel-"
"Frage"
"Daten"Jedes der vier Beispiele zeigt einen typischen Cleanup-Fall: Whitespace, Ziffern, Interpunktion, Steuerzeichen. Das Prädikat aus dem unicode-Paket übernimmt die fachlich korrekte Klassifizierung, TrimRightFunc die Iteration und das Abschneiden.
Closures sind besonders mächtig, weil sie Zustand aus dem umgebenden Scope einfangen. So lässt sich z. B. ein dynamisches Cutset aus einer Variable bauen, ohne ein eigenes Prädikat-Typ-Konstrukt anzulegen.
package main
import (
"fmt"
"strings"
)
func main() {
suffix := "xyz0123"
in := map[rune]bool{}
for _, r := range suffix {
in[r] = true
}
pred := func(r rune) bool { return in[r] }
out := strings.TrimRightFunc("config-files-x0y3z1", pred)
fmt.Printf("%q\n", out)
}"config-files-"Die Closure pred greift auf die Map in zu, ohne dass diese als Parameter durchgereicht werden muss. Damit lässt sich ein zur Laufzeit konfigurierbares Trim-Verhalten bauen, das mit reinem TrimRight (Cutset als String) zwar auch ginge, aber bei komplexeren Bedingungen schnell unflexibel würde.
Die drei Varianten überschneiden sich im Zweck, unterscheiden sich aber in der Art, wie das Trim-Kriterium ausgedrückt wird, und in der Seite, an der getrimmt wird.
| Funktion | Kriterium | Seite | Wann nutzen? |
|---|---|---|---|
TrimRight | Cutset (String) | rechts | feste, einfache Zeichenmenge |
TrimRightFunc | Prädikat func(rune) | rechts | dynamische/klassenbasierte Logik |
TrimFunc | Prädikat func(rune) | beidseitig | wenn links und rechts gleichbehandelt werden |
TrimRight ist am performantesten für triviale Fälle, TrimRightFunc der Allrounder mit Unicode-Klassen, TrimFunc der Spezialfall für symmetrischen Beschnitt.
Der wohl häufigste Anwendungsfall ist robustes Trailing-Whitespace-Entfernen. Während TrimRight(s, " \t\n") nur die explizit aufgezählten ASCII-Zeichen erwischt, deckt unicode.IsSpace das volle Spektrum ab — inklusive U+00A0, U+2028 und Tabulator-Varianten.
package main
import (
"fmt"
"strings"
"unicode"
)
func main() {
s := "Eintrag\t \n"
out := strings.TrimRightFunc(s, unicode.IsSpace)
fmt.Printf("%q (Länge: %d -> %d)\n", out, len(s), len(out))
}"Eintrag" (Länge: 11 -> 7)Hier wird der Trailing-Whitespace zuverlässig entfernt — auch No-Break Space und Unicode-Line-Separator würden mit unicode.IsSpace korrekt erkannt. Mit reinem TrimRight und einem ASCII-Cutset würde solcher Müll am Ende kleben bleiben.
Beim Normalisieren von Sätzen für Suche oder Hashing stört oft nur die nachgestellte Interpunktion. TrimRightFunc mit unicode.IsPunct schneidet sie sauber ab, ohne Apostrophe oder Bindestriche innerhalb des Satzes anzutasten.
package main
import (
"fmt"
"strings"
"unicode"
)
func normalize(satz string) string {
return strings.TrimRightFunc(satz, unicode.IsPunct)
}
func main() {
saetze := []string{
"Was ist los?!",
"Geht's dir gut...",
"Sicher!",
"Hallo-Welt.",
}
for _, s := range saetze {
fmt.Printf("%-22q -> %q\n", s, normalize(s))
}
}"Was ist los?!" -> "Was ist los"
"Geht's dir gut..." -> "Geht's dir gut"
"Sicher!" -> "Sicher"
"Hallo-Welt." -> "Hallo-Welt"Das Apostroph in Geht's und der Bindestrich in Hallo-Welt bleiben erhalten, weil sie nicht am Ende stehen. Nur die abschließende Interpunktion verschwindet — genau das, was für eine sinnvolle Satz-Normalisierung gebraucht wird.
Library-Namen tragen häufig ein Versions-Suffix wie lib-v1.2.3 oder pkg-v2. Mit einem zusammengesetzten Prädikat lässt sich dieser Suffix entfernen, sodass der reine Basis-Name übrigbleibt.
package main
import (
"fmt"
"strings"
"unicode"
)
func stripVersion(name string) string {
return strings.TrimRightFunc(name, func(r rune) bool {
return unicode.IsDigit(r) || r == '.' || r == 'v'
})
}
func main() {
libs := []string{
"redis-v7.2.1",
"postgres-v16",
"nginx-v1.25.3",
"app-server",
}
for _, l := range libs {
base := strings.TrimRight(stripVersion(l), "-")
fmt.Printf("%-18s -> %s\n", l, base)
}
}redis-v7.2.1 -> redis
postgres-v16 -> postgres
nginx-v1.25.3 -> nginx
app-server -> app-serverDas Prädikat akzeptiert Ziffern, Punkte und das Versions-v. Anschließend räumt ein zweites TrimRight den verbliebenen Bindestrich weg. Bei app-server greift das Prädikat schon beim r nicht mehr — die Funktion lässt den String unverändert.
Reverse-Iteration
TrimRightFunc läuft von rechts nach links durch den String und stoppt beim ersten false des Prädikats.
rune-aware
Die Iteration dekodiert UTF-8 korrekt; Mehrbyte-Sequenzen werden als eine Rune behandelt.
Ideal mit unicode-Paket
Prädikate wie unicode.IsSpace, unicode.IsPunct oder unicode.IsDigit decken volles Unicode ab.
Closures erlauben Capture
Das Prädikat darf Zustand aus dem umgebenden Scope einfangen — etwa Sets oder Konfiguration.
Spiegel zu TrimLeftFunc
Die Funktion verhält sich exakt symmetrisch zu TrimLeftFunc, nur eben am rechten Ende.
Allgemeiner als TrimRight mit Cutset
Wo TrimRight nur Zeichenmengen kennt, erlaubt TrimRightFunc beliebige Logik bis hin zu Range-Checks.
Threadsafe, wenn Prädikat es ist
Die Funktion selbst hat keinen geteilten Zustand; Sicherheit hängt am übergebenen Prädikat.
No-op bei nicht-trimbarem Ende
Liefert das Prädikat für die letzte Rune sofort false, wird der String unverändert zurückgegeben.