String Formatting in Crystal

Crystal offers excellent support for string formatting. Here are some examples of common string formatting tasks.

class Point
  property x : Int32
  property y : Int32

  def initialize(@x, @y)
  end
end

p = Point.new(1, 2)

# Crystal offers several formatting options for general values.
# For example, this prints an instance of our Point struct.
puts "struct1: #{p}"

# If the value is a struct, the `#inspect` method will
# include the struct's field names.
puts "struct2: #{p.inspect}"

# To print the type of a value, use `.class`.
puts "type: #{p.class}"

# Formatting booleans is straight-forward.
puts "bool: #{true}"

# There are many options for formatting integers.
# Use standard string interpolation for base-10 formatting.
puts "int: #{123}"

# This prints a binary representation.
puts "bin: #{14.to_s(2)}"

# This prints the character corresponding to the given integer.
puts "char: #{33.chr}"

# `to_s(16)` provides hex encoding.
puts "hex: #{456.to_s(16)}"

# There are also several formatting options for floats.
# For basic decimal formatting use string interpolation.
puts "float1: #{78.9}"

# `%e` and `%E` format the float in (slightly different versions of) scientific notation.
puts "float2: #{sprintf("%e", 123400000.0)}"
puts "float3: #{sprintf("%E", 123400000.0)}"

# For basic string printing use string interpolation.
puts "str1: #{"\"string\""}"

# To double-quote strings, use `%q{}`.
puts "str2: #{%q{"string"}}"

# `unpack` can be used to render the string in base-16, with two output characters per byte of input.
puts "str3: #{"hex this".bytes.map { |b| sprintf("%02x", b) }.join}"

# To print a representation of a pointer, use `object_id`.
puts "pointer: 0x#{p.object_id.to_s(16)}"

# When formatting numbers you will often want to control the width and precision of the resulting figure.
# To specify the width of an integer, use sprintf. By default the result will be right-justified and padded with spaces.
puts "width1: |#{sprintf("%6d", 12)}|#{sprintf("%6d", 345)}|"

# You can also specify the width of printed floats, though usually you'll also want to restrict the decimal precision
# at the same time with the width.precision syntax.
puts "width2: |#{sprintf("%6.2f", 1.2)}|#{sprintf("%6.2f", 3.45)}|"

# To left-justify, use the `-` flag.
puts "width3: |#{sprintf("%-6.2f", 1.2)}|#{sprintf("%-6.2f", 3.45)}|"

# You may also want to control width when formatting strings, especially to ensure that they align in table-like output.
# For basic right-justified width:
puts "width4: |#{sprintf("%6s", "foo")}|#{sprintf("%6s", "b")}|"

# To left-justify use the `-` flag as with numbers.
puts "width5: |#{sprintf("%-6s", "foo")}|#{sprintf("%-6s", "b")}|"

# So far we've seen `puts`, which prints the formatted string to STDOUT.
# `sprintf` formats and returns a string without printing it anywhere.
s = sprintf("sprintf: a %s", "string")
puts s

# You can format+print to IO objects other than STDOUT using `IO#printf`.
STDERR.printf("io: an %s\n", "error")

To run the program, save it as string_formatting.cr and use crystal run:

$ crystal run string_formatting.cr
struct1: #<Point:0x7f8e1b8b6f60>
struct2: #<Point:0x7f8e1b8b6f60 @x=1, @y=2>
type: Point
bool: true
int: 123
bin: 1110
char: !
hex: 1c8
float1: 78.9
float2: 1.234000e+08
float3: 1.234000E+08
str1: "string"
str2: "string"
str3: 6865782074686973
pointer: 0x7f8e1b8b6f60
width1: |    12|   345|
width2: |  1.20|  3.45|
width3: |1.20  |3.45  |
width4: |   foo|     b|
width5: |foo   |b     |
sprintf: a string
io: an error

Crystal’s string formatting capabilities are similar to those in other languages, with some syntax differences. The language provides both string interpolation and sprintf-style formatting for more complex cases.