Crystal 1.17.0 is released!
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 insideMakefile
(#15765) - Support for LibXML2 2.14 (#15899, #15906)
Thanks, @HertzDevil, @ysbaddaden
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