fmt.Appendln ist die Println-Variante der Append-Familie: Sie nimmt einen vorhandenen []byte-Buffer, fügt die Operanden mit garantierten Spaces dazwischen an und schließt das Ganze mit einem \n ab. Der erweiterte Buffer wird zurückgegeben — kein Zwischen-String, keine zweite Kopie, kein manueller Newline.
Die Funktion ist seit Go 1.19 Teil des fmt-Pakets und schließt eine Lücke, die Sprintln zuvor mit einer unnötigen String-Allokation füllen musste. Wer zeilenbasierten Output direkt in einen wiederverwendbaren Buffer schreibt — NDJSON-Writer, Logger-Backends, Netzwerk-Protokolle — bekommt mit Appendln den passenden Hot-Path.
Signatur
func Appendln(b []byte, a ...any) []byte — der erste Parameter b ist der Ziel-Buffer (typischerweise ein langlebiges []byte, das in einer Schleife wiederverwendet wird). Die Operanden in a werden nacheinander mit der Default-Formatierung (%v) serialisiert. Der Rückgabewert ist derselbe Buffer, möglicherweise nach interner Reallocation — deshalb muss er immer wieder zugewiesen werden.
Verhalten
Appendln verhält sich semantisch wie Println, schreibt aber nicht in os.Stdout, sondern hängt an einen Buffer an. Zwischen allen Operanden steht ein Space — unabhängig davon, ob die Nachbarn Strings, Zahlen oder Strukturen sind. Am Ende kommt genau ein \n.
package main
import (
"fmt"
"os"
)
func main() {
buf := make([]byte, 0, 64)
buf = fmt.Appendln(buf, "user", 42, true)
buf = fmt.Appendln(buf, "next", "line")
os.Stdout.Write(buf)
}user 42 true
next lineAuffällig: Auch zwischen "user" und der Zahl 42 steht ein Space. Das ist der entscheidende Unterschied zu Append — dort wird nur dann ein Space eingefügt, wenn beide Nachbarn keine Strings sind. Appendln zieht diese Regel nicht durch, sondern setzt konsequent Spaces zwischen alle Felder.
Vergleich Append / Appendln / Appendf
| Funktion | Spaces zwischen Operanden | Trailing Newline | Format-String |
|---|---|---|---|
fmt.Append | nur zwischen Nicht-Strings | nein | nein |
fmt.Appendln | immer | ja (\n) | nein |
fmt.Appendf | gesteuert durch Format | gesteuert durch Format | ja |
Appendln ist also der „Println-Modus" der Familie: zeilenorientiert, vorhersehbar, ohne Format-Bastelei. Für strukturierten Output mit präzisen Verbs ist Appendf die richtige Wahl, für rohes Zusammenfügen ohne Whitespace-Magie Append.
Use Case: NDJSON oder zeilenbasierter Output
Newline-Delimited-Formate (NDJSON, klassische Log-Zeilen, Plain-Text-Streams) leben davon, dass jede Zeile in sich abgeschlossen ist und mit \n endet. Hier spart Appendln zwei Handgriffe gleichzeitig: Die Spaces zwischen den Feldern sind gesetzt, und der Zeilenumbruch wird nicht vergessen.
package main
import (
"fmt"
"os"
)
type Event struct {
ID int
Kind string
}
func (e Event) String() string {
return fmt.Sprintf("event(id=%d kind=%s)", e.ID, e.Kind)
}
func main() {
events := []Event{
{1, "login"},
{2, "click"},
{3, "logout"},
}
buf := make([]byte, 0, 256)
for _, ev := range events {
buf = fmt.Appendln(buf, ev)
}
os.Stdout.Write(buf)
}event(id=1 kind=login)
event(id=2 kind=click)
event(id=3 kind=logout)Jeder Schleifendurchlauf hängt eine vollständige Zeile an — inklusive Newline. Im Gegensatz zu Sprintln entsteht kein Zwischen-String, der danach wieder verworfen würde.
Idiomatic Pattern
Das typische Appendln-Muster ist eine Schleife mit wiederverwendetem Buffer. Nach jedem Flush wird der Buffer per buf[:0] auf Länge null zurückgesetzt, behält aber die zugrundeliegende Kapazität.
buf := make([]byte, 0, 4096)
for batch := range incoming {
for _, item := range batch {
buf = fmt.Appendln(buf, item.ID, item.Name, item.Status)
}
if _, err := out.Write(buf); err != nil {
return err
}
buf = buf[:0]
}Der Effekt ist messbar: Nach den ersten Iterationen wächst der Buffer nicht mehr, die append-Operationen innerhalb von Appendln finden in bereits allozierter Kapazität statt.
NDJSON-Writer ohne Sprintf-Umweg
Ein simpler Logger schreibt Items als Zeilen in einen Buffer und gibt den Buffer am Ende an einen io.Writer weiter. Appendln ersetzt hier sowohl den Sprintln-Zwischenschritt als auch das manuelle Anhängen eines \n.
package main
import (
"fmt"
"os"
)
type Item struct {
ID int
Name string
}
func main() {
items := []Item{
{101, "alpha"},
{102, "beta"},
{103, "gamma"},
}
buf := make([]byte, 0, 128)
for _, item := range items {
buf = fmt.Appendln(buf, item.ID, item.Name)
}
os.Stdout.Write(buf)
}101 alpha
102 beta
103 gammaDie Zeilen sind durch das automatische Space getrennt, und der Newline pro Zeile kommt aus Appendln selbst. Kein fmt.Sprintf("%d %s\n", ...), kein append(buf, '\n') — die Funktion erledigt beides.
Zeilenbasiertes Protokoll im Buffer aufbauen
Text-Protokolle wie SMTP, IMAP oder Redis (RESP) bestehen aus Befehlen, die jeweils mit Newline terminiert sind. Wer eine Sequenz von Kommandos in einem Rutsch rausschicken will, baut sie zuerst im Buffer auf und schreibt dann eine Socket-Operation.
package main
import (
"fmt"
"os"
)
func main() {
buf := make([]byte, 0, 256)
buf = fmt.Appendln(buf, "EHLO", "client.example.com")
buf = fmt.Appendln(buf, "MAIL", "FROM:<sender@example.com>")
buf = fmt.Appendln(buf, "RCPT", "TO:<recipient@example.com>")
buf = fmt.Appendln(buf, "DATA")
os.Stdout.Write(buf)
}EHLO client.example.com
MAIL FROM:<sender@example.com>
RCPT TO:<recipient@example.com>
DATAJede Zeile ist ein Aufruf, die Spaces zwischen Verb und Argument fallen automatisch an, und das \n am Zeilenende ist garantiert. Der Vorteil gegenüber einzelnen Write-Calls auf den Socket: ein einziger Syscall am Ende, deutlich weniger Round-Trip-Overhead.
Interessantes
Go 1.19+ vorausgesetzt
fmt.Appendln existiert erst seit Go 1.19. In älteren Toolchains gibt es nur Sprintln mit String-Zwischenschritt.
Auto-Spaces und Newline wie Println
Zwischen allen Operanden steht ein Space, am Ende ein \n. Anders als bei Append gilt die Regel ausnahmslos.
Append-Semantik: Buffer wächst
Appendln ruft intern append auf dem übergebenen Buffer auf. Es entsteht kein neuer String, keine zweite Kopie.
%v auf Operanden, Stringer respektiert
Jeder Operand wird mit dem Default-Verb %v formatiert. Implementiert ein Typ fmt.Stringer, ruft Appendln String() auf.
Buffer-Reset mit buf[:0]
Nach dem Flush den Buffer per buf = buf[:0] zurücksetzen. Länge wird null, Kapazität bleibt.
Newline ist automatisch — nicht doppelt
Wer nach Appendln noch ein eigenes \n anhängt, bekommt Leerzeilen.
Für NDJSON, Logger, Line-Protokolle
Überall, wo zeilenbasierter Output in einen Buffer fließt, ist Appendln der idiomatische Hot-Path.
Spart Result-String gegenüber Sprintln
Sprintln alloziert einen String; Appendln schreibt direkt in den Buffer.