Skip to content
GitHub Repository Forum RSS-Newsfeed

Crystal 1.17.0 is released!

Johannes Müller

We are announcing a new Crystal release 1.17.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.

Stats

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

Changes

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

Breaking

The following changes break prior behavior of the compiler, but we expect them to not break much in existing code. If you notice any unexpected issues, please let us know in the issue tracker or forum.

Colorize only on TTY by default

Colorize.on_tty_only! is now the default behavior. It seems sensible to have colorization only enabled implicitly when writing to a TTY the supports colors (#15881).

Previously, it would always be enabled unless the NO_COLOR environment variable is set. Now the default honors this variable, checks the standard streams are TTYs and TERM is not dumb, just as Colorize.on_tty_only! did already. The old behaviour is available by setting ``.

Calling Colorize.on_tty_only! is no longer necessary, unless you want to explicitly reset after the default is overridden.

The new method Colorize.default_enabled? exposes the default logic for arbitrary IOs (#15912).

require "colorize"

# Prints "world" in color in a shell, but not when piped to a file:
puts "Hello #{"world".colorize(:red)}"

# This resets the behaviour before Crystal 1.17
Colorize.enabled = !ENV["NO_COLOR"]?.try(&.empty?.!)

# Now always prints "word" in color unless `NO_COLOR` is specified, even when piped to a file:
puts "Hello #{"world".colorize(:red)}"

Thanks, @HertzDevil

Manual memory management for libxml2

The libxml2 bindings are switching to use manual memory management (#15906).

There are no breaking changes in the exposed stdlib API and we do not expect any issues if you’re only using that. However, custom extensions that use libxml2 directly in conjunction with stdlib API nodes may break in some specific use cases. When linking a node into a document, it’s necessary to update the Node#document reference accordingly.

The API methods that expose internal details of libxml2 are considered unsafe and we’re soft-deprecating them. They’re not documented anymore, but still continue to work.

We’re planning to expose more features for DOM manipulation in the stdlib API, so nobody has to extend the libxml2 integration themselves. Follow the progress in #15915.

We used to plug the garbage collector as memory allocator for libxml2. This had worked well so far, but started showing issue: In multi-threading, or with the newest release libxml 2.14 this integration causes segfaults when a GC cycle happens within a libxml function. In addition, the libxml2 distributed in macOS 15.4 is patched to remove the custom memory allocators API entirely, so support for it was broken.

Thanks, @ysbaddaden

Blocking behaviour of Socket and IO.pipe

The blocking parameter used in Socket constructors, IO.pipe and IO::Stapled.pipe changes from false to nil by default. Each event loop now decides to set the mode to blocking or not (#15804, #15823, #15925).

  • On UNIX systems, there is no immediate effect: the existing event loops (libevent, epoll and kqueue) keep using non-blocking mode.
  • On Windows, we don’t set the socket to non-blocking anymore. There really is no need for that with overlapped IO.

Future event loops (such as io_uring) may default to blocking mode.

Also, the blocking parameter is deprecated. It’s not necessary to configure in the constructor (#15924). It’s still possible to change the mode after creation with #blocking=.

Thanks, @ysbaddaden

SystemError.from_errno

The helper method SystemError.from_errno is now a macro. This ensures it promptly reads Errno.value from the previous lib call, without contamination from evaluating other arguments. The macro keeps the same signature as the class method, so we expect no friction (#15874).

Thanks, @straight-shoota

Execution Contexts and Multi-Threading

Execution contexts from RFC 0002 are still a preview feature, but getting more and more into shape.

With this release, we’re getting more stability. And there’s progress in related areas, such as improving multi-threaded support for stdlib libraries like XML. Work has begun on decoupling schedulers from threads which will make it easy to react to blocking lib calls without blocking other fibers in the execution context (#15871).

Check out ysbaddaden/sync for a preview of synchronization primitives to build concurrent-safe and parallel-safe data structures.

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

Thanks, @ysbaddaden

Windows Support

Windows support is going steady with a number of improvements in this release:

  • Support for Windows local device paths in Path (#15590)
  • Support for Windows system time zone transitions in all years (#15891)
  • Fixed IANA time zone names for Windows system time zones (#15914)
  • Improved stability ([#15820], [#15850]).

Thanks, @HertzDevil

Language

Macro expressions now support further expressions after an if expression: {% if ...; end; ... %} (#15917).

Thanks, @HertzDevil

Standard library

Experimental support for Struct.pre_initialize (#15896)

Thanks, @HertzDevil

Enum.from_value now raises ArgumentError instead of Exception (#15624). This should not break anything because it’s just a more specialized subclass.

Thanks, @HertzDevil

Time zone database

We improved the parser for TZif database files adding support for version 4 (#15825) and parsing POSIX TZ strings (#15863). POSIX TZ environment variable strings are also supported in the TZ environment variable (#15792).

Thanks, @HertzDevil

Scoped IPv6 addresses

Socket::IPAddress now has support for IPv6 scoped addresses from RFC4007 (#15263). Socket::IPAddress#zone_id returns the zone number and #link_local_interface the zone name.

require "socket"

addr = Socket::IPAddress.new("fe80::1111%1", 0) # => Socket::IPAddress([fe80::1111%1]:0)
addr.zone_id              # => 1
addr.link_local_interface # => "lo"

Thanks, @foxxx0

Ensuring string suffix and prefix

New convenience methods String#ensure_suffix and String#ensure_prefix for adding a specific prefix or suffix but only if not already there (#15782):

"foo".ensure_suffix("/")  # => "foo/"
"foo/".ensure_suffix("/") # => "foo/"

"Foo".ensure_prefix("::")   # => "::Foo"
"::Foo".ensure_prefix("::") # => "::Foo"

Thanks, @MatheusRich

Time.month_week_date

New method Time.month_week_date (#15620)

Time.month_week_date(2025, 7, 3, 3, location: Time::Location::UTC) # => 2025-07-16 00:00:00.0 UTC

Thanks, @HertzDevil

Helper methods for subclasses

We split StaticFileHandler#call into structured sub-components, so there are now a number of helper methods to override individual aspects in custom subclasses (#15678).

Thanks, @straight-shoota

Similarly, WebSocket now has explicit #do_ping and #do_close helper methods for easy overriding with custom behaviour (#15545).

Thanks, @luislavena

Compiler

The new CLI option --x86-asm-syntax configures emitting assembly code in Intel style (#15612).

Thanks, @HertzDevil

Stringification of several AST nodes has been improved: single line blocks (#15568), multiline named tuple literals (#15566), multiline calls (#15691), significant whitespace before a block body (#15692), MacroIf unless (#15919), elsif when stringifying If (#15918), newline in trailing expressions (#15614), multiline (boolean) expressions (#15709), Not as call receiver (#15801).

Thanks, @Blacksmoke16, @HertzDevil

Compiler tools

We have a new compiler tool, macro_code_coverage, which generate a code coverage report for macros (#15738).

Thanks, @Blacksmoke16

Infrastructure

The Crystal repo has adopted ameba for static code analysis and linting (#15875), as well as and typos for spell checking (#15873). With the help of these tools we have been able to improve code quality a lot, and continue doing so with continuous testing in CI.

Thanks, @straight-shoota

Dependencies

  • Support for LLVM 21 (#15771)
  • Allow LLVM_VERSION override inside Makefile (#15765)
  • Support for LibXML2 2.14 (#15899, #15906)

Thanks, @HertzDevil, @ysbaddaden


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