String Formatting in Elixir

Elixir offers excellent support for string formatting using various functions and operators. Here are some examples of common string formatting tasks.

defmodule StringFormatting do
  defmodule Point do
    defstruct x: 0, y: 0
  end

  def run do
    # Elixir offers several ways to format general values.
    # For example, this prints an instance of our Point struct.
    p = %Point{x: 1, y: 2}
    IO.puts("struct1: #{inspect(p)}")

    # If the value is a struct, the inspect function will
    # include the struct's field names by default.
    IO.puts("struct2: #{inspect(p, pretty: true)}")

    # The :structs option can be used to control how structs are printed.
    IO.puts("struct3: #{inspect(p, structs: false)}")

    # To print the type of a value, use the __struct__ attribute for structs,
    # or the built-in is_* functions for primitives.
    IO.puts("type: #{p.__struct__}")

    # Formatting booleans is straightforward.
    IO.puts("bool: #{true}")

    # There are many options for formatting integers.
    # Use to_string for standard base-10 formatting.
    IO.puts("int: #{to_string(123)}")

    # This prints a binary representation.
    IO.puts("bin: #{Integer.to_string(14, 2)}")

    # This prints the character corresponding to the given integer.
    IO.puts("char: #{<<33>>}")

    # Base.encode16 provides hex encoding.
    IO.puts("hex: #{Base.encode16(<<456::16>>, case: :lower)}")

    # There are also several formatting options for floats.
    # For basic decimal formatting use to_string.
    IO.puts("float1: #{to_string(78.9)}")

    # :erlang.float_to_binary can be used for more control over float formatting.
    IO.puts("float2: #{:erlang.float_to_binary(123400000.0, [:scientific, decimals: 6])}")
    IO.puts("float3: #{:erlang.float_to_binary(123400000.0, [:scientific, decimals: 6])}")

    # For basic string printing, just use string interpolation.
    IO.puts("str1: #{"\"string\""}")

    # To include quotes, you can use the inspect function.
    IO.puts("str2: #{inspect("string")}")

    # Base.encode16 can be used to render the string in base-16.
    IO.puts("str3: #{Base.encode16("hex this", case: :lower)}")

    # To print a representation of a reference, use inspect.
    IO.puts("reference: #{inspect(make_ref())}")

    # When formatting numbers you will often want to control the width and precision.
    # You can use String.pad_leading for this.
    IO.puts("width1: |#{String.pad_leading(to_string(12), 6)}|#{String.pad_leading(to_string(345), 6)}|")

    # For floats, you can use :erlang.float_to_binary and then pad the result.
    IO.puts("width2: |#{String.pad_leading(:erlang.float_to_binary(1.2, decimals: 2), 6)}|#{String.pad_leading(:erlang.float_to_binary(3.45, decimals: 2), 6)}|")

    # To left-justify, use String.pad_trailing instead.
    IO.puts("width3: |#{String.pad_trailing(:erlang.float_to_binary(1.2, decimals: 2), 6)}|#{String.pad_trailing(:erlang.float_to_binary(3.45, decimals: 2), 6)}|")

    # You can also control width when formatting strings.
    IO.puts("width4: |#{String.pad_leading("foo", 6)}|#{String.pad_leading("b", 6)}|")

    # To left-justify strings, use String.pad_trailing.
    IO.puts("width5: |#{String.pad_trailing("foo", 6)}|#{String.pad_trailing("b", 6)}|")

    # So far we've seen IO.puts, which prints the formatted string.
    # You can use string interpolation or Kernel.to_string to format without printing.
    s = "sprintf: a #{to_string("string")}"
    IO.puts(s)

    # You can format+print to IO devices other than :stdio using IO.write.
    IO.write(:stderr, "io: an #{to_string("error")}\n")
  end
end

StringFormatting.run()

To run the program, save it as string_formatting.exs and use elixir:

$ elixir string_formatting.exs
struct1: %StringFormatting.Point{x: 1, y: 2}
struct2: %StringFormatting.Point{
  x: 1,
  y: 2
}
struct3: %{x: 1, y: 2}
type: Elixir.StringFormatting.Point
bool: true
int: 123
bin: 1110
char: !
hex: 01c8
float1: 78.9
float2: 1.234000e+8
float3: 1.234000e+8
str1: "string"
str2: "\"string\""
str3: 6865782074686973
reference: #Reference<0.123456789.1234567.123456>
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

This example demonstrates various string formatting techniques in Elixir, including struct formatting, number formatting, width control, and writing to different IO devices. Elixir’s approach to string formatting is somewhat different from Go’s, relying more on functions and operators rather than format specifiers, but it provides similar capabilities.