strings.ContainsAny beantwortet eine einfache, aber häufig gestellte Frage: Steckt in einem String irgendeine Rune aus einer vorgegebenen Zeichenmenge? Die Funktion liefert ein nacktes bool, ohne Position und ohne Trefferanzahl — sie eignet sich also als reines Prädikat in if-Bedingungen und Validierungspfaden.

Das zweite Argument chars ist ausdrücklich ein Cutset, also eine Menge einzelner Unicode-Codepoints, und kein zu suchender Substring. Reihenfolge und Mehrfachnennungen darin spielen keine Rolle. Intern arbeitet ContainsAny rune-basiert, weshalb auch Multi-Byte-UTF-8-Zeichen wie Umlaute oder Emojis sauber erkannt werden.

Die Funktion ist eines der schlankesten Prädikate im strings-Paket. Sie nimmt zwei Strings entgegen und gibt einen Wahrheitswert zurück — kein Index, kein Fehler, keine Allokation.

Go signatur.go
func ContainsAny(s, chars string) bool

s ist der zu durchsuchende Text, chars die Zeichenmenge. Ein true bedeutet: mindestens eine Rune aus chars taucht irgendwo in s auf.

Der häufigste Stolperstein bei ContainsAny ist die Verwechslung mit Contains. Während Contains einen festen, zusammenhängenden Substring sucht, behandelt ContainsAny sein zweites Argument als Menge: jedes Zeichen darin ist ein eigener Suchkandidat. ContainsAny("Hallo", "xyz") ist false, ContainsAny("Hallo", "xya") ist true, sobald die Rune a in s vorkommt.

Genau deshalb sind auch Reihenfolge und Doppelungen im Cutset bedeutungslos. "abc", "cba" und "aabbcc" sind als Cutsets exakt äquivalent.

Go cutset.go
package main

import (
	"fmt"
	"strings"
)

func main() {
	s := "Hallo Welt"

	// Cutset: irgendeine dieser Runes?
	fmt.Println(strings.ContainsAny(s, "xyz")) // false
	fmt.Println(strings.ContainsAny(s, "xyW")) // true (W trifft)

	// Reihenfolge und Doppelungen egal
	fmt.Println(strings.ContainsAny(s, "Wxy"))  // true
	fmt.Println(strings.ContainsAny(s, "WWWy")) // true

	// Vergleich mit Contains: dort ist "Wxy" EIN Substring
	fmt.Println(strings.Contains(s, "Wxy")) // false
}
Output
false
true
true
true
false

Wer das Cutset-Konzept einmal verinnerlicht hat, vermeidet eine ganze Klasse von Bugs, in denen ein gemeinter Substring-Test heimlich zur Mengenprüfung wird.

Beide Randfälle leerer Strings liefern dieselbe Antwort, allerdings aus unterschiedlichen Gründen. Ein leeres Cutset enthält keine Runes, die getroffen werden könnten — das Ergebnis ist daher zwingend false. Ein leeres s enthält schlicht keine Zeichen, die einem Cutset angehören könnten, also ebenfalls false.

Go empty.go
package main

import (
	"fmt"
	"strings"
)

func main() {
	fmt.Println(strings.ContainsAny("Hallo", "")) // false, Cutset leer
	fmt.Println(strings.ContainsAny("", "abc"))   // false, s leer
	fmt.Println(strings.ContainsAny("", ""))      // false
}
Output
false
false
false

Diese Symmetrie ist praktisch, weil keine Sonderfallbehandlung im aufrufenden Code nötig ist — leere Eingaben fallen einfach durch.

ContainsAny iteriert intern über Runes, nicht über Bytes. Dadurch sind auch Multi-Byte-Codepoints wie deutsche Umlaute oder Emojis vollwertige Mitglieder des Cutsets. Ein ä belegt in UTF-8 zwei Bytes, wird aber als eine einzige Rune behandelt — eine byteweise Suche würde hier falsche Treffer produzieren.

Go rune.go
package main

import (
	"fmt"
	"strings"
)

func main() {
	s := "Schöne Grüße aus München 🍻"

	// Umlaute als Cutset-Mitglieder
	fmt.Println(strings.ContainsAny(s, "äöü")) // true (ö, ü treffen)
	fmt.Println(strings.ContainsAny(s, "áéí")) // false

	// Emojis als Runes im Cutset
	fmt.Println(strings.ContainsAny(s, "🍕🍻🍷")) // true (🍻 trifft)
	fmt.Println(strings.ContainsAny(s, "🍕🍷"))   // false
}
Output
true
false
true
false

Für die meisten internationalen Texte ist dieses Verhalten genau das, was man erwartet — anders als bei naiver Byte-Suche bleiben gemischte Zeichen aus verschiedenen Sprachen korrekt prüfbar.

Die drei Funktionen bilden ein zusammengehöriges Trio, unterscheiden sich aber klar in Rückgabewert und Semantik des zweiten Arguments. Wer den Unterschied im Kopf hat, greift in fast jedem Such-Szenario zum richtigen Werkzeug.

FunktionRückgabeZweites ArgumentFrage
ContainsAnyboolCutset (Runes-Menge)Kommt irgendeine Rune vor?
Containsboolfester SubstringKommt der genaue Substring vor?
IndexAnyint (-1 = nein)Cutset (Runes-Menge)Wo kommt eine Rune zuerst vor?

Faustregel: Contains für ganze Substrings, ContainsAny für reine Ja/Nein-Fragen über eine Zeichenmenge, IndexAny wenn die Trefferposition gebraucht wird.

Ein klassisches Einsatzfeld ist die Validierung von Benutzereingaben. Bevor ein Username in eine URL eingebaut oder in HTML eingebettet wird, lohnt sich eine schnelle Vorprüfung auf strukturzerstörende Zeichen wie <, >, ", ' oder &. ContainsAny reduziert diese Prüfung auf eine einzige, gut lesbare Zeile.

Go validation.go
package main

import (
	"fmt"
	"strings"
)

const forbidden = `<>"'&`

func isValidUsername(name string) bool {
	if name == "" {
		return false
	}
	return !strings.ContainsAny(name, forbidden)
}

func main() {
	candidates := []string{
		"michael",
		"max_mustermann",
		`<script>`,
		"alice&bob",
		"O'Reilly",
	}

	for _, c := range candidates {
		fmt.Printf("%-20s -> %v\n", c, isValidUsername(c))
	}
}
Output
michael              -> true
max_mustermann       -> true
<script>             -> false
alice&bob            -> false
O'Reilly             -> false

Wichtig: ContainsAny ersetzt keine vollständige Eingabevalidierung oder ein sauberes Escaping — es ist ein billiger Vorfilter, der offensichtlich riskante Eingaben früh ausschließt.

Beim Einlesen unbekannter Tabellendateien hilft eine schnelle Trennzeichen-Heuristik, bevor ein vollwertiger Parser gewählt wird. Die erste nicht-leere Zeile reicht oft, um zwischen Komma-, Semikolon- und Tab-getrennten Formaten zu unterscheiden.

Go csv_sniff.go
package main

import (
	"fmt"
	"strings"
)

func sniffSeparator(line string) string {
	switch {
	case strings.ContainsAny(line, "\t"):
		return "tab"
	case strings.ContainsAny(line, ";"):
		return "semicolon"
	case strings.ContainsAny(line, ","):
		return "comma"
	default:
		return "unknown"
	}
}

func main() {
	lines := []string{
		"id,name,email",
		"id;name;email",
		"id\tname\temail",
		"einzelspalte",
	}

	for _, l := range lines {
		fmt.Printf("%-25q -> %s\n", l, sniffSeparator(l))
	}
}
Output
"id,name,email"           -> comma
"id;name;email"           -> semicolon
"id\tname\temail"         -> tab
"einzelspalte"            -> unknown

Für robustere Erkennung würde man mehrere Zeilen sampeln und die Trennzeichen zählen — aber als erste Weichenstellung in einer Importpipeline ist ContainsAny ausreichend und sehr schnell.

chars ist Cutset, kein Substring

Das zweite Argument wird Rune für Rune als Menge gelesen. ContainsAny(s, "abc") fragt nicht nach dem Substring abc, sondern nach mindestens einem der Zeichen a, b oder c.

Rune-aware: Multi-Byte sicher

Iteration läuft über Codepoints, nicht über Bytes. Umlaute wie ä oder Emojis wie 🍻 funktionieren als Cutset-Mitglieder korrekt.

Leeres Cutset liefert immer false

ContainsAny(s, "") ist stets false, weil eine leere Menge keine Treffer enthalten kann — keine Sonderbehandlung nötig.

Ersetzt Ketten aus mehreren Contains

Statt Contains(s, "a") || Contains(s, "b") || Contains(s, "c") reicht ein einziger Aufruf ContainsAny(s, "abc") — kürzer, schneller, lesbarer.

Schneller als Regex für reine Zeichensets

Wenn nur geprüft wird, ob irgendein Zeichen aus einer festen Menge vorkommt, ist ContainsAny einer Regex-Klasse wie [abc] klar überlegen — kein Compile-Schritt, keine NFA-Maschinerie.

Threadsafe — reine Lesefunktion

Die Funktion mutiert keinen geteilten Zustand und kann gefahrlos aus mehreren Goroutinen heraus auf denselben Strings aufgerufen werden.

Case-sensitiv

A und a sind verschiedene Runes. Für case-insensitive Prüfung vorher strings.ToLower auf s und chars anwenden oder ContainsFunc mit eigener Vergleichslogik nutzen.

Für die Position: IndexAny

ContainsAny verwirft den Offset des Treffers. Wer wissen will, wo das erste Set-Zeichen steht, greift zu IndexAny, das -1 oder den Byte-Offset zurückgibt.

Weiterführende Ressourcen

Externe Quellen

/ Weiter

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

Zur Übersicht