fmt.Sprintln ist der String-Zwilling von fmt.Println: gleiche Formatierungsregeln, gleiche Operanden-Behandlung — nur landet das Ergebnis nicht auf os.Stdout, sondern als string in deinem Programm. Damit hast du eine Println-Zeile in der Hand, bevor sie irgendwo geschrieben wird.

Zwei Garantien prägen die Funktion: Zwischen je zwei Operanden steht immer ein Leerzeichen, am Ende steht immer ein \n. Das macht Sprintln zur ersten Wahl, wenn du zeilenweise Strings für Buffer, Test-Erwartungen oder Netzwerk-Protokolle brauchst.

Die Funktions-Signatur

func Sprintln(a ...any) string — nimmt beliebig viele Operanden entgegen und liefert genau einen String mit Trailing-Newline zurück. Es gibt keinen Format-String und keine Fehler-Rückgabe — die Funktion kann nicht scheitern, weil sie nur in den Heap schreibt. Das variadische a ...any akzeptiert eine beliebige Mischung aus Typen; intern wendet Sprintln auf jeden Operanden das %v-Verb an und prüft optional auf das fmt.Stringer-Interface.

Spaces, Newline, Reihenfolge

Sprintln ist deterministisch: Die Operanden werden in der angegebenen Reihenfolge ausgewertet, mit Leerzeichen verbunden und mit \n abgeschlossen. Anders als bei fmt.Sprint gilt die Space-Regel immer — egal ob beide Nachbarn Strings sind oder nicht.

Go verhalten.go
package main

import "fmt"

func main() {
	s1 := fmt.Sprintln("Server", "started", "on", "port", 8080)
	s2 := fmt.Sprintln("a", "b", "c")
	s3 := fmt.Sprintln(1, 2, 3)
	s4 := fmt.Sprintln()

	fmt.Printf("%q\n", s1)
	fmt.Printf("%q\n", s2)
	fmt.Printf("%q\n", s3)
	fmt.Printf("%q\n", s4)
}
Output
"Server started on port 8080\n"
"a b c\n"
"1 2 3\n"
"\n"

Beachte den letzten Fall: Selbst ohne Operanden liefert Sprintln einen nicht-leeren String — nämlich nur "\n". Wer also „leere Strings" als Marker erwartet, wird hier überrascht. Wenn du einen wirklich leeren String brauchst, ist fmt.Sprint() die richtige Wahl.

Sprint vs. Sprintln vs. Sprintf

FunktionEingabeAusgabe (mit %q)SpacesTrailing-\n
fmt.Sprint("a", "b", 3)drei Operanden"ab 3"nur zwischen Nicht-Stringsnein
fmt.Sprintln("a", "b", 3)drei Operanden"a b 3\n"immerja
fmt.Sprintf("%s-%s-%d", "a", "b", 3)Format + Args"a-b-3"gemäß Formatnein

Faustregel: Sprintf braucht eine Schablone, Sprint und Sprintln brauchen keine — und unter den beiden Letzten entscheidet die Frage „brauche ich eine Zeile?" über die Wahl.

Wo Sprintln glänzt

Überall, wo zeilenbasierte Strings entstehen und du sie nicht sofort schreibst, ist Sprintln passend: Buffer und Builder mit Zeilen füllen, ohne manuell \n anzuhängen; Log-Zeilen bauen, bevor sie an mehrere Sinks (File, Network, Stdout) verteilt werden; zeilenbasierte Netzwerk-Protokolle wie SMTP, IMAP, HTTP/1.x; Test-Erwartungen, wenn der Code-Under-Test Println verwendet und im Test exakt derselbe String erwartet wird.

Go protokoll.go
package main

import "fmt"

func main() {
	helo := fmt.Sprintln("HELO", "mail.example.com")
	mailFrom := fmt.Sprintln("MAIL", "FROM:<alice@example.com>")
	fmt.Printf("%q\n", helo)
	fmt.Printf("%q\n", mailFrom)
}
Output
"HELO mail.example.com\n"
"MAIL FROM:<alice@example.com>\n"

Für echtes SMTP würdest du \r\n statt \n brauchen — dafür ist Sprintln dann nicht das richtige Werkzeug. Aber für Protokolle mit reinem \n-Trenner spart Sprintln eine Fehlerquelle: das vergessene Newline am Zeilenende.

Allokation und Reflection

Sprintln ist nicht für Hot-Paths gemacht. Drei Kostenfaktoren fallen ins Gewicht: Reflection für jeden Operanden über reflect; Heap-Allokation für den Rückgabe-String (immer neu); und Interface-Boxing für primitive Werte wie int, die in ein any-Interface verpackt werden. In einem Hot-Loop ist strconv plus strings.Builder deutlich schneller. Sprintln ist die richtige Wahl, wenn Klarheit und Vollständigkeit (Spaces, Newline) wichtiger sind als jede Mikrosekunde.

Die \n ist immer dabei

Die garantierte Trailing-Newline ist der entscheidende Unterschied zu Sprint und Sprintf. Das macht Sprintln großartig für zeilenbasierte Senken — und gefährlich, wenn du den String später nochmal einrahmst.

Go doppel_newline.go
package main

import "fmt"

func main() {
	line := fmt.Sprintln("Antwort:", 42)

	fmt.Printf("%s\n", line)
	fmt.Print(line)
}
Output
Antwort: 42

Antwort: 42

Im ersten Fall steht eine Leerzeile zwischen den beiden Ausgaben, weil %s\n an die bereits vorhandene Sprintln-Newline noch eine zweite hängt. Merksatz: Wer Sprintln verwendet, sollte den Wert mit fmt.Print oder direkt mit io.WriteString ausgeben — nicht mit Println oder %s\n.

Buffer-Aufbau mit garantierten Zeilen

Ein häufiges Muster: Mehrere Datenpunkte werden zu einem mehrzeiligen String zusammengesetzt — etwa für einen Diagnose-Bericht, der später als Ganzes geschrieben wird. Sprintln nimmt dir die Newline-Disziplin ab.

Go bericht.go
package main

import (
	"fmt"
	"strings"
)

type Sensor struct {
	Name        string
	Temperatur  float64
	Luftdruck   int
}

func bericht(sensoren []Sensor) string {
	var b strings.Builder
	b.WriteString(fmt.Sprintln("=== Sensor-Bericht ==="))
	for _, s := range sensoren {
		b.WriteString(fmt.Sprintln(s.Name, s.Temperatur, "°C", s.Luftdruck, "hPa"))
	}
	b.WriteString(fmt.Sprintln("=== Ende ==="))
	return b.String()
}

func main() {
	daten := []Sensor{
		{"Halle-Nord", 21.4, 1013},
		{"Halle-Süd", 22.1, 1012},
	}
	fmt.Print(bericht(daten))
}
Output
=== Sensor-Bericht ===
Halle-Nord 21.4 °C 1013 hPa
Halle-Süd 22.1 °C 1012 hPa
=== Ende ===

Kein einziges \n im Code — und trotzdem stimmen alle Zeilenumbrüche. Die Ausgabe mit fmt.Print (nicht Println) am Ende verhindert die Doppel-Newline.

Mock-Email-Body für Tests

In Tests von HTTP-Clients oder Parsern brauchst du oft einen kontrollierten Response-Body mit präzisen Zeilen. Sprintln baut diese Bodies lesbar auf — und die Newlines sind exakt dort, wo der echte Server sie setzen würde.

Go mock_email.go
package mailparser_test

import (
	"fmt"
	"strings"
	"testing"
)

func mockEmailBody() string {
	var b strings.Builder
	b.WriteString(fmt.Sprintln("From:", "alice@example.com"))
	b.WriteString(fmt.Sprintln("To:", "bob@example.com"))
	b.WriteString(fmt.Sprintln("Subject:", "Statusmeldung"))
	b.WriteString(fmt.Sprintln())
	b.WriteString(fmt.Sprintln("Hallo Bob,"))
	b.WriteString(fmt.Sprintln("der Build ist grün."))
	return b.String()
}

func TestParseHeaders(t *testing.T) {
	body := mockEmailBody()
	if !strings.Contains(body, "Subject: Statusmeldung\n") {
		t.Fatalf("Subject-Zeile fehlt — Body war:\n%s", body)
	}
	if !strings.Contains(body, "\n\n") {
		t.Fatal("Leerzeile zwischen Headern und Body fehlt")
	}
}

Die Leerzeile zwischen Headern und Body — bei E-Mails RFC-relevant — entsteht durch fmt.Sprintln() ohne Argumente und ergibt den String "\n". Zusammen mit der Trailing-Newline der vorherigen Zeile wird daraus die geforderte Doppel-Newline "\n\n".

Interessantes

Auto-Spaces zwischen ALLEN Operanden

Anders als fmt.Sprint setzt Sprintln Leerzeichen zwischen jedem Paar von Operanden — unabhängig vom Typ.

Auto-Newline am Ende

Das Ergebnis endet immer auf \n. Selbst fmt.Sprintln() ohne Argumente liefert den nicht-leeren String "\n".

Liefert String, schreibt nichts

Sprintln berührt weder os.Stdout noch einen io.Writer. Das Ergebnis wandert in den Heap und ist deins.

%v auf jedem Operanden

Intern wird jeder Operand mit dem Default-Format %v formatiert. Für Pretty-Printing mit Feldnamen brauchst du %+v, also Sprintf.

Stringer-Interface greift

Implementiert ein Typ fmt.Stringer, nutzt Sprintln diese Repräsentation. Damit bekommst du sauberes Logging ohne explizite %s-Verben.

Performance: nicht für Hot-Paths

Reflection, Interface-Boxing und garantierte Heap-Allokation summieren sich. In Hot-Loops ist strings.Builder plus strconv deutlich schneller.

Trailing-Newline-Falle

fmt.Printf(&quot;%s\n&quot;, sprintlnResult) ergibt eine Leerzeile zu viel. Sprintln-Ergebnisse gibst du mit fmt.Print aus.

In Tests: präzise Erwartungswerte

Wenn der Production-Code Println verwendet, erzeugt Sprintln im Test denselben String bit-genau. Das macht Vergleiche zuverlässig.

Weiterführende Ressourcen

Externe Quellen

/ Weiter

Zurück zu Das fmt-Paket — Formatierte I/O

Zur Übersicht