strings.ContainsRune beantwortet die Frage, ob ein einzelner Unicode-Codepoint in einem String vorkommt. Die Funktion ist semantisch identisch zu IndexRune(s, r) >= 0, vermeidet aber den Umweg über eine temporäre String-Konvertierung, wie sie bei Contains(s, string(r)) nötig wäre.

Weil Go-Strings als UTF-8 kodiert sind und eine rune einem 32-Bit-Codepoint entspricht, arbeitet ContainsRune automatisch korrekt mit Multi-Byte-Zeichen wie Umlauten oder Emojis. Wer einen einzelnen Codepoint sucht, sollte deshalb ContainsRune statt Contains nutzen — kürzer, schneller und semantisch präziser.

Die Signatur ist minimal und nimmt genau einen String und einen Codepoint entgegen. Der Rückgabewert ist ein simples bool, das den Treffer signalisiert, ohne Position oder Häufigkeit preiszugeben.

Go signatur.go
func ContainsRune(s string, r rune) bool

Intern delegiert die Implementierung an IndexRune und vergleicht das Ergebnis mit 0. Für die meisten Aufrufer ist das ein irrelevantes Detail — relevant ist, dass die Suche linear über die UTF-8-Bytes läuft und keine Allokationen verursacht.

Der Typ rune ist in Go ein Alias für int32 und repräsentiert einen einzelnen Unicode-Codepoint. Anders als ein byte (uint8), das nur Werte von 0 bis 255 abdeckt, fasst eine rune den vollen Unicode-Bereich bis U+10FFFF und damit auch Zeichen außerhalb von ASCII.

Rune-Literale werden in Go mit einfachen Anführungszeichen geschrieben — 'A' ergibt den numerischen Wert 65, 'ä' entspricht 228, und Emojis wie das Raketen-Symbol haben einen Codepoint weit jenseits des Basic Multilingual Plane.

Go rune_literale.go
package main

import "fmt"

func main() {
    var a rune = 'A'
    var u rune = 'ä'
    var e rune = '🚀'

    fmt.Printf("%c = %d (U+%04X)\n", a, a, a)
    fmt.Printf("%c = %d (U+%04X)\n", u, u, u)
    fmt.Printf("%c = %d (U+%04X)\n", e, e, e)
}
Output
A = 65 (U+0041)
ä = 228 (U+00E4)
🚀 = 128640 (U+1F680)

Wichtig ist die Unterscheidung zu byte: 'A' passt in ein einzelnes UTF-8-Byte, 'ä' belegt zwei Bytes, '🚀' sogar vier. ContainsRune nimmt diese Komplexität ab und sucht direkt auf Codepoint-Ebene.

Der entscheidende Vorteil gegenüber byte-basierten Vergleichen zeigt sich bei Multi-Byte-Zeichen. ContainsRune dekodiert den Zielstring UTF-8-sauber und matcht den Codepoint als Ganzes — es kommt nicht zu Fehltreffern mitten in einer Byte-Sequenz.

Go utf8_suche.go
package main

import (
    "fmt"
    "strings"
)

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

    fmt.Println(strings.ContainsRune(s, 'ö'))  // true
    fmt.Println(strings.ContainsRune(s, 'ü'))  // true
    fmt.Println(strings.ContainsRune(s, 'ß'))  // true
    fmt.Println(strings.ContainsRune(s, '🚀')) // true
    fmt.Println(strings.ContainsRune(s, 'x'))  // false
}
Output
true
true
true
true
false

Selbst der Vier-Byte-Codepoint des Raketen-Emojis wird zuverlässig gefunden. Wer stattdessen mit bytes.IndexByte oder einer manuellen Byte-Schleife arbeiten würde, müsste die UTF-8-Dekodierung selbst implementieren.

Funktional liefern beide Varianten dasselbe Ergebnis, denn string(r) erzeugt aus einem Codepoint die zugehörige UTF-8-Byte-Sequenz, die Contains dann als Substring sucht. Der Unterschied liegt in Allokation und Lesbarkeit.

Go vergleich.go
package main

import (
    "fmt"
    "strings"
)

func main() {
    s := "Café"
    r := 'é'

    // Idiomatisch — keine String-Allokation
    a := strings.ContainsRune(s, r)

    // Funktioniert auch, allokiert aber einen temporären String
    b := strings.Contains(s, string(r))

    fmt.Println(a, b)
}
Output
true true

string(r) ist eine Heap- oder zumindest Stack-Allokation für einen 1- bis 4-Byte-String, der nur konstruiert wird, um sofort wieder verworfen zu werden. ContainsRune spart diesen Umweg und drückt die Absicht „suche genau diesen Codepoint" zudem klarer aus.

Übergibt man utf8.RuneError (Codepoint 0xFFFD, das Unicode-Ersatzzeichen) als gesuchte Rune, sucht ContainsRune explizit nach diesem Codepoint im String — nicht nach „irgendeiner ungültigen Sequenz". Ungültige UTF-8-Bytes im Eingabestring werden beim Dekodieren als RuneError interpretiert, was den Match folglich auslösen kann.

Go edge_cases.go
package main

import (
    "fmt"
    "strings"
    "unicode/utf8"
)

func main() {
    // Gültiger String, kein Ersatzzeichen
    ok := "hallo"
    fmt.Println(strings.ContainsRune(ok, utf8.RuneError)) // false

    // String mit kaputter UTF-8-Sequenz (\xff ist kein gültiger Start)
    broken := "ab\xffcd"
    fmt.Println(strings.ContainsRune(broken, utf8.RuneError)) // true

    // Surrogate-Hälften (U+D800..U+DFFF) sind in UTF-8 nicht erlaubt
    fmt.Println(strings.ContainsRune("abc", 0xD800)) // false
}
Output
false
true
false

Die letzte Zeile zeigt eine subtile Eigenheit: Surrogate-Codepoints existieren in gültigem UTF-8 nicht, weshalb die Suche garantiert leer ausgeht. Wer Eingaben aus externen Quellen prüft, sollte den Edge-Case RuneError bewusst einplanen.

In Parsern und Loggern muss man häufig entscheiden, ob eine Zeile ein bestimmtes Trennzeichen enthält — etwa einen Tabulator, der TSV-Daten von Plain-Text trennt. ContainsRune erledigt das in einer Zeile und ohne Allokation.

Go trenner.go
package main

import (
    "fmt"
    "strings"
)

func istTSV(zeile string) bool {
    return strings.ContainsRune(zeile, '\t')
}

func main() {
    zeilen := []string{
        "name\talter\tort",
        "nur reiner text",
        "id,name,email",
        "key\tvalue",
    }

    for _, z := range zeilen {
        fmt.Printf("%-25q -> TSV? %v\n", z, istTSV(z))
    }
}
Output
"name\talter\tort"        -> TSV? true
"nur reiner text"         -> TSV? false
"id,name,email"           -> TSV? false
"key\tvalue"              -> TSV? true

Diese Variante ist klarer als ein strings.Contains(zeile, "\t") und kommuniziert: „Ich suche genau ein Steuerzeichen, keinen mehrstelligen Substring." Bei großen Log-Streams summiert sich auch der Allokations-Vorteil messbar.

In sozialen Plattformen werden Emojis im Usernamen oft als Tag- oder Status-Marker genutzt — ein grüner Haken für verifizierte Accounts, ein Stern für Premium. ContainsRune prüft den Codepoint direkt, ohne dass man sich um die Vier-Byte-UTF-8-Sequenz kümmern müsste.

Go emoji_filter.go
package main

import (
    "fmt"
    "strings"
)

const (
    verifiziert = ''
    premium     = ''
)

type User struct {
    Name string
}

func (u User) IstVerifiziert() bool {
    return strings.ContainsRune(u.Name, verifiziert)
}

func (u User) IstPremium() bool {
    return strings.ContainsRune(u.Name, premium)
}

func main() {
    users := []User{
        {"alice ✅"},
        {"bob ⭐"},
        {"carol ✅⭐"},
        {"dave"},
    }

    for _, u := range users {
        fmt.Printf("%-12s verifiziert=%v premium=%v\n",
            u.Name, u.IstVerifiziert(), u.IstPremium())
    }
}
Output
alice ✅     verifiziert=true premium=false
bob ⭐       verifiziert=false premium=true
carol ✅⭐    verifiziert=true premium=true
dave         verifiziert=false premium=false

Der Zugriff auf einzelne Codepoints bleibt selbst dann sauber, wenn der Username mehrere Multi-Byte-Marker kombiniert. Für komplexere Klassifizierungen — etwa „enthält irgendeines aus einer Menge von Tags" — wäre ContainsAny der nächste Schritt.

rune = int32

Der Typ rune ist ein Alias für int32 und repräsentiert genau einen Unicode-Codepoint bis U+10FFFF.

IndexRune-Wrapper

Intern ist ContainsRune lediglich IndexRune(s, r) >= 0 — das Verhalten ist exakt das gleiche, nur der Rückgabetyp wechselt zu bool.

UTF-8-sicher

Multi-Byte-Codepoints wie 'ä', 'ß' oder '🚀' werden korrekt gematcht, weil die Suche auf Codepoint-Ebene und nicht Byte-Ebene arbeitet.

Keine String-Allokation

ContainsRune ist schlanker als Contains(s, string(r)), weil die Konvertierung der Rune in einen temporären String entfällt.

Rune-Literale mit Single-Quote

Rune-Literale verwenden einfache Anführungszeichen — 'a' ergibt einen rune-Wert, "a" einen string.

Mehrere Runes? ContainsAny

Soll geprüft werden, ob irgendeine aus einer Menge von Runen vorkommt, ist ContainsAny(s, chars) der passende Helfer.

Position? IndexRune

Wer die Byte-Position des Treffers braucht, greift direkt zu IndexRune — ContainsRune verwirft diese Information.

Threadsafe

Wie alle Funktionen im strings-Paket arbeitet ContainsRune ausschließlich lesend auf unveränderlichen Strings und ist damit ohne weitere Synchronisation nebenläufig nutzbar.

Weiterführende Ressourcen

Externe Quellen

/ Weiter

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

Zur Übersicht