Recently, I was coding a module to create CSV files from database records when I came across an unexpected error in Elixir. Using the CSV library, I wrote something like this:

# mix.exs
defp deps do
  [
    {:csv, "~> 1.4.2"}
    ...
  ]
end

# csv.ex
defmodule Foo do
  defmodule CSV do
    def export(file) do
      CSV.encode(file)
    end
  end
end

I expected Foo.CSV.export(file) to trigger the CSV library to encode the file I passed into the function. To my surprise, the app came back with this error:

(UndefinedFunctionError) function Foo.CSV.encode/1 is undefined 
or private

This error kind of makes sense. I’m calling a function on a CSV module, and I just defined a named CSV module right above it. Naturally, the app should be confused as to which CSV module I’m referring to. But I really didn’t want to change my module’s name, so how can I tell the app which module I want to use?

After some experimenting, I found something even more interesting - switching from a nested module to a single namespaced module made it work:

# csv.ex
defmodule Foo.CSV do
  def export(file) do
    CSV.encode(file)
  end
end

What?? I thought nested modules and namespaced modules compiled to the same thing? Shouldn’t the first example also compile to Foo.CSV? And why does the second example not throw the same error as the first one?

Why this happens

After asking around, I found this behavior puzzled other Elixir developers as well. Thankfully, Bryan Joseph helped me figure out what was happening and also pointed me to part in the docs that explains this behavior.

While compiling, when Elixir reaches a nested module, it creates an “auto-alias” for that nested module. In other words, this code:

defmodule Foo do
  defmodule CSV do
    def export(file) do
      CSV.encode(file)
    end
  end
end

actually compiles to something more like this behind the scenes:

defmodule Foo do
  defmodule Foo.CSV do
    def export(file) do
      CSV.encode(file)
    end
  end
 
  alias Foo.CSV, as: CSV
end

This is why my original example threw an error - when CSV.encode(file) is called, the auto-alias directs the app to instead look for an encode/1 function on the Elixir.Foo.CSV module.

Yet, when modules are not nested, the compiler does not create an auto-alias, so there is no confusion about which CSV module I’m trying to reference.

So if you are hitting a similar error with nested modules, consider either changing the name or switching over to a namespaced module instead.