Elixir v1.15 has just been released. 🎉

Elixir v1.15 is a smaller release with focused improvements on compilation and boot times. This release also completes our integration process with Erlang/OTP logger, bringing new features such as log rotation and compression out of the box.

You will also find additional convenience functions in Code, Map, Keyword, all Calendar modules, and others.

Finally, we are glad to welcome Jean Klingler as a member of the Elixir Core team. Thank you for your contributions!

Compile and boot-time improvements

The last several releases brought improvements to compilation time and this version is no different. In particular, Elixir now caches and prunes load paths before compilation, ensuring your project (and dependencies!) compile faster and in an environment closer to production.

In a nutshell, the Erlang VM loads modules from code paths. Each application that ships with Erlang and Elixir plus each dependency become an entry in your code path. The larger the code path, the more work Erlang has to do in order to find a module.

In previous versions, Mix would only add entries to the load paths. Therefore, if you compiled 20 dependencies and you went to compile the 21st, the code path would have 21 entries (plus all Erlang and Elixir apps). This allowed modules from unrelated dependencies to be seen and made compilation slower the more dependencies you had. With this release, we will now prune the code paths to only the ones listed as dependencies, bringing the behaviour closer to mix release.

Furthermore, Erlang/OTP 26 allows us to start applications concurrently and cache the code path lookups, decreasing the cost of booting applications. The combination of Elixir v1.15 and Erlang/OTP 26 should also reduce the boot time of applications, such as when starting iex -S mix or running a single test with mix test.

As an example, I have benchmarked the Livebook application on a M1 Max MacStudio across different Elixir and Erlang/OTP versions. At the time of benchmarking, Livebook had ~200 source .ex files and ~35 dependencies. Compilation-times were improved by 16%:

Livebook compilation times

Livebook saw an improvement of 30% on boot times:

Livebook boot times

Different application will see different results. Our expectations are the gains will be more meaningful the more dependencies you have, the more files you have, and the more cores you have. We have even received reports of up to 40% faster compilation times, although it is yet unclear how generalizable this will be in practice. Note this work does not improve the time to compile slow individual files.

The compiler is also smarter in several ways: @behaviour declarations no longer add compile-time dependencies and aliases in patterns and guards add no dependency whatsoever, as no dispatching happens. Furthermore, Mix now tracks the digests of @external_resource files, reducing the amount of recompilation when swapping branches. Finally, dependencies are automatically recompiled when their compile-time configuration changes, providing a smoother development experience.

Potential incompatibilities

Due to the code path pruning, if you have an application or dependency that does not specify its dependencies on Erlang/OTP and core Elixir applications, which has always been erroneus behaviour, it may no longer compile successfully in Elixir v1.15. You can temporarily disable code path pruning by setting prune_code_paths: false in your mix.exs, although doing so may lead to runtime bugs that are only manifested inside a mix release.

Compiler warnings and errors

The Elixir compiler can now emit many errors for a single file, making sure more feedback is reported to developers before compilation is aborted.

In Elixir v1.14, an undefined function would be reported as:

** (CompileError) undefined function foo/0 (there is no such import)

In Elixir v1.15, the new reports will look like:

error: undefined function foo/0 (there is no such import)

** (CompileError) my_file.exs: cannot compile file (errors have been logged)

A new function, called Code.with_diagnostics/2, has been added so this information can be leveraged by editors, allowing them to point to several errors at once. We have currently ongoing work and contribution to further improve the compiler diagnostics in future Elixir releases.

Potential incompatibilities

As part of this effort, the behaviour where undefined variables were transformed into nullary function calls, often leading to confusing error reports, has been disabled during project compilation. You can invoke Code.compiler_options(on_undefined_variable: :warn) at the top of your mix.exs to bring the old behaviour back.

Integration with Erlang/OTP logger

This release provides additional features such as global logger metadata and file logging (with rotation and compression) out of the box!

This release also soft-deprecates Elixir’s Logger Backends in favor of Erlang’s Logger handlers. Elixir will automatically convert your :console backend configuration into the new configuration. Previously, you would set:

config :logger, :console,
  level: :error,
  format: "$time $message $metadata"

Which is now translated to the equivalent:

config :logger, :default_handler,
  level: :error

config :logger, :default_formatter,
  format: "$time $message $metadata"

To replace the default console handler by one that writes to disk, with log rotation and compression:

config :logger, :default_handler,
  config: [
    file: ~c"system.log",
    filesync_repeat_interval: 5000,
    file_check: 5000,
    max_no_bytes: 10_000_000,
    max_no_files: 5,
    compress_on_rotate: true

Finally, the previous Logger Backends API is now soft-deprecated. If you implement your own backends, you want to consider migrating to :logger_backends in the long term. See the new Logger documentation for more information on the new features and compatibility.

Learn more

For a complete list of all changes, see the full release notes.

Check the Install section to get Elixir installed and read our Getting Started guide to learn more.

Happy compiling!