What is an iolist? What is a string?

elixir erlang

Posted

I was looking back at my first post on iolists in Elixir and Erlang and realized that I never exactly defined what an iolist is. I said:

An IO list just means "a list of things suitable for input/output operations", like strings or codepoints. Functions like IO.puts/1 and File.write/2 accept "IO data", which can be either a simple string or an IO list.

That's not terrible, but not very precise.

Also, Elixir has a bunch of string-like types, as James walked through in our ElixirConf 2016 talk "String Theory", and they're very confusing.

So to put it on the internet in textual form, here's a rundown of Elixir's string-like types, including iolists.

Bitstrings, binaries, and strings

More examples:

iolists

An iolist is a list which many Erlang functions can use for io - input/output - like writing to a file or writing to a socket. (For example, sending an HTTP response involves writing data to a TCP socket.) But not every list is an iolist. A list of maps is not an iolist, for example.

An iolist is one of the following things, as defined in the Erlang reference manual page on typespecs:

You can call :erlang.iolist_to_binary/1 with any of the examples above, whereas if you call it with [%{}] you'll get an ArgumentError. (If you want to test whether a list is a charlist, you can call :io_lib.char_list/1.)

Note: it's acceptable for iolists to be "improper lists", meaning "a list whose tail is not a list but something else". However, this is only acceptable if the tail is a binary.

Examples:

Aside: why nested and/or improper lists?

The ability to use nested lists means we can append to a list in O(1).

l = ["a"]    # => ["a"]
l = [l, "b"] # => [["a"], "b"]
l = [l, "c"] # => [[["a"], "b"], "c"]

No need to copy the whole list (because it's immutable), walk to the end of the new list and add a pointer to the new item. Instead, we just allocate a new list to wrap them both.

The ability to use improper lists means we can do the same trick but allocate fewer lists:

l = ["a"]     # => ["a"]
l = [l | "b"] # => [["a"] | "b"]
l = [l | "c"] # => [[["a"] | "b"] | "c"]

iodata and chardata

Again, from the Erlang manual, the definition of iodata is simple, given the (complicated) definitions above: iodata is either an iolist or a binary.

chardata is harder to find, but the way James defined in our ElixirConf talk "String Theory" was:

A proper or improper list of UTF-8 codepoints, strings, and/or nested chardata lists or a string

The Elixir docs describe them as:

Conclusion

I just finished typing this and I already find it hard to keep these straight again. 😅 So don't worry if you do, too.