Skip to content
GitHub Repository Forum RSS-Newsfeed

Crystal 1.20.0 is released!

Julien Portalier

We are announcing a new Crystal release 1.20.0 with several new features and bug fixes.

Pre-built packages are available on GitHub Releases and our official distribution channels. See crystal-lang.org/install for installation instructions.

This release includes 161 changes since 1.19.1 by 21 contributors. We thank all the contributors for all the effort put into improving the language! ❤️

Below we list the most remarkable changes in the language, compiler and stdlib. For more details, visit the full changelog.

We do not expect any breaking changes in existing code. We expect the few bugfixes below to not negatively impact your programs. If you notice any unexpected issues, please let us know in the issue tracker or forum.

  • OpenSSL sockets shouldn’t flush on read (#16650)
  • Fix Process#wait to not close @input before waiting (#16620, #16638)
  • Ensure that heredoc lexing allows only valid identifiers (#16548)

Thanks @straight-shoota, @ysbaddaden, @Sija

HTTP::Server accepted requests containing both Content-Length and Transfer-Encoding headers and prioritized Content-Length. This allowed HTTP request smuggling as per CWE-444 when the server is behind a vulnerable frontend. Refer to the advisory for more details.

HTTP::Server now rejects requests where both headers are present. The HTTP parser now ignores Content-Length when the Transfer-Encoding header is present. (commit c948b31).

RFC 0020 introduces a new @[TargetFeature] annotation that allows specifying CPU features or a CPU model or variant for individual functions. It complements the --mattr and --mcpu CLI arguments that target the whole program. This allows embedding multiple optimized functions for different CPU features into a single executable, for example a portable SIMD implementation alongside AVX2 and AVX512 alternatives.

The program is responsible for detecting which feature is available and calling the proper function at runtime. Otherwise, the program might crash with SIGILL, for example.

For example:

{% if flag?(:x86_64) %}
  @[TargetFeature("+avx2")]
  private def foo_avx2
  end

  private def cpu_supports_avx2?
  end
{% end %}

private def foo_portable
end

def foo
  {% if flag?(:x86_64) %}
    return foo_avx2 if cpu_supports_avx2?
  {% end %}
  foo_portable
end

While not strictly required, we still recommend wrapping architecture-specific method definitions within macro flag checks.

Thanks @stakach

Execution contexts from RFC 0002 continue as a final preview feature, enabled with the compiler flags -Dpreview_mt -Dexecution_context. We plan to enable it by default in Crystal 1.21.

In addition to fixing bugs, we implemented M:N scheduling that allows threads to be attached to and detached from a context. This avoids blocking other fibers on certain syscalls, such as getaddrinfo.

  • Add Fiber::ExecutionContext::ThreadPool (#15885, #16750)
  • Detach execution context scheduler from running thread during blocking syscall (#15871, #16679)

The parallel contexts now automatically start threads to adapt to the actual workload and distribute fibers across more cores when needed.

This effort is part of the ongoing project to improve multi-threading support with the help of 84codes.

Thanks @ysbaddaden

This release brings a new ergonomic, safe, and portable API for spawning sub-processes, as proposed in RFC 0025.

A significant change is that the modern API treats the command line as an array of strings. The first element is the program to execute, and the remaining elements are its arguments.

# legacy API:
Process.run("crystal", ["tool", "format"])

# modern API:
Process.run(["crystal", "tool", "format"])

The new API also includes convenient methods for capturing process output: Process.capture and Process.capture_result.

The new API does not have a shell parameter. If you need shell behaviour, we recommend running a shell explicitly.

These are the most significant individual changes:

All new methods and overloads are experimental. We expect to stabilize the API for the next release.

Thanks @straight-shoota

An io_uring event loop is available for Linux targets and can be selected by specifying -Devloop=io_uring when compiling. The event loop is highly experimental and its performance benefits will differ for every program. We don’t expect it to be faster than epoll, and it may even be slower, except on some benchmarks with SQPOLL enabled.

SQPOLL can be selected by specifying the idle time for the kernel threads with -Dio_uring_sq_thread_idle=200 (for 200ms). It is recommended to keep the idle time low (below 2s), otherwise your CPU cores will regularly spin at 100% usage when doing nothing.

An interesting but potentially surprising behavior is that any I/O call that could yield the calling fiber will now always yield. One benefit is that an always ready socket won’t block other fibers from progressing. A downside is that there might be lots of context switches.

  • Add io_uring event loop (linux) (#16264)

Thanks @ysbaddaden

Kernel TLS is now supported by default on Linux and FreeBSD as long as support has been compiled into OpenSSL (which should be the default) and enabled on the system (likely disabled by default), for example with modprobe tls on Linux.

  • Add Kernel TLS support to custom OpenSSL::BIO (#16646)

Thanks @ysbaddaden

Thanks @Blacksmoke16, @hahwul

OptionParser now supports option bundling and can parse -ncfoo as -n -c foo.

Thanks @Qard

The OAuth module expected a success response on HTTP OK statuses, but multiple OAuth providers send error responses with an HTTP OK status (sic).

Thanks @jgaskins

HTTP::WebSocket can now negotiate sub-protocols.

Thanks @antondalgren, @straight-shoota

StringScanner saw multiple improvements, ranging from performance to subtle niceties for debugging.

Thanks @jneen

The memory management model for libxml2 changed in Crystal 1.17 to become manual instead of integrating the GC. The change introduced another memory leak that is now fixed.

  • Call xmlFree for the string pointer from xmlNodeGetContent (#16688)

Thanks @toddsundsted

Some additional niceties:

Thanks @BlobCodes, @CTC97, @zw963

  • Prefer modern linker (mold or lld) (#16696)

Thanks @straight-shoota

  • Parse MacroVar with empty expressions: %var{} (#16772)
  • Parse ProcNotation with empty arg parenthesis: () -> (#16741)
  • Add --base-path for crystal docs (#16091)

Thanks @straight-shoota, @matiasgarciaisaia

  • Support for LLVM 22.1 and 23.0 (#16631)

Thanks @HertzDevil

There is a single deprecation in this release, and it is a trivial one: rename Mutex to Sync::Mutex to use the new algorithm introduced in Crystal 1.19.

We’re experimenting with a two-step mechanism to deprecate a feature. First, we soft deprecate the method in the documentation with a DEPRECATE: callout, then after a few releases we’ll hard deprecate by adding the @[Deprecated] annotation.

Thanks @ysbaddaden


Thanks

We have been able to do all of this thanks to the continued support of 84codes and every other sponsor. To maintain and increase the development pace, donations and sponsorships are essential. OpenCollective is available for that.

Reach out to crystal@manas.tech if you’d like to become a direct sponsor or find other ways to support Crystal. We thank you in advance!

Contribute