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.

Go signatur.go
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]))
}
Output
12 3

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

Go reverse_iteration.go
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]))
}
Output
15 a

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

Go byte_offset.go
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:])
}
Output
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.

Go mit_unicode.go
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)
}
Output
Ziffer: 6
Space : 9
Letter: 3

Drei 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:

FunktionRichtungMatch-KriteriumTypischer Use Case
IndexFunc(s, f)von vornePrädikat func(rune) boolerstes Sonderzeichen, Tokenstart
LastIndexFunc(s, f)von hintenPrädikat func(rune) boolletztes Sonderzeichen, Suffix-Suche
LastIndexAny(s, chars)von hintenRune in Cutset charsletztes 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.

Go letztes_sonderzeichen.go
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"))
}
Output
json
com
trenner

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

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

Weiterführende Ressourcen

Externe Quellen

/ Weiter

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

Zur Übersicht