Emacs, scripting and anything text oriented.

Nim fmt

Kaushal Modi

Nim 0.18.0+ ships with the strformat library that provides the fmt template which allows one to format strings using format specifiers like in Python 3’s f-strings.

The strformat library provides string interpolation inspired by Python 3’s Formatted String Literals or f-strings. I find myself almost always importing strformat to get the beautiful string formatting using its fmt template.

Basics #

Use the fmt template to get an interpolated string.

You can also use the unary operator & instead of fmt, which works better for the \n explained in fmt and & – Printing newline using \n inside fmt.

import strformat
let
  fname = "Kaushal"
  lname = "Modi"
echo fmt"My name is {fname} {lname}."
echo &"My name is {fname} {lname}."
My name is Kaushal Modi.
My name is Kaushal Modi.

The fmt can be used like a proc too:

import strformat
let str = "abc"
echo fmt("{str}")
echo "{str}".fmt
abc
abc

Format Specifier #

The general syntax for fmt is fmt"{VARorSTRING:FORMATSPECIFIER}" (or &"{VARorSTRING:FORMATSPECIFIER}").

The “FORMATSPECIFIER” is further broken down into (ref):

[[fill]align][sign][#][0][minimumwidth][.precision][type]

The square brackets [] indicate an optional element.

Below sub-sections delve into the explanation of each of those format specifier fields, with examples.

Alignment #

[[fill] align ][sign][#][0][ minimumwidth ][.precision][type]

Table 1: fmt 'alignment' field
Alignment + Min widthDescription
NLeft aligns strings, right aligns numbers (ints and floats)
>NRight align, or pad spaces to the left
<NLeft align, or pad spaces to the right
^NCenter align, or pad spaces on both sides
  • N is the number of characters.
    • Spaces are padded only if N is greater than the string length.
  • If N is not defined, the field width will always be the same size as the data to fill it. So the alignment field has no meaning in that case.
  • >, < and ^ are the right, left and center alignment flags respectively.

Right align #

  • Number type values (ints, floats) get right aligned by default.
  • >N forces the right align.
import strformat
let
  str: string = "abc"
  flt: float = 123.456
  int1: int = -12345
  int2: int = -1
  int3: int = 16
echo "String formatting:"
echo &"  `{str:>5}'"
echo &"""  `{"def":>5}'"""
echo "\nFloat formatting:"
echo fmt"  `{flt:9.3f}'"
echo fmt"  `{flt:9.4f}'"
echo fmt"  `{flt:>9.3f}'"
echo fmt"  `{flt:>9.0f}'"
echo fmt"  `{flt:e}'"
echo fmt"  `{flt:13e}'"
echo fmt"  `{flt:>13e}'"
echo "\nInteger formatting:"
echo fmt"  `{int1:9}'"
echo fmt"  `{int1:>9}'"
echo fmt"  `{int2:4}'"
String formatting:
  `  abc'
  `  def'

Float formatting:
  `  123.456'
  ` 123.4560'
  `  123.456'
  `     123.'
  `1.234560e+02'
  ` 1.234560e+02'
  ` 1.234560e+02'

Integer formatting:
  `   -12345'
  `   -12345'
  `  -1'

Left align #

  • String type values get left aligned by default.
  • <N forces the left align.
import strformat
let
  str: string = "abc"
  flt: float = 123.456
  int1: int = -12345
  int2: int = -1
  int3: int = 16
echo "String formatting:"
echo &"  `{str:5}'"
echo &"""  `{str:<5}'"""
echo &"""  `{"def":<5}'"""
echo "\nFloat formatting:"
echo fmt"  `{flt}'"
echo fmt"  `{flt:<9.4f}'"
echo "\nInteger formatting:"
echo fmt"  `{int1:<10}'"
echo fmt"  `{int2:<4}'"
echo fmt"  `{int3:<4}'"
String formatting:
  `abc  '
  `abc  '
  `def  '

Float formatting:
  `123.456'
  `123.4560 '

Integer formatting:
  `-12345    '
  `-1  '
  `16  '

Center align #

import strformat
let
  str: string = "abc"
  flt: float = 123.4
  int1: int = -12345
echo "String formatting:"
echo &"  `{str:^5}'"
echo &"  `{str:^6}'"
echo &"  `{str:^7}'"
echo "\nFloat formatting:"
echo fmt"  `{flt:^11.3f}'"
echo fmt"  `{flt:^11.0f}'"
echo fmt"  `{flt:^13e}'"
echo fmt"  `{flt:^11.4f}'"
echo "\nInteger formatting:"
echo fmt"  `{int1:^10}'"
String formatting:
  ` abc '
  ` abc  '
  `  abc  '

Float formatting:
  `  123.400  '
  `   123.    '
  `1.234000e+02 '
  ` 123.4000  '

Integer formatting:
  `  -12345  '

If exact centering cannot be done, the one extra space is added to the right.

Fill #

[[ fill ] align ][sign][#][0][ minimumwidth ][.precision][type]

Table 2: fmt 'fill' field
Fill + Alignment + Min widthDescription
F>NRight align and pad the “F” char to the left
F<NLeft align and pad the “F” char to the right
F^NCenter align and pad the “F” char on both sides
  • N is the number of characters.
    • Filling happens only if N is greater than the string length.
  • F is the optional ‘fill’ character.
    • The fill character, if present, must be followed by an alignment flag (<, >, ^).

Right Align + Fill #

import strformat
let
  str: string = "abc"
  flt: float = 123.456
  int1: int = 123
echo "String formatting:"
echo &"  `{str:x>5}'"
echo "\nFloat formatting:"
echo fmt"  `{flt:x>9.3f}'"
echo "\nInteger formatting:"
echo fmt"  `{int1:x>9}'"
String formatting:
  `xxabc'

Float formatting:
  `xx123.456'

Integer formatting:
  `xxxxxx123'

Left Align + Fill #

import strformat
let
  str: string = "abc"
  flt: float = 123.456
  int1: int = 123
echo "String formatting:"
echo &"  `{str:x<5}'"
echo "\nFloat formatting:"
echo fmt"  `{flt:x<9.3f}'"
echo "\nInteger formatting:"
echo fmt"  `{int1:x<9}'"
String formatting:
  `abcxx'

Float formatting:
  `123.456xx'

Integer formatting:
  `123xxxxxx'

Center Align + Fill #

import strformat
let
  str: string = "abc"
  flt: float = 123.456
  int1: int = 123
echo "String formatting:"
echo &"  `{str:x^5}'"
echo "\nFloat formatting:"
echo fmt"  `{flt:x^9.3f}'"
echo "\nInteger formatting:"
echo fmt"  `{int1:x^9}'"
String formatting:
  `xabcx'

Float formatting:
  `x123.456x'

Integer formatting:
  `xxx123xxx'

Sign #

[[fill]align][ sign ][#][0][minimumwidth][.precision][type]

The ‘sign’ field is only valid for numeric types, and can be one of the following:

Table 3: fmt 'sign' field
SignMeaning
- (default)Indicates that a sign should be used only for negative numbers.
+Indicates that a sign should be used for both positive as well as negative numbers.
(space)Indicates that a leading space should be used on positive numbers.

The ‘sign’ field affects the formatting of only positive numbers.

import strformat
let
  intp: int = 32
  intn: int = -44
  fltp: float = 1.123
  fltn: float = -4.56
echo "Positive integer formatting:"
echo fmt"  Default: `{intp}'"
echo fmt"        -: `{intp:-}'"
echo fmt"        +: `{intp:+}'"
echo fmt"    space: `{intp: }'"
echo "Negative integer formatting:"
echo fmt"  Default: `{intn}'"
echo fmt"        -: `{intn:-}'"
echo fmt"        +: `{intn:+}'"
echo fmt"    space: `{intn: }'"
echo "\nPositive float formatting:"
echo fmt"  Default: `{fltp}'"
echo fmt"        -: `{fltp:-}'"
echo fmt"        +: `{fltp:+}'"
echo fmt"    space: `{fltp: }'"
echo "Negative float formatting:"
echo fmt"  Default: `{fltn}'"
echo fmt"        -: `{fltn:-}'"
echo fmt"        +: `{fltn:+}'"
echo fmt"    space: `{fltn: }'"
Positive integer formatting:
  Default: `32'
        -: `32'
        +: `+32'
    space: ` 32'
Negative integer formatting:
  Default: `-44'
        -: `-44'
        +: `-44'
    space: `-44'

Positive float formatting:
  Default: `1.123'
        -: `1.123'
        +: `+1.123'
    space: ` 1.123'
Negative float formatting:
  Default: `-4.56'
        -: `-4.56'
        +: `-4.56'
    space: `-4.56'

FIXED Negative zero #

import strformat
echo fmt"{-0.0:+}"
-0
Older issue #

Nim Issue #7923

Thanks to @skilchen from GitHub for fixing this in 230692a22f!

Hash sign “#” (only for integers) #

[[fill]align][sign][ # ][0][minimumwidth][.precision][ type ]

If the ‘#’ character is present, integers use an alternate form for formatting. This means that binary, octal, and hexadecimal output will be prefixed with ‘0b’, ‘0o’, and ‘0x’, respectively.

The presentation type for the integer is specified in the ‘type’ field.

Table 4: fmt 'type' field for integers
TypeDescription
d (or none)(default) Decimal Integer. Outputs the number in base 10.
bBinary. Outputs the number in base 2.
oOctal format. Outputs the number in base 8.
xHex format. Outputs the number in base 16, using lower-case letters for the digits above 9.
XHex format. Outputs the number in base 16, using uppercase letters for the digits above 9.

If the ‘type’ field is not specified, “#” does not do anything (it defaults to the decimal presentation).

import strformat
let i_seq: seq[int] = @[-16, 0, 57005, 48879]
for i in i_seq:
  echo fmt"value = {i}"
  echo fmt"  none: `{i}'"
  echo fmt"     d: `{i:#d}'"
  echo fmt"     b: `{i:#b}'"
  echo fmt"     o: `{i:#o}'"
  echo fmt"     x: `{i:#x}'"
  echo fmt"     X: `{i:#X}'"
value = -16
  none: `-16'
     d: `-16'
     b: `-0b10000'
     o: `-0o20'
     x: `-0x10'
     X: `-0x10'
value = 0
  none: `0'
     d: `0'
     b: `0b0'
     o: `0o0'
     x: `0x0'
     X: `0x0'
value = 57005
  none: `57005'
     d: `57005'
     b: `0b1101111010101101'
     o: `0o157255'
     x: `0xdead'
     X: `0xDEAD'
value = 48879
  none: `48879'
     d: `48879'
     b: `0b1011111011101111'
     o: `0o137357'
     x: `0xbeef'
     X: `0xBEEF'

Zero-padding (only for numbers) #

[[fill]align][sign][#][ 0 ][minimumwidth][.precision][type]

Zero-padding is enabled if the ‘minimumwidth’ field is preceded by a zero (‘0’) character.

Integers #

import strformat
let
  int1: int = -12345
  int2: int = 1
echo fmt"`{int1:08}'"
echo fmt"`{int2:04}'"
`-0012345'
`0001'

Floats #

import strformat
echo fmt"{-1.5:08}"
echo fmt"{1.5:08}"
-00001.5
000001.5
Older issue #

Thanks to @skilchen from GitHub, this issue was fixed in 07ff9940f4.

This was Nim Issue #7932 – Zero padding did not work with floats.

Precision #

[[fill]align][sign][#][0][minimumwidth][ .precision ][type]

For floats
The ‘precision’ is a decimal number indicating how many digits should be displayed after the decimal point in a floating point conversion.
For integers
This field is ignored.
For non-numeric types
This field indicates the maximum field size - in other words, how many characters will be used from the field content.

Floats #

import strformat
echo fmt"   .1  :: {12.345678:.1}"
echo fmt"  7.1  :: {12.345678:7.1}"
echo fmt"  7.1g :: {12.345678:7.1g}"
echo fmt"  7.1f :: {12.345678:7.1f}"
echo fmt" 07.1f :: {12.345678:07.1f}"
echo fmt"+07.1f :: {12.345678:+07.1f}"
echo fmt"+07.2f :: {12.345678:+07.2f}"
echo fmt"+07.3f :: {12.345678:+07.3f}"
echo fmt"+07.4f :: {12.345678:+07.4f}"
echo fmt"+07.5f :: {12.345678:+07.5f}"
echo fmt"+07.6f :: {12.345678:+07.6f}"
   .1  :: 1.e+01
  7.1  ::  1.e+01
  7.1g ::  1.e+01
  7.1f ::    12.3
 07.1f :: 00012.3
+07.1f :: +0012.3
+07.2f :: +012.35
+07.3f :: +12.346
+07.4f :: +12.3457
+07.5f :: +12.34568
+07.6f :: +12.345678

FIXED Strings #

import strformat
let str = "abc"
echo fmt">7.1 :: `{str:>7.1}'"
echo fmt" 7.1 :: `{str:7.1}'"
echo fmt" 7.2 :: `{str:7.2}'"
echo fmt" 7.3 :: `{str:7.3}'"
>7.1 :: `      a'
 7.1 :: `a      '
 7.2 :: `ab     '
 7.3 :: `abc    '
Older issue #

Nim Issue #7933 – The .precision field should behave as maxwidth for strings as per the documentation, but did not work.

Thanks to @skilchen from GitHub for fixing this in fd102f39bb!

Type (only for numbers) #

[[fill]align][sign][#][0][minimumwidth][.precision][ type ]

Integers #

See Hash sign “#” (only for integers).

Floats #

Table 5: fmt 'type' field for floats
TypeDescription
eExponent notation. Prints the number in scientific notation using the letter ‘e’ to indicate the exponent.
EExponent notation. Same as ‘e’ except it converts the number to uppercase.
fFixed point. Displays the number as a fixed-point number.
FFixed point. Same as ‘f’ except it converts the number to uppercase.
gGeneral format. This prints the number as a fixed-point number, unless the number is too large, in which case it switches to ‘e’ exponent notation.
GGeneral format. Same as ‘g’ except switches to ‘E’ if the number gets to large.
(None)(default) similar to ‘g’, except that it prints at least one digit after the decimal point.
import strformat
let f_seq: seq[float] = @[-1234.567, 0.0, 1.2345, 5678901234.5678]
for flt in f_seq:
  echo fmt"value = {flt}"
  echo fmt"  none: `{flt}'"
  echo fmt"     e: `{flt:e}'"
  echo fmt"     E: `{flt:E}'"
  echo fmt"     f: `{flt:f}'"
  echo fmt"     F: `{flt:F}'"
  echo fmt"     g: `{flt:g}'"
  echo fmt"     G: `{flt:G}'"
value = -1234.567
  none: `-1234.567'
     e: `-1.234567e+03'
     E: `-1.234567E+03'
     f: `-1234.567000'
     F: `-1234.567000'
     g: `-1234.57'
     G: `-1234.57'
value = 0.0
  none: `0.0'
     e: `0.000000e+00'
     E: `0.000000E+00'
     f: `0.000000'
     F: `0.000000'
     g: `0'
     G: `0'
value = 1.2345
  none: `1.2345'
     e: `1.234500e+00'
     E: `1.234500E+00'
     f: `1.234500'
     F: `1.234500'
     g: `1.2345'
     G: `1.2345'
value = 5678901234.5678
  none: `5678901234.5678'
     e: `5.678901e+09'
     E: `5.678901E+09'
     f: `5678901234.567800'
     F: `5678901234.567800'
     g: `5.6789e+09'
     G: `5.6789E+09'

Strings #

For strings, the ‘type’ field is implicitly ’s’, the only valid value for strings.

import strformat
let str = "abc"
echo fmt"{str}"
echo fmt"{str:s}"
abc
abc

fmt Limitations #

fmt and & – Printing newline using \n inside fmt #

As you see above, you can use either fmt or the unary & operator for formatting. The difference between them is subtle but important.

The fmt"{expr}" syntax is more aesthetically pleasing, but it hides a small gotcha — The string is a generalized raw string literal. So the below won’t work:

import strformat
echo fmt"line1\nline2"
line1\nline2

But there are multiple ways to get around that limitation:

  1. Use & instead

    import strformat
    echo &"line1\nline2"
    line1
    line2
  2. Wrap the \n in braces

    import strformat
    echo fmt"line1{'\n'}line2"
    line1
    line2
  3. Call fmt like a proc

    import strformat
    echo fmt("line1\nline2")
    echo "line1\nline2".fmt
    line1
    line2
    line1
    line2

TO BE FIXED fmt and & formatting strings cannot be a variable #

import strformat
let
  str = "abc"
  f = "{abc}"
echo fmt(f)

Trying to evaluate the above gives this error:

stack trace: (most recent call last)
lib/pure/strformat.nim(281) &
nim_src_rJX8ZE.nim(8, 10) Error: string formatting (fmt(), &) only works with string literals

It’s the same for & too; same the same error:

import strformat
let
  str = "abc"
  f = "{abc}"
echo &f

FIXED fmt does not handle open arrays #

Nim Issue #7940

Thanks to @data-man from GitHub, this issue is now fixed!

import strformat
proc foo(x: openArray[int]) =
  echo fmt"{x}"
foo([1, 2, 3])
[1, 2, 3]

See this section in Nim notes for details about the failure before this fix.

Reference #