strings.TrimSpace schneidet von beiden Enden eines Strings alles weg, was nach Unicode-Definition als Whitespace zählt. Das Kriterium liefert unicode.IsSpace — also nicht nur das gewöhnliche ASCII-Space, sondern auch Tab, Newline (\n), Carriage Return (\r), Vertical Tab, Form Feed sowie die Unicode-Sonderlinge wie das Non-Breaking Space (U+00A0), das Em-Space (U+2003) oder das Ideographic Space (U+3000). In der Praxis ist TrimSpace die mit Abstand am häufigsten verwendete Trim-Variante: überall dort, wo Strings aus Dateien, Netzwerk-Streams, Formularen oder Konsolen-Input kommen, ist ein abschließendes TrimSpace der idiomatische erste Schritt zur Normalisierung.

Die Signatur ist denkbar schlank — kein Cutset, kein Prädikat, nur ein Eingabe-String. Genau diese Einfachheit macht TrimSpace zum Default für die meisten Trim-Situationen.

Go signatur.go
func TrimSpace(s string) string

Ohne weitere Parameter trifft TrimSpace selbst die Entscheidung darüber, was Whitespace ist. Diese Entscheidung delegiert es intern an unicode.IsSpace, das die offizielle Unicode-Klassifikation kennt und damit deutlich breiter aufgestellt ist als ein hartcodiertes ASCII-Set.

unicode.IsSpace deckt eine ganze Familie von Codepoints ab. Dazu gehören die klassischen Verdächtigen — Space (U+0020), Tab (U+0009), Newline (U+000A), CR (U+000D), Vertical Tab (U+000B), Form Feed (U+000C) — aber eben auch Unicode-spezifische Leerzeichen wie das Non-Breaking Space (U+00A0), das Ogham Space Mark (U+1680), die diversen Spaces aus dem General-Punctuation-Block (U+2000 bis U+200A), das Line Separator (U+2028), das Paragraph Separator (U+2029), das Narrow No-Break Space (U+202F), das Medium Mathematical Space (U+205F) und das Ideographic Space (U+3000).

Go whitespace_arten.go
package main

import (
    "fmt"
    "strings"
)

func main() {
    eingaben := []string{
        "  hallo  ",                 // ASCII-Spaces
        "\t\thallo\n",               // Tab + Newline
        " hallo ",         // NBSP beidseitig
        " hallo ",         // Em-Space + Ideographic Space
        "\r\n  hallo  \r\n",         // CRLF-Pakete
    }
    for _, s := range eingaben {
        fmt.Printf("%q -> %q\n", s, strings.TrimSpace(s))
    }
}
Output
"  hallo  " -> "hallo"
"\t\thallo\n" -> "hallo"
" hallo " -> "hallo"
" hallo " -> "hallo"
"\r\n  hallo  \r\n" -> "hallo"

In jedem dieser Fälle bleibt nur der bedeutungstragende Kern übrig. Genau diese Breite ist der Grund, warum man sich bei TrimSpace keine Gedanken über exotische Eingaben aus Copy-Paste-Quellen, Office-Dokumenten oder ostasiatischen Texten machen muss.

Ein häufiges Anti-Pattern ist der Versuch, sich mit einem manuellen Cutset wie strings.Trim(s, " \t\n\r") selbst eine TrimSpace-Variante zu bauen. Das funktioniert für ASCII, übersieht aber alle Unicode-Whitespace-Varianten — und das bedeutet, dass scheinbar harmlose Eingaben wie ein aus Word kopierter Text mit NBSP unerkannt durchrutschen.

Go nbsp_falle.go
package main

import (
    "fmt"
    "strings"
)

func main() {
    // Eingabe mit NBSP (U+00A0) statt regulaerem Space.
    eingabe := "  Beispiel  "

    manuell := strings.Trim(eingabe, " \t\n\r")
    richtig := strings.TrimSpace(eingabe)

    fmt.Printf("manuelles Cutset: %q (Laenge %d)\n", manuell, len(manuell))
    fmt.Printf("TrimSpace:        %q (Laenge %d)\n", richtig, len(richtig))
}
Output
manuelles Cutset: "  Beispiel  " (Laenge 12)
TrimSpace:        "Beispiel" (Laenge 8)

Das manuelle Cutset lässt die NBSPs unangetastet, weil sie nicht in der Liste stehen — der String sieht visuell unverändert aus, hat aber acht zusätzliche Bytes und vergleicht sich nicht gleich mit "Beispiel". TrimSpace erkennt das NBSP über unicode.IsSpace korrekt und liefert das, was man eigentlich wollte.

TrimSpace versucht zuerst zu prüfen, ob überhaupt etwas zu trimmen ist. Ist der String an beiden Enden bereits sauber, gibt die Funktion den ursprünglichen string-Header unverändert zurück — kein neuer Backing-Storage, keine Kopie, keine Allokation. Das macht ein defensiv eingestreutes TrimSpace auch in Hot-Paths billig, solange die Eingaben meistens bereits getrimmt sind.

Wenn tatsächlich getrimmt wird, liefert TrimSpace einen Sub-String, der auf denselben Backing-Storage zeigt (Offset und Länge angepasst) — es gibt also auch im Trim-Fall keine echte Datenkopie, sondern nur einen verschobenen String-Header.

Intern wählt TrimSpace einen ASCII-Fast-Path: solange die Bytes am Rand klar im ASCII-Bereich (< 0x80) liegen, wird nur byteweise gegen eine kleine Tabelle geprüft. Erst wenn ein Byte mit gesetztem High-Bit auftaucht, fällt die Funktion auf die volle Unicode-Dekodierung über utf8.DecodeRune und unicode.IsSpace zurück.

Für reinen ASCII-Input ist TrimSpace damit deutlich schneller als ein selbstgebautes TrimFunc(s, unicode.IsSpace), das auf jedem Codepoint die teurere Rune-Dekodierung durchziehen würde. Bei gemischten oder rein nicht-ASCII-Eingaben verschwindet dieser Vorteil naturgemäß, aber der Best-Case — und der ist in Server-Logs, HTTP-Headern und CSV-Dateien der Normalfall — bleibt rasend schnell.

Funktional ist strings.TrimSpace(s) exakt äquivalent zu strings.TrimFunc(s, unicode.IsSpace) — beide produzieren bei jedem denkbaren Input dasselbe Ergebnis. Der Unterschied liegt rein in der Implementierung: TrimSpace ist auf den häufigsten Fall hin optimiert und nimmt den ASCII-Fast-Path, während TrimFunc für jeden Codepoint den allgemeinen Prädikat-Aufruf durchspielt.

Go equivalenz.go
package main

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

func main() {
    s := "\t  Hallo Welt \n"

    a := strings.TrimSpace(s)
    b := strings.TrimFunc(s, unicode.IsSpace)

    fmt.Printf("TrimSpace: %q\n", a)
    fmt.Printf("TrimFunc:  %q\n", b)
    fmt.Println("identisch:", a == b)
}
Output
TrimSpace: "Hallo Welt"
TrimFunc:  "Hallo Welt"
identisch: true

Die Regel ist simpel: wer Whitespace meint, schreibt TrimSpace. TrimFunc mit unicode.IsSpace als Argument wirkt am Lesepunkt umständlich, ist messbar langsamer und bringt keinerlei semantischen Mehrwert.

Eingaben aus HTML-Formularen, REST-Bodies oder CLI-Argumenten sind notorisch unsauber: Nutzer fügen Leerzeichen vor oder nach dem eigentlichen Wert ein, mobile Tastaturen schießen Newlines am Ende rein, Copy-Paste schleppt NBSPs mit. Bevor solche Strings in Vergleiche, Datenbank-Lookups oder Validierungen gehen, ist TrimSpace der erste Schritt.

Go form_normalisieren.go
package main

import (
    "fmt"
    "strings"
)

type RegistrierungsForm struct {
    Email    string
    Vorname  string
    Nachname string
}

func normalisiere(f RegistrierungsForm) RegistrierungsForm {
    return RegistrierungsForm{
        Email:    strings.ToLower(strings.TrimSpace(f.Email)),
        Vorname:  strings.TrimSpace(f.Vorname),
        Nachname: strings.TrimSpace(f.Nachname),
    }
}

func main() {
    roh := RegistrierungsForm{
        Email:    "  Anna.Muster@Example.COM\n",
        Vorname:  " Anna ",
        Nachname: "  Muster  ",
    }
    sauber := normalisiere(roh)
    fmt.Printf("%+q\n", sauber)
}
Output
{Email:"anna.muster@example.com" Vorname:"Anna" Nachname:"Muster"}

Ohne diese Normalisierung würde anna.muster@example.com nicht zum bereits in der Datenbank gespeicherten Eintrag passen — das führende NBSP, der nachgezogene Newline und die Großschreibung sind drei unabhängige Quellen für falsch-negative Lookups, die TrimSpace plus ToLower in einem Aufwasch erledigen.

bufio.Scanner.Text() liefert die aktuelle Zeile bereits ohne den Zeilenumbruch — auf Unix-Systemen ist das Ergebnis sauber. Sobald die Eingabe aber von Windows kommt oder durch Tools mit CRLF-Endings gelaufen ist, bleibt am Ende ein \r hängen, das in stillen Bugs endet (Vergleiche schlagen fehl, geparste Zahlen sind plötzlich Strings mit unsichtbarem Anhängsel).

Go scanner_trim.go
package main

import (
    "bufio"
    "fmt"
    "strings"
)

func main() {
    // Simulierter CRLF-Input plus Leerzeilen und eingerueckte Zeilen.
    eingabe := "alpha\r\n  beta  \r\n\r\n\tgamma\r\n"
    scanner := bufio.NewScanner(strings.NewReader(eingabe))

    for scanner.Scan() {
        zeile := strings.TrimSpace(scanner.Text())
        if zeile == "" {
            continue // Leerzeilen ueberspringen
        }
        fmt.Printf("verarbeite: %q\n", zeile)
    }
}
Output
verarbeite: "alpha"
verarbeite: "beta"
verarbeite: "gamma"

Das Muster strings.TrimSpace(scanner.Text()) ist in produktivem Go-Code so verbreitet, dass es als idiomatisch gilt — es macht Scanner-Schleifen robust gegen Eingabekodierung, schluckt eingerückte Zeilen und ermöglicht den if zeile == ""-Check für Leerzeilen-Sprungmarken in einem einzigen Schritt.

unicode.IsSpace als Kriterium

TrimSpace delegiert die Whitespace-Erkennung an unicode.IsSpace und kennt damit die volle Unicode-Klassifikation, nicht nur ASCII.

Deckt mehr als ASCII ab

NBSP (U+00A0), Em-Space (U+2003) und Ideographic Space (U+3000) werden ebenfalls erkannt — manuelle Cutsets wie " \t\n\r" übersehen genau diese.

Beidseitig in einem Aufruf

Ein Aufruf trimmt links und rechts gleichzeitig; für nur eine Seite gibt es TrimLeft/TrimRight bzw. die Func-Varianten.

Meistgenutzte Trim-Variante

In produktivem Go-Code ist TrimSpace die dominante Trim-Form — Trim mit explizitem Cutset ist die Ausnahme, nicht die Regel.

No-op wenn nichts zu trimmen

War der Rand bereits sauber, wird derselbe String-Header zurückgegeben — keine Allokation, kein Kopieren.

ASCII-Fast-Path

Für reinen ASCII-Input nutzt die Implementierung einen byteweisen Fast-Path und ist messbar schneller als TrimFunc(s, unicode.IsSpace).

Idiomatisch nach scanner.Text()

Das Muster strings.TrimSpace(scanner.Text()) neutralisiert CRLF-Reste und macht Zeilen-Parsing robust gegen Eingabe-Kodierung.

Threadsafe

Strings sind in Go immutable, TrimSpace hat keinen internen Zustand — beliebig parallel aufrufbar ohne Synchronisation.

Weiterführende Ressourcen

Externe Quellen

/ Weiter

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

Zur Übersicht