strings.LastIndexFunc durchläuft einen String rune-weise von hinten und liefert den Byte-Offset der letzten Rune, für die das übergebene Prädikat f(r) == true ergibt. Wenn keine einzige Rune das Prädikat erfüllt, kommt -1 zurück.
Während IndexFunc für das linksbündige Auffinden des ersten Treffers gedacht ist, ist LastIndexFunc das Gegenstück für rechtsbündige Suchen: das letzte Sonderzeichen, die letzte Ziffer, die letzte Whitespace-Position vor dem Stringende. Damit eignet sich die Funktion für Tokenisierung von hinten, für eigene Trim-Helfer oder für die Frage, ab wo ein Suffix beginnt.
Die Signatur ist symmetrisch zu IndexFunc und nutzt dasselbe Prädikat-Konzept. Das Prädikat ist eine beliebige Funktion vom Typ func(rune) bool — also entweder eine Funktion aus dem unicode-Paket oder eine selbst geschriebene.
package main
import (
"fmt"
"strings"
"unicode"
)
func main() {
s := "Version 2.7.3-rc"
idx := strings.LastIndexFunc(s, unicode.IsDigit)
fmt.Println(idx, string(s[idx]))
}12 3Die letzte Ziffer im String ist die 3 direkt vor -rc. Der Rückgabewert 12 ist ein Byte-Offset, kein Rune-Index — das wird gleich noch wichtig.
Intern dekodiert LastIndexFunc den String von hinten nach vorne entlang der UTF-8-Boundaries. Die Reihenfolge ist also nicht byteweise, sondern rune-weise rückwärts, und das auch bei Multi-Byte-Sequenzen sauber.
package main
import (
"fmt"
"strings"
)
func main() {
istVokal := func(r rune) bool {
switch r {
case 'a', 'e', 'i', 'o', 'u', 'ä', 'ö', 'ü':
return true
}
return false
}
s := "Programmiersprache"
idx := strings.LastIndexFunc(s, istVokal)
fmt.Println(idx, string(s[idx]))
}15 aDas eigene Prädikat istVokal wird auf jede Rune von hinten angewendet, bis die erste Übereinstimmung gefunden wird. Sobald das Prädikat true liefert, bricht die Iteration ab und der Offset der Treffer-Rune wird zurückgegeben.
Wie alle Index-Funktionen im strings-Paket gibt LastIndexFunc einen Byte-Offset zurück, keinen Rune-Index. Bei Multi-Byte-Runen zeigt der Offset auf das erste Byte der Treffer-Sequenz, sodass s[idx:] direkt eine valide UTF-8-Sequenz beginnt.
package main
import (
"fmt"
"strings"
"unicode"
)
func main() {
s := "Café — résumé"
idx := strings.LastIndexFunc(s, func(r rune) bool {
return r > unicode.MaxASCII
})
fmt.Println("Offset:", idx)
fmt.Println("Suffix ab Treffer:", s[idx:])
}Offset: 14
Suffix ab Treffer: éDas Prädikat sucht nach der letzten Nicht-ASCII-Rune und trifft das finale é. Der Offset zeigt auf das erste Byte dieser 2-Byte-UTF-8-Sequenz — s[14:] liefert deshalb ein wohlgeformtes Suffix und keine kaputte Byte-Mitte.
Die elegantesten Aufrufe von LastIndexFunc kombinieren die Funktion mit Prädikaten aus dem unicode-Paket. Funktionen wie unicode.IsDigit, unicode.IsSpace oder unicode.IsPunct haben bereits die passende Signatur func(rune) bool und sind Unicode-bewusst.
package main
import (
"fmt"
"strings"
"unicode"
)
func main() {
s := "user_42 \t"
letzteZiffer := strings.LastIndexFunc(s, unicode.IsDigit)
letzterSpace := strings.LastIndexFunc(s, unicode.IsSpace)
letzteLetter := strings.LastIndexFunc(s, unicode.IsLetter)
fmt.Println("Ziffer:", letzteZiffer)
fmt.Println("Space :", letzterSpace)
fmt.Println("Letter:", letzteLetter)
}Ziffer: 6
Space : 9
Letter: 3Drei Suchen, drei verschiedene Unicode-Kategorien — ohne dass eigene Tabellen gepflegt werden müssen. Das unicode-Paket deckt die gängigen Klassifikatoren ab und arbeitet mit dem vollen Unicode-Bereich, nicht nur mit ASCII.
Diese drei Funktionen wirken auf den ersten Blick ähnlich, unterscheiden sich aber in Richtung und Match-Mechanismus. Die folgende Tabelle stellt die Unterschiede gegenüber:
| Funktion | Richtung | Match-Kriterium | Typischer Use Case |
|---|---|---|---|
IndexFunc(s, f) | von vorne | Prädikat func(rune) bool | erstes Sonderzeichen, Tokenstart |
LastIndexFunc(s, f) | von hinten | Prädikat func(rune) bool | letztes Sonderzeichen, Suffix-Suche |
LastIndexAny(s, chars) | von hinten | Rune in Cutset chars | letztes Vorkommen einer bekannten Menge |
LastIndexAny ist die richtige Wahl, wenn die gesuchten Runen eine fixe, kleine Menge sind. LastIndexFunc greift, sobald das Kriterium dynamisch ist oder eine Unicode-Kategorie betrifft.
Ein häufiges Tokenisierungsproblem: aus einem Pfad oder Bezeichner soll der Teil nach dem letzten Sonderzeichen extrahiert werden. Mit unicode.IsPunct und LastIndexFunc ist das eine Zeile — wer nur eine sichere Slice-Grenze braucht und mit ASCII-Punctuation arbeitet, kann den Offset direkt als Startpunkt nehmen.
package main
import (
"fmt"
"strings"
"unicode"
"unicode/utf8"
)
func suffixNachPunct(s string) string {
idx := strings.LastIndexFunc(s, unicode.IsPunct)
if idx < 0 {
return s
}
_, size := utf8.DecodeRuneInString(s[idx:])
return s[idx+size:]
}
func main() {
fmt.Println(suffixNachPunct("config.local.json"))
fmt.Println(suffixNachPunct("user@example.com"))
fmt.Println(suffixNachPunct("ohne-trenner"))
}json
com
trennerDer Helper schneidet jeweils ab dem Zeichen nach dem letzten Punctuation-Treffer. Die Suffix-Berechnung idx + size ist wichtig, weil ein blindes idx+1 bei Multi-Byte-Sonderzeichen mitten in eine UTF-8-Sequenz fallen würde — utf8.DecodeRuneInString liefert die korrekte Breite.
strings.TrimRightFunc existiert in der Stdlib, aber zur Veranschaulichung lässt sich der Mechanismus mit LastIndexFunc plus Negation des Prädikats nachbauen. Das ist ein gutes Beispiel, wie sich Index-Funktionen zu Slice-Operationen zusammensetzen.
package main
import (
"fmt"
"strings"
"unicode"
"unicode/utf8"
)
// trimRightCustom entfernt am Ende alle Runen, für die f true liefert.
func trimRightCustom(s string, f func(rune) bool) string {
// Position der letzten Rune, die das Prädikat NICHT erfüllt:
idx := strings.LastIndexFunc(s, func(r rune) bool {
return !f(r)
})
if idx < 0 {
return ""
}
_, size := utf8.DecodeRuneInString(s[idx:])
return s[:idx+size]
}
func main() {
fmt.Printf("%q\n", trimRightCustom("hallo \t\n", unicode.IsSpace))
fmt.Printf("%q\n", trimRightCustom("zahl123", unicode.IsDigit))
fmt.Printf("%q\n", trimRightCustom(" ", unicode.IsSpace))
}"hallo"
"zahl"
""Der Trick ist die Negation im Prädikat: gesucht wird die letzte Rune, die nicht getrimmt werden soll. Alles ab idx + size ist Trimm-Material und fällt durch das Slicing weg. Bei reinen Trimm-Strings liefert LastIndexFunc -1, sodass der Helper sauber den leeren String zurückgibt.
Reverse rune-weise Iteration
LastIndexFunc läuft von hinten durch den String und dekodiert dabei UTF-8-Sequenzen rückwärts entlang der Rune-Boundaries.
UTF-8-korrekt
Multi-Byte-Runen werden als Einheit behandelt — das Prädikat sieht echte rune-Werte, keine einzelnen Bytes.
Byte-Offset als Rückgabe
Der Rückgabewert ist ein Byte-Index in den Quellstring, kein Rune-Index; bei Multi-Byte zeigt er auf das erste Byte der Treffer-Rune.
-1 bei Nicht-Treffer
Erfüllt keine einzige Rune das Prädikat, liefert die Funktion -1 — typischer Sentinel der strings-Index-Familie.
Ideal mit unicode-Paket
Prädikate wie unicode.IsDigit, IsSpace, IsPunct, IsLetter passen direkt zur Signatur und decken die meisten Praxisfälle ab.
Spiegel zu IndexFunc
LastIndexFunc ist das rechtsbündige Gegenstück zu IndexFunc; beide nutzen denselben Prädikat-Typ und dieselbe Sentinel-Semantik.
Threadsafe bei reinem Prädikat
Da nur über den String iteriert wird und die Funktion keinen Zustand hält, ist der Aufruf nebenläufig sicher, sofern das Prädikat es ebenfalls ist.
Alternative zu eigenem Rückwärts-Loop
Ein selbst geschriebener for i := len(s); i > 0;-Loop mit utf8.DecodeLastRuneInString lässt sich durch einen LastIndexFunc-Aufruf ersetzen.