format

package module
v1.0.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 26, 2026 License: BSD-3-Clause Imports: 9 Imported by: 0

README

Format Function

Go Reference

The Format function formats strings with Python-like placeholder syntax, writing into a caller-supplied buffer. Common formatting paths are designed to reuse that buffer without allocating; see Performance for the cases where allocations may still occur.

Function Signature

func Format(dst []byte, template string, a ...any) []byte

Usage

// Basic usage
buf := make([]byte, 0, 128)
buf = format.Format(buf, "Hello {}, you are {} years old!", "Alice", 30)
// Result: "Hello Alice, you are 30 years old!"

// Reuse the buffer
buf = format.Format(buf[:0], "Price: ${:.2f}", 19.99)
// Result: "Price: $19.99"

// Various format specifiers
buf = format.Format(nil, "{:#08x} {:,d}", 255, 1000000)
// Result: "0x0000ff 1,000,000"

Supported Types

  • Integers: int, int8, int16, int32, int64
  • Unsigned integers: uint, uint8, uint16, uint32, uint64, uintptr
  • Floats: float32, float64
  • Strings: string
  • Byte slices and arrays: []byte (text by default), [N]byte (hex by default), both with the presentations documented below
  • Booleans: bool
  • Durations: time.Duration

A defined type is formatted by its underlying kind, so type ID uint64 obeys every uint64 spec and {:x} on ID(255) gives ff. An Error or String method takes precedence over the kind, which is why time.Duration prints as 1.5s and not as an integer.

Types implementing error or Stringer are formatted as strings, using the same string formatting rules — so {:>10} aligns an error the way it aligns any other string. A type with both methods is rendered by Error(), the order fmt uses, and a nil pointer held in either interface prints as <nil> rather than panicking.

Types with no supported kind — structs, maps, non-byte slices — fall back to fmt.Sprintf("%v", value), and with spec {#}/{:#} to fmt.Sprintf("%#v", value). These ignore the rest of the spec.

Template Syntax

  • {} - empty placeholder (uses default formatting)
  • {format} or {:format} - placeholder with format spec (: prefix is optional)
  • {{ - literal {
  • }} - literal }

Performance

Formatting is a single streaming pass over the template: literal runs are copied straight through and each placeholder is formatted where it stands, so there is no parse table and no limit on the number of placeholders. Given a pre-allocated buffer, common built-in values and format specs normally need no internal allocations.

Passing an argument to a ...any parameter boxes it and may move that box to the heap. In the benchmark call shape below, each runtime argument costs one allocation, while values the compiler can keep in static interface values do not. Exact escape behaviour is compiler- and call-site-dependent:

The numbers are from one local benchmark run and vary with the compiler and hardware.

ns/op B/op allocs/op
format.Format, 3 runtime arguments 248 32 3
fmt.Appendf, the same 257 32 3
format.Format, 3 constants 180 0 0
direct Append*, 3 values 135 0 0

The package does not use unsafe to influence escape analysis because a user's String or Error method may retain its receiver. Unsupported types also use fmt.Sprintf, user methods may allocate themselves, and unusually large float formats may outgrow the internal scratch buffer.

Where the allocation matters, use the direct append API. It takes concrete types and avoids any boxing. With enough destination capacity, common formats stay allocation-free and are faster in the benchmark above.

Important notes:

  • The format_spec can optionally start with a colon : (e.g., both {>10} and {:>10} work).
  • A []byte argument must not refer to dst's backing array, even when dst has zero length. Calls such as Format(b[:0], "{x}", b) are unsupported.
  • A spec that cannot be parsed is reported inline as %!(BADSPEC:<spec>) followed by the value formatted plainly, so the mistake is visible without the data being lost. The usual cause is a spec meeting the wrong argument type — {:.2f} given an int, or {:,d} given a float64.
  • Width is limited to 1000, float precision to 340 (enough for any float64: math.MaxFloat64 has 309 integer digits), and string precision to 16,777,216 runes. A spec exceeding a bound is treated as unparsable and the value is formatted without it.

Direct append API

When the value and its format spec are known at the call site, these functions skip the template parser and the any boxing entirely. They take the same format_spec strings documented below, without the surrounding braces.

func AppendInt(dst []byte, formatSpec string, v int64) []byte
func AppendUint(dst []byte, formatSpec string, v uint64) []byte
func AppendFloat(dst []byte, formatSpec string, v float64) []byte
func AppendFloat32(dst []byte, formatSpec string, v float32) []byte
func AppendDuration(dst []byte, formatSpec string, v time.Duration) []byte
func AppendString(dst []byte, formatSpec string, s string) []byte
func AppendBytes(dst []byte, formatSpec string, b []byte) []byte

func AppendSigFixed(dst []byte, value float64) []byte
func AppendSigFixed32(dst []byte, value float32, sig int) []byte
func AppendSigFixed64(dst []byte, value float64, sig int) []byte
buf = format.AppendUint(buf, ",d", 18446744073709551615)      // "18,446,744,073,709,551,615"
buf = format.AppendFloat(buf, ".2f", 19.99)                   // "19.99"
buf = format.AppendDuration(buf, ">8", 1500*time.Millisecond) // "    1.5s"
buf = format.AppendString(buf, ">8", "αβγδεζ")                // "  αβγδεζ"
buf = format.AppendBytes(buf, "0>8X", []byte{1, 2})            // "00000102"

AppendBytes requires b not to refer to dst's backing array, even when dst has zero length. Calls such as AppendBytes(b[:0], spec, b) are unsupported.

An unparsable formatSpec yields %!(BADSPEC:<spec>) followed by the value in plain strconv-style output (base 10 for integers, 'g' with round-trip precision for floats).

String argument

  • Fill: A character used to pad the string to meet the width (default is space ' ').
  • Align: Controls how the string is positioned within the width:
    • <: Left-aligned (default for strings).
    • >: Right-aligned.
    • ^: Center-aligned.
    • =: Not applicable to strings (used for numbers only; excluded here).
  • Width: Minimum field width (an integer ≥ 0). If the string is shorter, it's padded; if longer, it's unchanged unless precision truncates it.
  • Precision: Maximum number of characters to display (.N where N is an integer ≥ 0). Truncates the string if longer.
format_spec     ::=  [[fill] align] [width] ["." precision]
                  |  [[fill] align] "." precision
                  |  "." precision

fill            ::=  <any character except '{', '}', '<', '>', or '^'>
align           ::=  "<" | ">" | "^"
width           ::=  digit+
digit           ::=  "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"
precision       ::=  digit+

Byte slice and array argument

With an empty spec, []byte is appended as text. String alignment, width and precision specs such as {>10} and {.3} use the same rules as string.

A byte array such as [32]byte accepts the same specs, but its default presentation is x, not text: an array is a hash, key or id far more often than it is text, raw bytes would put control characters into a log line, and hex is the only form usable outside one. This covers every spec that names no presentation, so {} and {>10} both give hex and text cannot slip back in through a bare layout. {v} still prints the decimal list %v gives.

An unparsable spec falls back to the default presentation as well, so %!(BADSPEC:...) is followed by hex for an array and by the raw bytes for a []byte.

Text for an array is therefore reachable only by slicing it — h[:] is the explicit way to say the bytes are a stream — so {} on h and on h[:] deliberately differ.

An array held in an interface cannot be sliced, so it is copied first — without allocating, up to 64 bytes.

The following specs select a non-text presentation:

Spec Meaning Example for []byte("Hi\n")
x Lowercase hexadecimal 48690a
X Uppercase hexadecimal 48690A
q Quoted and escaped text "Hi\n"
v Decimal byte list [72 105 10]
# Go syntax, naming the argument's own type []byte{0x48, 0x69, 0xa}

The x, X and q presentations accept a string fill, alignment and width prefix, for example {>12x}, {0>12X} and {^16q}. Width applies to the rendered representation, including quotes and escapes for q. Precision is not accepted for these presentations. The v and # specs remain standalone.

byte_format_spec ::= [[fill] align] [width] ("x" | "X" | "q")
                   | "v"
                   | "#"
                   | format_spec

A defined byte-slice type uses the same rules unless its Error or String method takes precedence.

Integer argument

  • Fill – any character (except { or }) used for padding. Default: space ' '.
  • Align – positioning inside the field width:
    • < – left-aligned
    • > – right-aligned (default for numbers when no sign-aware option is used)
    • ^ – center-aligned
    • = – forces the sign/prefix to the leftmost position and pads after the sign (very useful with +, space, or 0x/0b prefixes)
  • Sign – controls display of the sign:
    • + – always show sign (+123, -123)
    • - – sign only for negative numbers (default)
    • (space) – positive numbers get a leading space ( 123, -123)
  • Alternate form (#) – adds the base prefix:
    • binary → 0b…
    • octal → 0o…
    • hex → 0x… / 0X…
  • Zero-padding (0) – shorthand for fill='0' + align='>' (or align='=' with sign). Overridden by explicit fill/align.
  • Width – minimum field width (integer ≥ 0)
  • Grouping option (,) – use commas as thousands separators (also _ for underscores)
  • Type – optional for integers; defaults to d and determines the presentation:
Type Meaning Example (1234)
d Decimal integer (default) 1234
b Binary 10011010010 0b10011010010 (with #)
o Octal 2322 0o2322 (with #)
x Hexadecimal lowercase 4d2 0x4d2 (with #)
X Hexadecimal uppercase 4D2 0X4D2 (with #)
c Unicode character (int → chr) 65'A'
h produces a human readable representation of an SI size. 82854982 -> 83 MB

A value formatted with c that is not a valid Unicode code point falls back to its plain base-10 representation. Fill, alignment, sign, alternate form, zero-padding, width and grouping are not applied to this fallback.

format_spec ::= [[fill] align] [sign] ["#"] ["0"] [width] [grouping_option] [type]

fill            ::= <any character except "{", "}", "<", ">", "^">
align           ::= "<" | ">" | "^" | "="
sign            ::= "+" | "-" | " "
grouping_option ::= "," | "_"
width           ::= digit+
digit           ::= "0"|"1"|"2"|"3"|"4"|"5"|"6"|"7"|"8"|"9"
type            ::= "b" | "c" | "d" | "h" | "o" | "x" | "X"
                  ; note: "d" is the most common

Float argument

  • Fill – Any character used for padding (default: space). Can only be specified together with an align.

  • Align – Positioning inside the field width:

    • < left
    • > right (default for numbers, as in Python)
    • ^ center
    • = forces the sign to the leftmost position (only useful with a fill character and a width)
  • Sign – Controls display of the sign:

    • (none) show sign only for negative numbers (default)
    • + always show sign for positive and negative
    • (space) show space for positive, minus for negative
  • Alternate form (#) – forces a decimal point where there would be none, and for g/G/p additionally keeps the trailing zeros those types trim: {:#.0f} on 1 gives 1., {:#g} gives 1., {:#.3g} gives 1.00. It has no effect on e/E.

  • Zero-padding (0) – If present and no align is given, pads with zeros after the sign.

  • Width – Minimum field width (integer ≥ 0).

  • Thousands separator (,) – Inserts locale-independent commas (or underscores with _).

  • Precision – Meaning depends on the type:

    • f/F/e/E number of digits after the decimal point
    • g/G maximum number of significant digits
    • p relative precision from 1 to 15 (default 13) — see Relative precision (p) below

    With no precision, every type except p prints the fewest digits that read back as the same value. This is not printf's default of 6: {:f} on 1234.5678 gives 1234.5678, where C and Python give 1234.567800. Logging a float should not silently drop or invent digits.

  • Type – Determines presentation type; optional and defaults to g:

Type Meaning Example (1234.5678)
f / F Fixed-point notation (lowercase/uppercase nan/inf) 1234.5678 / 1234.5678
e / E Exponential notation (lowercase/uppercase e) 1.2345678e+03 / 1.2345678E+03
g / G e for large exponents, f otherwise 1234.5678 / 1234.5678
p Fixed-point with relative precision (see AppendSigFixed) 1234.5678
format_spec ::= [[fill] align] [sign] ["#"] ["0"] [width] [grouping] ["." precision] [type]

fill          ::= <any character except '{', '}', '<', '>', '^'>
align         ::= "<" | ">" | "^" | "="
sign          ::= "+" | "-" | " "
grouping      ::= "," | "_"
width         ::= digit+
precision     ::= digit+
digit         ::= "0"|"1"|"2"|"3"|"4"|"5"|"6"|"7"|"8"|"9"
type          ::= "f"|"F"|"e"|"E"|"g"|"G"
                | "p"

Relative precision (p)

p prints a value in fixed-point notation — never an exponent — carrying a number of digits that scales with the magnitude, and trims trailing zeros. It is meant for numbers whose useful precision follows their size, such as prices and quantities, where f would need a different precision per instrument and g would flip to exponent notation.

The rule is a single one. Count integer digits as zero below 1, then calculate:

Decimal places = sig − (number of integer digits).

A positive result is the number of fractional digits. A negative result rounds inside the integer part: decimal places -3, for example, means rounding to the nearest thousand. The output remains fixed-point and retains the positional integer zeros; limiting significant digits does not limit the string length.

For values at or above 1 this makes sig the maximum count of significant digits. Below 1, sig becomes the count of fractional digits — and since leading zeros after the point are not significant, the significant digits kept shrink as the value gets smaller:

Value sig = 5 sig = 8
123456789 123460000 123456790
1234.5678 1234.6 1234.5678
12.345678 12.346 12.345678
1.2345678 1.2346 1.2345678
0.12345678 0.12346 0.12345678
0.0012345678 0.00123 0.00123457
1.23e-07 0 0.00000012

Note the last row: a value smaller than sig fractional digits can express formats as 0, losing it entirely. Pick sig for the smallest magnitude you need to keep, not for the typical one. The default of 13 keeps values down to 1e-13.

A negative value that rounds away this way prints as 0, not -0. With # (which keeps trailing zeros) the sign survives, because there -0.000000 still tells you the value was negative.

For float32 the result never carries more than 9 significant digits, which is all a float32 distinguishes; asking for more is not an error, it simply stops adding digits.

Documentation

Overview

Package format appends values to byte buffers using Python-like replacement fields and format specifications.

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func AppendBytes

func AppendBytes(dst []byte, formatSpec string, b []byte) []byte

AppendBytes formats b according to formatSpec and appends the result to dst. An empty spec appends b as text, while other string layout specs apply the same alignment, width and precision rules as AppendString. The x, X and q presentations accept an optional string fill, alignment and width prefix. The standalone v and # specs produce a decimal list and Go syntax.

b must not refer to dst's backing array, even when dst has zero length. Calls such as AppendBytes(b[:0], formatSpec, b) are unsupported.

func AppendDuration

func AppendDuration(dst []byte, formatSpec string, value time.Duration) []byte

AppendDuration appends the same textual representation as time.Duration.String and applies the string fill, alignment, width and precision rules.

func AppendFloat

func AppendFloat(dst []byte, formatSpec string, v float64) []byte

AppendFloat formats v according to formatSpec and appends the result to dst, returning the extended buffer. An unparsable formatSpec yields a "%!(BADSPEC:...)" marker followed by v in 'g' with the smallest number of digits that round-trips.

func AppendFloat32

func AppendFloat32(dst []byte, formatSpec string, v float32) []byte

AppendFloat32 is AppendFloat for float32 values: the result carries only the digits a float32 actually distinguishes, rather than the exact decimal expansion of its binary value.

func AppendInt

func AppendInt(dst []byte, formatSpec string, v int64) []byte

AppendInt formats v according to formatSpec and appends the result to dst, returning the extended buffer. An unparsable formatSpec yields a "%!(BADSPEC:...)" marker followed by v in plain base 10. With the c presentation, a value that is not a valid Unicode code point falls back to plain base 10 without applying the other format options.

func AppendSigFixed

func AppendSigFixed(dst []byte, value float64) []byte

AppendSigFixed is AppendSigFixed64 with the default precision of 13, which keeps values down to 1e-13.

func AppendSigFixed32

func AppendSigFixed32(dst []byte, value float32, sig int) []byte

AppendSigFixed32 is AppendSigFixed64 for float32 values. The result never carries more than 9 significant digits, which is all a float32 distinguishes.

func AppendSigFixed64

func AppendSigFixed64(dst []byte, value float64, sig int) []byte

AppendSigFixed64 appends value in fixed-point notation, never an exponent, with trailing zeros trimmed. At or above 1, sig limits significant digits, rounding within the integer part when necessary. Below 1, sig counts fractional digits, so a value too small for them formats as "0". sig must be between 1 and 15; it panics otherwise.

func AppendString

func AppendString(dst []byte, formatSpec string, s string) []byte

AppendString formats s according to formatSpec and appends the result to dst, returning the extended buffer.

The spec accepts an optional fill and alignment, a width and a precision; precision truncates s to that many runes. Width and truncation are counted in runes, not bytes. An unparsable formatSpec yields a "%!(BADSPEC:...)" marker followed by s unformatted.

func AppendUint

func AppendUint(dst []byte, formatSpec string, v uint64) []byte

AppendUint formats v according to formatSpec and appends the result to dst, returning the extended buffer. An unparsable formatSpec yields a "%!(BADSPEC:...)" marker followed by v in plain base 10.

Unlike AppendInt, values above math.MaxInt64 keep their full magnitude, so this is the correct entry point for uint, uint64 and uintptr. With the c presentation, a value that is not a valid Unicode code point falls back to plain base 10 without applying the other format options.

func Format

func Format(dst []byte, template string, a ...any) []byte

Format formats a template string with the provided arguments and appends the result to dst. It returns the extended buffer.

Example:

buf := make([]byte, 0, 128)
buf = format.Format(buf, "Hello {}, you are {} years old!", "Alice", 30)
// buf now contains: "Hello Alice, you are 30 years old!"

// Reuse the buffer
buf = format.Format(buf[:0], "Price: ${:.2f}", 19.99)
// buf now contains: "Price: $19.99"

// Various format specifiers
buf = format.Format(nil, "{:#08x} {:,d}", 255, 1000000)
// returns: "0x0000ff 1,000,000"

Template syntax:

  • {} - empty placeholder
  • {format} or {:format} - placeholder with format spec (: is optional and will be stripped)
  • {{ - literal {
  • }} - literal }

Supported types:

  • int, int8, int16, int32, int64
  • uint, uint8, uint16, uint32, uint64, uintptr
  • float32, float64
  • string; []byte and byte arrays with the x, X, q, v and # presentations. A spec naming none gives text for []byte but hex for an array, which is a hash or key far more often than it is text.
  • bool
  • time.Duration
  • error and fmt.Stringer, formatted by the string rules

A defined type is formatted by its underlying kind, so `type ID uint64` obeys every uint64 spec. An Error or String method takes precedence over the kind. The # presentation names the defined type, the way %#v does.

Types with no supported kind are formatted using fmt.Sprintf("%v", value).

With enough capacity in dst, common supported values and format specs usually need no internal allocations. Passing runtime values through `...any` may still allocate at the call site, depending on the compiler and call shape. Unsupported values use fmt.Sprintf, Error and String methods control their own allocation behavior, and unusually large float output may need a larger temporary buffer.

A []byte argument must not refer to dst's backing array, even when dst has zero length. Calls such as Format(b[:0], "{x}", b) are unsupported.

Use the AppendInt/AppendUint/AppendFloat/AppendDuration/AppendString/ AppendBytes family where the extra allocation matters; those take concrete types and avoid `any` boxing.

Example
package main

import (
	"fmt"

	"github.com/gavriva/format"
)

func main() {
	// Basic integer formatting
	result := format.Format(nil, "Answer: {}", 42)
	fmt.Println(string(result))
}
Output:
Answer: 42
Example (Binary)
package main

import (
	"fmt"

	"github.com/gavriva/format"
)

func main() {
	result := format.Format(nil, "Binary: {:b}, With prefix: {:#b}", 5, 5)
	fmt.Println(string(result))
}
Output:
Binary: 101, With prefix: 0b101
Example (Booleans)
package main

import (
	"fmt"

	"github.com/gavriva/format"
)

func main() {
	result := format.Format(nil, "Is active: {}, Is admin: {}", true, false)
	fmt.Println(string(result))
}
Output:
Is active: true, Is admin: false
Example (ByteSlice)
package main

import (
	"fmt"

	"github.com/gavriva/format"
)

func main() {
	data := []byte("hello")
	result := format.Format(nil, "Data: {}", data)
	fmt.Println(string(result))
}
Output:
Data: hello
Example (EscapedBraces)
package main

import (
	"fmt"

	"github.com/gavriva/format"
)

func main() {
	result := format.Format(nil, "Set: {{ {} }}", 42)
	fmt.Println(string(result))
}
Output:
Set: { 42 }
Example (FloatGrouping)
package main

import (
	"fmt"

	"github.com/gavriva/format"
)

func main() {
	result := format.Format(nil, "Price: ${:,.2f}", 12345.67)
	fmt.Println(string(result))
}
Output:
Price: $12,345.67
Example (FloatPrecision)
package main

import (
	"fmt"

	"github.com/gavriva/format"
)

func main() {
	result := format.Format(nil, "Pi is approximately {:.2f}", 3.14159)
	fmt.Println(string(result))
}
Output:
Pi is approximately 3.14
Example (Hexadecimal)
package main

import (
	"fmt"

	"github.com/gavriva/format"
)

func main() {
	result := format.Format(nil, "Color: {:#08x}", 255)
	fmt.Println(string(result))
}
Output:
Color: 0x0000ff
Example (MixedTypes)
package main

import (
	"fmt"

	"github.com/gavriva/format"
)

func main() {
	result := format.Format(nil, "Name: {}, Age: {}, Height: {:.2f}m", "Alice", 30, 1.65)
	fmt.Println(string(result))
}
Output:
Name: Alice, Age: 30, Height: 1.65m
Example (MultipleArguments)
package main

import (
	"fmt"

	"github.com/gavriva/format"
)

func main() {
	result := format.Format(nil, "{} + {} = {}", 2, 3, 5)
	fmt.Println(string(result))
}
Output:
2 + 3 = 5
Example (ReuseBuffer)
package main

import (
	"fmt"

	"github.com/gavriva/format"
)

func main() {
	buf := make([]byte, 0, 128)

	// First use
	buf = format.Format(buf, "Line 1: {}", 100)
	fmt.Println(string(buf))

	// Reuse buffer
	buf = format.Format(buf[:0], "Line 2: {}", 200)
	fmt.Println(string(buf))

}
Output:
Line 1: 100
Line 2: 200
Example (SignDisplay)
package main

import (
	"fmt"

	"github.com/gavriva/format"
)

func main() {
	result := format.Format(nil, "{:+d} {:+d}", 42, -42)
	fmt.Println(string(result))
}
Output:
+42 -42
Example (StringPadding)
package main

import (
	"fmt"

	"github.com/gavriva/format"
)

func main() {
	result := format.Format(nil, "[{:<10}] [{:>10}] [{:^10}]", "left", "right", "center")
	fmt.Println(string(result))
}
Output:
[left      ] [     right] [  center  ]
Example (ThousandsSeparator)
package main

import (
	"fmt"

	"github.com/gavriva/format"
)

func main() {
	result := format.Format(nil, "Population: {:,d}", 1234567)
	fmt.Println(string(result))
}
Output:
Population: 1,234,567
Example (ZeroPadding)
package main

import (
	"fmt"

	"github.com/gavriva/format"
)

func main() {
	result := format.Format(nil, "ID: {:05d}", 42)
	fmt.Println(string(result))
}
Output:
ID: 00042

Types

This section is empty.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL