What's New in Elixir 1.16
Tags: elixir
Published: 2024-01-09 12:00:00

The [Elixir 1.16 release candidate](https://github.com/elixir-lang/elixir/releases/tag/v1.16.0-rc.1) is out now, and it comes with some compelling improvements to diagnostics, documentation, and a few other enhancements that make Elixir an even better choice for developers.

We'll dive into some of these changes and highlight Elixir's continued focus on developer happiness and community building.

Let's get started!

## Better Diagnostics for Compiler Errors in Elixir

We'll begin with the improvements to compiler diagnostics provided in Elixir 1.16. Now, your `mix compile` errors come with detailed code snippets that tell you exactly where your code goes wrong.

You'll benefit from these improvements when you have syntax errors, mismatched delimiter errors, and generic compiler errors.

### Syntax Errors

First up, you can see below that syntax errors now come with a pointer to exactly where the error happened. Let's say we have the following code:

```elixir
defmodule Doctor do
  def show_prescription_codes do
    ["DR", "DS", "amp", &]
  end
end
```

If you run `mix compile`, you'll see a syntax error with an associated code snippet, like this:

```bash
sophiedebenedetto[ 7:30PM] ~/personal-projects/doctor  💖 mix compile
Compiling 1 file (.ex)

== Compilation error in file lib/doctor.ex ==
** (SyntaxError) invalid syntax found on lib/doctor.ex:48:26:
    error: syntax error before: ']'
    │
 48 │     ["DR", "DS", "amp", &]
    │                          ^
    │
    └─ lib/doctor.ex:48:26
    (elixir 1.16.0-rc.1) lib/kernel/parallel_compiler.ex:428: anonymous fn/5 in Kernel.ParallelCompiler.spawn_workers/8
```

Note the `^` pointer to the exact location of the syntax error. With this, it's even easier to quickly spot and correct such errors.

### Mismatched Delimiter Errors

Another diagnostic improvement comes with the code snippet representation for `MismatchedDelimiterError` occurrences. Given the following code with a mismatched delimiter:

```elixir
defmodule Doctor do
  def show_prescription_codes do
    ["DR", "DS", "amp", "ad")
  end
end
```

Running `mix compile` will now show you the following:

```bash
sophiedebenedetto[ 7:29PM] ~/personal-projects/doctor  💖 mix compile
Compiling 1 file (.ex)

== Compilation error in file lib/doctor.ex ==
** (MismatchedDelimiterError) mismatched delimiter found on lib/doctor.ex:48:29:
    error: unexpected token: )
    │
 48 │     ["DR", "DS", "amp", "ad")
    │     │                       └ mismatched closing delimiter (expected "]")
    │     └ unclosed delimiter
    │
    └─ lib/doctor.ex:48:29
    (elixir 1.16.0-rc.1) lib/kernel/parallel_compiler.ex:428: anonymous fn/5 in Kernel.ParallelCompiler.spawn_workers/8
```

This error includes a display of the code snippet that is causing it, and even highlights the unclosed and mismatched delimiters associated to the `MismatchedDelimiterError`.

### Other Errors

You'll now see similarly detailed code snippets for all kinds of compiler errors, including errors that describe undefined variables. Let's look at an example of that now.

Given the following code:

```elixir
def list_patients do
  Patient.for_doctor(doctor)
end
```

Running `mix compile` will display the following error:

```bash
sophiedebenedetto[ 7:36PM] ~/personal-projects/doctor  💖 mix compile
Compiling 1 file (.ex)
    error: undefined variable "doctor"
    │
 53 │     Patient.for_doctor(doctor)
    │                        ^^^^^^
    │
    └─ lib/doctor.ex:53:24: Doctor.list_patients/0


== Compilation error in file lib/doctor.ex ==
** (CompileError) lib/doctor.ex: cannot compile module Doctor (errors have been logged)
```

These improved diagnostics make it even easier for Elixir developers to understand compilation errors, catch and remedy basic mistakes, and write clean and functioning code. These improvements show Elixir's continued focus on developer experience.

## ExDoc Content Matures with Elixir

Some of the most exciting changes coming in Elixir 1.16 are the improvements to official Elixir language docs, offered through ExDoc. The "Getting Started" guide from [elixir-lang.org](https://elixir-lang.org/docs.html) has been incorporated into the [official Elixir documentation on Hex docs](https://hexdocs.pm/elixir/1.16/introduction.html).

While this may not seem too exciting on the surface, it represents a concerted effort to refine and unify official guidance on the Elixir programming language. The language and the community have matured to such a stage that we can better align on official guides like this and provide the best agreed-upon direction for new developers.

### Anti-Patterns

In this same vein, the official Elixir docs now include robust content on [Elixir anti-patterns](https://hexdocs.pm/elixir/1.16.0-rc.1/what-anti-patterns.html). This content is divided into four separate sections, covering code, design, processes, and metaprogramming.

![Anti-patterns documentation in Elixir](/images/blog/2023-12/anti-patterns.png)

Once again, this content is made possible by the maturity of the Elixir language. As Elixir has grown up, and as adoption and the community have grown, we're at a point where Elixir developers can align on what good and bad Elixir code looks like.

With this extensive guidance in the official docs, including framing context and code samples, Elixir developers have the resources they need to ship clean code that meets generally agreed-upon standards. It will be even easier for experienced and novice Elixir developers alike to write and maintain clean code in various scenarios.

And that's not it for docs enhancements.

### ExDoc Cheatsheets In the Official Elixir Docs

The official docs now include an ExDoc cheatsheet for the first time. ExDoc [cheatsheets](https://elixir-lang.org/blog/2022/12/22/cheatsheets-and-8-other-features-in-exdoc-that-improve-the-developer-experience/) allow developers to author quick, summary-type guides to modules and libraries.

The official Elixir docs now include an [Enum module cheatsheet](https://hexdocs.pm/elixir/main/enum-cheat.html), and that's just the beginning.

Elixir maintainers are looking for more community members to contribute cheatsheets to the official docs. This is a great way for first-timers to get an Elixir language contribution! Get started by opening an [issue](https://github.com/elixir-lang/elixir/issues/new) that outlines what cheatsheet you'd like to add.

Taken together, these docs improvements all point to the maturity of the Elixir language and community and the continued focus on education and support for Elixir newcomers. The official introduction docs, the anti-pattern docs, and the inclusion of cheatsheets provide clear and accessible guidance to Elixir developers at every level. This kind of alignment and clarity makes Elixir even more accessible to novices. At the same time, it empowers experienced Elixir devs to deliver clean, standardized Elixir code that performs well.

## More Enhancements

You might be interested in taking advantage of some of the smaller enhancements from this release in your upgraded Elixir apps. Let's explore them briefly now.

### The `String.replace_invalid/2` Function

First up, there is a new `String.replace_invalid/2` function that (you guessed it) lets you replace invalid characters in a string. Let's take a look at an example:

```elixir
iex> String.replace_invalid("asd" <> <<0xFF::8>>)
"asd�"
```

The default behavior is to replace the invalid character with a `�`, but you can replace it with any character you like:

```elixir
iex> String.replace_invalid("asd" <> <<0xFF::8>>, "😵")
"asd😵"
```

This will come in handy if you find yourself processing strings of text from external sources. Such text may contain invalid characters, and identifying and replacing such characters just got a lot easier.

### New Functionality for `Task.yield_many/2`

Next up, let's take a look at the new option you can pass to `Task.yield_many`. Previously, you could tell `Task.yield_many` to timeout and stop waiting for tasks if a certain time limit was exceeded. Now, you can provoke that same behavior if a certain _number_ of tasks has been reached. You can do so by providing the `limit` option.

Let's say you have the following code to spawn ten tasks. Each task will sleep for `i` number of seconds and then return a message indicating what happened. Then, we yield each task with its result, and iterate over those `{task, result}` tuples to operate on the result by `IO.inspect`-ing them:

```elixir
def MyModule do
  def do_tasks do
    tasks =
      for i <- 1..10 do
        Task.async(fn ->
          Process.sleep(i * 1000)
          "I slept for #{i} seconds"
        end)
      end

    tasks_with_results = Task.yield_many(tasks, timeout: 10000)

    results =
      Enum.map(tasks_with_results, fn {task, res} ->
        # Shut down the tasks that did not reply nor exit
        res || Task.shutdown(task, :brutal_kill)
      end)

    # Here we are matching only on {:ok, value} and
    # ignoring {:exit, _} (crashed tasks) and `nil` (no replies)
    for {:ok, value} <- results do
      IO.inspect(value)
    end
  end
end
```

This code will continue to yield tasks and their results until all the tasks are done _or_ the timeout is exceeded. Now, you can use the `limit: task_num` option instead of `timeout`, like this:

```elixir
tasks_with_results = Task.yield_many(tasks, limit: 1)
```

If we execute our function in IEx, you'll see that the code stops waiting for tasks after just one task is yielded:

```elixir
iex> MyModule.do_tasks()
"I slept for 1 seconds"
["I slept for 1 seconds"]
```

If the limit is reached before the default timeout (`5000`), or before all the tasks are done, then `Task.yield_many` returns immediately without triggering the `:on_timeout` behaviour. You can dig further into this functionality in [the docs](https://hexdocs.pm/elixir/1.16.0-rc.1/Task.html#yield_many/2).

This new functionality gives you even more fine-grained control over how your program orchestrates async tasks.

### The `Logger.levels/0` Function

The last enhancement I'll highlight here is the new `Logger.levels/0` function. This function returns the list of all available log levels:

```elixir
iex> Logger.levels()
[:error, :info, :debug, :emergency, :alert, :critical, :warning, :notice]
```

This provides a quick reminder of the levels you can use to configure your logger.

## Wrap Up

With the 1.16 release, Elixir continues to be a compelling choice for developers looking for:

- An excellent developer experience
- A welcoming and supportive community
- Strong and opinionated development guidelines
- Powerful language capabilities to meet the needs of various programming scenarios.

The new developments in diagnostics, documentation, and language functionality continue to make Elixir a pleasure to work with. Better, more detailed diagnostic messages for compiler errors show Elixir's emphasis on developer experience, making it easier than ever before to identify and fix these errors.

Improved docs and the addition of the anti-patterns guide empowers both experienced _and_ newbie Elixir devs to write clean and standardized code. And minor language improvements, like the functions we explored above, show Elixir's continued commitment to language excellence.

Happy coding!

**P.S. If you'd like to read Elixir Alchemy posts as soon as they get off the press, [subscribe to our Elixir Alchemy newsletter and never miss a single post](/elixir-alchemy)!**