Input, Output, PIDs, and Nodes

Input and output in the Erlang VM are performed using I/O servers. These are simply Erlang processes that implement a low-level message interface. You never have to deal with this interface directly (which is a good thing, as it is complex). Instead, you use the various Elixir and Erlang I/O libraries and let them do the heavy lifting.

In Elixir you identify an open file or device by the PID of its I/O server. And these PIDs behave like all other PIDs—you can, for example, send them between nodes. If you look at the implementation of Elixir’s IO.puts function, you’ll see

 def​ puts(device \\ group_leader(), item) ​do
  erl_dev = map_dev(device)
 :io​.put_chars erl_dev, [to_iodata(item), ​?​\n]
 end

(To see the source of an Elixir library module, view the online documentation at http://elixir-lang.org/docs/, navigate to the function in question, and click the Source link.)

The default device it uses is returned by the function :erlang.group_leader. (The group_leader function is imported from the :erlang module at the top of the IO module.) This will be the PID of an I/O server.

So, bring up two terminal windows and start a different named node in each. Connect to node one from node two, and register the PID returned by group_leader under a global name (we use :two).

Window #1

 $ iex --sname one
 iex(one@light-boy) >

Window #2

 $ iex --sname two
 iex(two@light-boy) > Node.connect(:"one@light-boy")
 true
 iex(two@light-boy) > :global.register_name(:two, :erlang.group_leader)
 :yes

Note that once we’ve registered the PID, we can access it from the other node. And once we’ve done that, we can pass it to IO.puts; the output appears in the other terminal window.

Window #1

 iex(one@light-boy) > two = :global.whereis_name :two
 #PID<7419.30.0>
 iex(one@light-boy) > IO.puts(two, "Hello")
 :ok
 iex(one@light-boy) > IO.puts(two, "World!")
 :ok

Window #2

 Hello
 World
 iex(two@light-boy) >