strings.ContainsFunc ist die flexibelste der Contains-Familie und seit Go 1.21 Teil der Standardbibliothek. Die Funktion bekommt einen String und ein Prädikat vom Typ func(rune) bool übergeben und liefert true, sobald irgendeine Rune im String die Bedingung erfüllt. Intern iteriert sie rune-weise über die UTF-8-Codepoints und bricht beim ersten Treffer ab, sodass weder ein vorher zusammengebauter Cutset noch ein expliziter Loop nötig ist. Damit lässt sich dynamisch prüfen, ob ein String etwa ein Steuerzeichen, ein Sonderzeichen aus einer Unicode-Kategorie oder eine projektspezifische Bedingung enthält.

Die Signatur ist bewusst minimal gehalten und macht den Vertrag explizit: Eingabe ist der zu durchsuchende String, das Prädikat entscheidet pro Rune über Treffer oder Nicht-Treffer. Rückgabewert ist ein simples Bool — wer die Position braucht, greift zu strings.IndexFunc, das exakt dasselbe Iterationsschema verwendet.

Go signatur.go
// ContainsFunc berichtet, ob irgendein Rune r in s
// f(r) == true erfuellt.
// Seit Go 1.21.
func ContainsFunc(s string, f func(rune) bool) bool

Wichtig ist die Garantie der rune-weisen Iteration: Multi-Byte-Sequenzen werden korrekt dekodiert, das Prädikat sieht echte Codepoints und nicht etwa rohe Bytes. Damit ist ContainsFunc UTF-8-sicher und eignet sich für internationalisierte Eingaben ohne weitere Vorverarbeitung.

Konzeptionell entspricht ContainsFunc einem for _, r := range s { if f(r) { return true } }. Sobald das Prädikat einmal true liefert, bricht die Funktion ab — der Rest des Strings wird nicht mehr besucht. Bei einem Prädikat ohne Treffer läuft die Schleife bis zum Ende durch und gibt false zurück.

Go iteration.go
package main

import (
	"fmt"
	"strings"
)

func istVokal(r rune) bool {
	switch r {
	case 'a', 'e', 'i', 'o', 'u', 'A', 'E', 'I', 'O', 'U':
		return true
	}
	return false
}

func main() {
	fmt.Println(strings.ContainsFunc("rhythm", istVokal)) // false
	fmt.Println(strings.ContainsFunc("hallo", istVokal))  // true
	fmt.Println(strings.ContainsFunc("", istVokal))       // false
}
Output
false
true
false

Das Beispiel zeigt zwei Eigenheiten: Ein leerer String liefert immer false, weil keine Rune iteriert wird, und die Reihenfolge der Prüfung folgt der Lesereihenfolge des Strings — relevant, falls das Prädikat Seiteneffekte hätte (was es nicht sollte).

Der typische Einsatz von ContainsFunc kombiniert es mit den vorgefertigten Klassifikatoren aus dem unicode-Paket. Funktionen wie unicode.IsSpace, unicode.IsDigit, unicode.IsPunct oder unicode.IsControl haben exakt die passende Signatur func(rune) bool und können direkt als Argument übergeben werden.

Go unicode_kategorien.go
package main

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

func main() {
	eingabe := "Bestellung Nr. 42"

	fmt.Println(strings.ContainsFunc(eingabe, unicode.IsDigit))   // true
	fmt.Println(strings.ContainsFunc(eingabe, unicode.IsSpace))   // true
	fmt.Println(strings.ContainsFunc(eingabe, unicode.IsPunct))   // true
	fmt.Println(strings.ContainsFunc("CleanID", unicode.IsSpace)) // false
}
Output
true
true
true
false

Diese Komposition ist deutlich robuster als manuell zusammengebaute Cutsets in strings.ContainsAny, weil die Unicode-Kategorien sämtliche Codepoints einer Klasse erfassen — also auch typografische Anführungszeichen, Geviertstriche oder fremdsprachige Ziffern. Wer „mindestens eine Ziffer" prüfen will, schreibt nicht ContainsAny(s, "0123456789"), sondern ContainsFunc(s, unicode.IsDigit).

Das Prädikat muss kein vorhandener Bezeichner sein — eine Closure erlaubt beliebig komplexe Bedingungen, inklusive Capture von Konfigurationswerten aus dem umgebenden Scope. So lassen sich Range-Checks, Kombinationen mehrerer Kategorien oder projektspezifische Whitelists ausdrücken, ohne eine eigene Schleife zu schreiben.

Go eigene_praedikate.go
package main

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

func main() {
	// Range-Check: enthaelt s eine ASCII-Klein-Letter?
	asciiKlein := func(r rune) bool {
		return r >= 'a' && r <= 'z'
	}
	fmt.Println(strings.ContainsFunc("HALLO", asciiKlein)) // false
	fmt.Println(strings.ContainsFunc("Hallo", asciiKlein)) // true

	// Kombination: Buchstabe ausserhalb ASCII (z. B. Umlaut)?
	nichtAsciiBuchstabe := func(r rune) bool {
		return unicode.IsLetter(r) && r > 127
	}
	fmt.Println(strings.ContainsFunc("Strasse", nichtAsciiBuchstabe)) // false
	fmt.Println(strings.ContainsFunc("Straße", nichtAsciiBuchstabe))  // true
	fmt.Println(strings.ContainsFunc("Café", nichtAsciiBuchstabe))    // true
}
Output
false
true
false
true
true

Closures lassen sich auch parametrisieren — etwa ein Generator func macheRangeCheck(lo, hi rune) func(rune) bool, der eine Familie von Prädikaten zurückgibt. Wichtig ist, dass das Prädikat seitenwirkungsfrei bleibt, sonst hängt das Ergebnis von der Iterationslänge ab und wird schwer testbar.

Die drei Funktionen teilen sich das gleiche Ziel — „enthält der String etwas Bestimmtes?" — unterscheiden sich aber in der Art, wie das „Bestimmte" beschrieben wird, und im Rückgabetyp. Die folgende Übersicht hilft bei der Wahl:

FunktionBeschreibung des TreffersRückgabeStoppt frühTypischer Einsatz
ContainsFuncPrädikat func(rune) boolbooljaUnicode-Kategorien, dynamische Bedingungen
ContainsAnyFester Zeichensatz (string)booljaWhitelist/Blacklist mit fester Zeichenmenge
IndexFuncPrädikat func(rune) boolintjaWie ContainsFunc, aber Position relevant

Faustregel: Geht es nur um „ja oder nein" und die Bedingung lässt sich als Prädikat ausdrücken, ist ContainsFunc die richtige Wahl. Bei einem statischen Zeichensatz aus wenigen Literalen ist ContainsAny knapper. Wird die Position des Treffers gebraucht, führt der Weg an IndexFunc (Rückgabe -1 bei Nicht-Treffer) nicht vorbei.

Eine klassische Anforderung an Passwörter ist, dass sie mindestens ein Sonderzeichen enthalten müssen. Mit ContainsFunc und einer Kombination aus unicode.IsPunct und unicode.IsSymbol lässt sich diese Regel in einer Zeile ausdrücken — und sie funktioniert für alle Unicode-Sonderzeichen, nicht nur eine willkürlich gewählte ASCII-Teilmenge.

Go password_validierung.go
package main

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

func hatSonderzeichen(s string) bool {
	return strings.ContainsFunc(s, func(r rune) bool {
		return unicode.IsPunct(r) || unicode.IsSymbol(r)
	})
}

func hatZiffer(s string) bool {
	return strings.ContainsFunc(s, unicode.IsDigit)
}

func valide(pw string) bool {
	return len(pw) >= 8 && hatSonderzeichen(pw) && hatZiffer(pw)
}

func main() {
	fmt.Println(valide("hallo"))         // false (zu kurz)
	fmt.Println(valide("hallowelt"))     // false (kein Sonder/Ziffer)
	fmt.Println(valide("hallowelt1"))    // false (kein Sonder)
	fmt.Println(valide("hallowelt1!"))   // true
	fmt.Println(valide("guten€tag123"))  // true (€ ist Symbol)
}
Output
false
false
false
true
true

Der entscheidende Vorteil gegenüber einer Lösung mit ContainsAny(pw, "!@#$%^&*...") ist die Vollständigkeit: Auch typografische Anführungszeichen, fremdsprachige Interpunktion oder das Euro-Zeichen werden erkannt, ohne dass die Sonderzeichen-Liste manuell gepflegt werden muss.

Für Sicherheitsprüfungen — etwa beim Übernehmen von User-Input in Logs, HTTP-Header oder Dateinamen — lohnt sich der Check auf Steuerzeichen. unicode.IsControl deckt alle C0- und C1-Controls ab, also Codepoints wie \x00, \x1b (ESC) oder Zeilenumbruch, die in vielen Kontexten gefährlich sind.

Go control_check.go
package main

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

var fehlerControl = errors.New("eingabe enthaelt steuerzeichen")

func saubereEingabe(s string) error {
	if strings.ContainsFunc(s, unicode.IsControl) {
		return fehlerControl
	}
	return nil
}

func main() {
	fmt.Println(saubereEingabe("Max Mustermann"))           // <nil>
	fmt.Println(saubereEingabe("Max\tMustermann"))          // Fehler (\t = Control)
	fmt.Println(saubereEingabe("Header\r\nInjected: x"))    // Fehler (CRLF)
	fmt.Println(saubereEingabe("Datei\x00name"))            // Fehler (NUL)
}
Output
<nil>
eingabe enthaelt steuerzeichen
eingabe enthaelt steuerzeichen
eingabe enthaelt steuerzeichen

Solche Guards sind klein, aber wirksam — speziell gegen Header-Injection per CRLF oder Path-Manipulation per Nullbyte. ContainsFunc mit unicode.IsControl bricht beim ersten Treffer ab, ist also auch bei langen Inputs günstig in der Laufzeit.

Go 1.21+ erforderlich

strings.ContainsFunc wurde mit Go 1.21 hinzugefügt — in älteren Versionen musste die Iteration manuell oder über strings.IndexFunc(s, f) >= 0 ausgedrückt werden.

Akzeptiert Closures

Das zweite Argument ist ein Funktionswert, also nimmt es sowohl benannte Funktionen wie unicode.IsSpace als auch Closures mit Capture des umgebenden Scopes entgegen.

Stoppt am ersten true

Die Iteration bricht ab, sobald das Prädikat einmal true zurückgibt — bei langen Strings mit frühem Treffer kostet das nur konstant viele Aufrufe.

Rune-weise Iteration

Intern läuft ein range-Loop über UTF-8-Codepoints, das Prädikat sieht echte Runes, keine Bytes — Multi-Byte-Sequenzen werden korrekt dekodiert.

Ideal mit unicode-Paket

Funktionen wie unicode.IsDigit, unicode.IsSpace, unicode.IsPunct, unicode.IsControl haben die passende Signatur und können direkt übergeben werden.

Prädikat darf komplex sein

Range-Checks, kombinierte Kategorien oder projektspezifische Whitelists lassen sich als Closure formulieren, ohne den umgebenden Code zu verkomplizieren.

Threadsafe wenn Closure es ist

ContainsFunc selbst ist nebenläufigkeitssicher — kritisch ist nur, ob das übergebene Prädikat seinen Capture-State threadsicher behandelt.

Für die Position: IndexFunc

Wird nicht nur „ob", sondern auch „wo" gebraucht, liefert strings.IndexFunc den Byte-Offset des ersten Treffers (oder -1).

Weiterführende Ressourcen

Externe Quellen

/ Weiter

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

Zur Übersicht