strconv.Atoi ist der kürzeste Weg, um aus einem dezimalen String ein int zu machen. Der Name stammt aus der C-Standardbibliothek — ASCII to integer — und Go hat ihn aus Gewohnheit übernommen, obwohl er in einer ansonsten gesprächigen API auffällt. Funktional ist Atoi(s) exakt äquivalent zu ParseInt(s, 10, 0) mit anschließendem Cast — also Basis 10, Zielgröße int (plattformnative 32 oder 64 Bit), strukturierter NumError bei Misserfolg.

Die Funktion ist absichtlich knauserig: kein führender Whitespace, kein +-Präfix außer als Vorzeichen direkt vor der ersten Ziffer, kein Tausender-Komma, keine wissenschaftliche Notation. Wer Toleranz braucht, trimmt vorher mit strings.TrimSpace oder wickelt den Aufruf in einen eigenen Wrapper. Diese Strenge ist gewollt — sie verhindert stille Bugs bei Konfigurations- und CLI-Eingaben.

Die Signatur ist eine der kürzesten im Paket: ein String rein, (int, error) raus. Kein bitSize, kein base — beides ist fest verdrahtet.

Go signatur.go
func Atoi(s string) (int, error)

Der Rückgabetyp ist int, nicht int64 — das unterscheidet Atoi von ParseInt. Auf 64-Bit-Plattformen sind beide gleich groß, auf 32-Bit-Plattformen schon nicht mehr. Wer ein definitives int64 braucht, nutzt ParseInt(s, 10, 64).

Atoi akzeptiert die kanonische Form -?[0-9]+ und liefert bei Erfolg den geparsten Wert. Bei jeder Abweichung — auch nur einem Leerzeichen — gibt es einen *NumError.

Go grundverhalten.go
package main

import (
	"fmt"
	"strconv"
)

func main() {
	cases := []string{"42", "-17", "+5", "0", "3.14", " 7", "abc", ""}
	for _, s := range cases {
		n, err := strconv.Atoi(s)
		fmt.Printf("%-6q -> %d  err=%v\n", s, n, err)
	}
}
Output
"42"   -> 42  err=<nil>
"-17"  -> -17  err=<nil>
"+5"   -> 5  err=<nil>
"0"    -> 0  err=<nil>
"3.14" -> 0  err=strconv.Atoi: parsing "3.14": invalid syntax
" 7"   -> 0  err=strconv.Atoi: parsing " 7": invalid syntax
"abc"  -> 0  err=strconv.Atoi: parsing "abc": invalid syntax
""     -> 0  err=strconv.Atoi: parsing "": invalid syntax

Beachte: +5 ist gültig (das + wird als explizites Vorzeichen erkannt), aber 3.14 und " 7" nicht — Atoi macht keine Float-Konvertierung und akzeptiert keinen Whitespace. Bei Fehler ist der erste Rückgabewert immer 0, niemals ein partiell geparster Wert.

Intern ruft Atoi für lange Strings ParseInt(s, 10, 0) auf und castet das Ergebnis auf int. Für kurze ASCII-Strings (Länge ≤ IntSize/3) gibt es einen optimierten Fast-Path ohne Subroutine — relevant, weil Atoi häufig in Hot-Paths landet.

Go aequivalent.go
package main

import (
	"fmt"
	"strconv"
)

func main() {
	s := "12345"

	a, errA := strconv.Atoi(s)
	b, errB := strconv.ParseInt(s, 10, 0)

	fmt.Printf("Atoi:     %d (err=%v)\n", a, errA)
	fmt.Printf("ParseInt: %d (err=%v)\n", b, errB)
	fmt.Println("identisch:", int64(a) == b)
}
Output
Atoi:     12345 (err=<nil>)
ParseInt: 12345 (err=<nil>)
identisch: true

Faustregel: solange Basis 10 und plattformnatives int reichen, ist Atoi die idiomatische Wahl. Sobald Basis-Auswahl, fester Zieltyp (int8, int32) oder explizite Range-Kontrolle gebraucht wird, ist ParseInt korrekt.

Atoi prüft die Range gegen den nativen int. Auf 64-Bit-Systemen reicht das von -9_223_372_036_854_775_808 bis 9_223_372_036_854_775_807. Werte darüber hinaus erzeugen ErrRange — formal okay, aber zu groß für den Zieltyp.

Go range_fehler.go
package main

import (
	"errors"
	"fmt"
	"strconv"
)

func main() {
	cases := []string{
		"9223372036854775807",  // MaxInt64
		"9223372036854775808",  // MaxInt64 + 1 -> Range
		"-9223372036854775808", // MinInt64
		"-9223372036854775809", // MinInt64 - 1 -> Range
	}
	for _, s := range cases {
		n, err := strconv.Atoi(s)
		switch {
		case err == nil:
			fmt.Printf("%-25s -> %d\n", s, n)
		case errors.Is(err, strconv.ErrRange):
			fmt.Printf("%-25s -> Range-Fehler\n", s)
		default:
			fmt.Printf("%-25s -> %v\n", s, err)
		}
	}
}
Output
9223372036854775807       -> 9223372036854775807
9223372036854775808       -> Range-Fehler
-9223372036854775808      -> -9223372036854775808
-9223372036854775809      -> Range-Fehler

Auf 32-Bit-Systemen liegt die Grenze entsprechend niedriger (±2_147_483_647). Wer plattformunabhängig denken muss, nutzt ParseInt(s, 10, 64) und prüft selbst gegen math.MaxInt32 o. Ä.

Der Fehler-Rückgabewert ist ein *strconv.NumError mit drei Feldern: Func, Num, Err. Über errors.Is lassen sich ErrSyntax und ErrRange trennen — die typische Frage in einem CLI- oder Config-Parser.

Go numerror.go
package main

import (
	"errors"
	"fmt"
	"strconv"
)

func diagnose(s string) string {
	n, err := strconv.Atoi(s)
	switch {
	case err == nil:
		return fmt.Sprintf("ok: %d", n)
	case errors.Is(err, strconv.ErrSyntax):
		return fmt.Sprintf("%q ist keine ganze Zahl", s)
	case errors.Is(err, strconv.ErrRange):
		return fmt.Sprintf("%q ist zu groß/klein für int", s)
	default:
		return err.Error()
	}
}

func main() {
	for _, s := range []string{"100", "abc", "999999999999999999999"} {
		fmt.Println(diagnose(s))
	}
}
Output
ok: 100
"abc" ist keine ganze Zahl
"999999999999999999999" ist zu groß/klein für int

Diese Trennung ist nicht akademisch: bei Syntax-Fehlern ist die Eingabe selbst falsch (Tippfehler, falscher Datentyp), bei Range-Fehlern war die Eingabe in Ordnung — sie passt nur nicht in den Zieltyp. Beide Fälle verdienen unterschiedliche User-Meldungen.

Ein klassischer Anwendungsfall: ein CLI-Tool nimmt eine optionale Zahl als Argument. Leerer String soll den Default-Wert nutzen, ungültige Eingabe soll mit klarer Fehlermeldung abbrechen.

Go cli_default.go
package main

import (
	"errors"
	"fmt"
	"strconv"
)

func parsePort(s string, def int) (int, error) {
	if s == "" {
		return def, nil
	}
	n, err := strconv.Atoi(s)
	if err != nil {
		if errors.Is(err, strconv.ErrSyntax) {
			return 0, fmt.Errorf("Port %q ist keine Zahl", s)
		}
		return 0, fmt.Errorf("Port %q ungültig: %w", s, err)
	}
	if n < 1 || n > 65535 {
		return 0, fmt.Errorf("Port %d außerhalb 1-65535", n)
	}
	return n, nil
}

func main() {
	cases := []string{"", "8080", "abc", "70000"}
	for _, s := range cases {
		n, err := parsePort(s, 3000)
		if err != nil {
			fmt.Printf("%-7q -> %v\n", s, err)
		} else {
			fmt.Printf("%-7q -> Port %d\n", s, n)
		}
	}
}
Output
""      -> Port 3000
"8080"  -> Port 8080
"abc"   -> Port "abc" ist keine Zahl
"70000" -> Port 70000 außerhalb 1-65535

Die Range-Prüfung gegen 1-65535 ist hier eine semantische Schicht ÜBER AtoiAtoi selbst akzeptiert 70000 problemlos, weil es in int passt. Range-Logik gehört in die Anwendung, nicht in die Konvertierung.

In freien Textquellen — Benutzer-Eingaben, Excel-Exports, gescannte Dokumente — gibt es oft Leerzeichen vor oder nach der Zahl. Atoi lehnt das ab; die idiomatische Lösung ist ein Trim-Schritt davor.

Go trim_atoi.go
package main

import (
	"fmt"
	"strconv"
	"strings"
)

func atoiTrim(s string) (int, error) {
	return strconv.Atoi(strings.TrimSpace(s))
}

func main() {
	cases := []string{"  42  ", "\t-17\n", "42 ", " ", "12 34"}
	for _, s := range cases {
		n, err := atoiTrim(s)
		fmt.Printf("%-10q -> n=%d err=%v\n", s, n, err)
	}
}
Output
"  42  "   -> n=42 err=<nil>
"\t-17\n"  -> n=-17 err=<nil>
"42 "      -> n=42 err=<nil>
" "        -> n=0 err=strconv.Atoi: parsing "": invalid syntax
"12 34"    -> n=0 err=strconv.Atoi: parsing "12 34": invalid syntax

Wichtig: TrimSpace trimmt nur außen. Whitespace innen ("12 34") bleibt ein Syntaxfehler — was richtig ist, denn die Eingabe ist semantisch ambig (zwei Zahlen? Tausender-Trenner? Tippfehler?).

Interessantes

Atoi = ParseInt(s, 10, 0)

Bequemlichkeits-Hülle für den häufigsten Fall: Basis 10, plattformnatives int, ein Aufruf. Funktional identisch, semantisch klarer.

Kein Whitespace, kein Komma

Atoi akzeptiert nur die kanonische Form -?\+?[0-9]+. Trimmen muss der Aufrufer; Tausender-Trenner und wissenschaftliche Notation sind nicht erlaubt.

Rückgabetyp int, nicht int64

Auf 32-Bit-Plattformen reicht der Wertebereich nur bis ±2.1 Mrd. Für plattformunabhängiges Parsen bei großen Zahlen ParseInt(s, 10, 64) nutzen.

ErrSyntax vs. ErrRange trennen

errors.Is(err, strconv.ErrSyntax) und errors.Is(err, strconv.ErrRange) unterscheiden „keine Zahl" von „zu groß" — die User-Meldung sollte das auch tun.

Bei Fehler ist n = 0

Der erste Rückgabewert ist im Fehlerfall immer 0, nie ein partieller Wert. Wer den Fehler ignoriert, bekommt stille 0-Werte — eine klassische Bug-Quelle.

Fast-Path für kurze Strings

Für ASCII-Eingaben mit wenigen Ziffern hat Atoi einen Inline-Pfad ohne Subroutine — sehr günstig im Hot-Path.

Plus-Vorzeichen erlaubt

+5 ist gültig, ebenso -5. Genau ein Vorzeichen-Zeichen vor den Ziffern, keine doppelten.

Default-Werte über leeren String

Üblicher Pattern: leerer String = „kein Wert" → Default. Funktioniert gut mit os.Getenv, das bei fehlender Variable ebenfalls "" liefert.

Weiterführende Ressourcen

Externe Quellen

/ Weiter

Zurück zu Das strconv-Paket — String-Zahl-Konvertierung

Zur Übersicht