diff --git a/_releases/2025-07-16-1.17.0-released.md b/_releases/2025-07-16-1.17.0-released.md new file mode 100644 index 00000000..3f7cd31d --- /dev/null +++ b/_releases/2025-07-16-1.17.0-released.md @@ -0,0 +1,421 @@ +--- +title: Crystal 1.17.0 is released! +version: 1.17.0 +date: 2025-07-16 +author: straight-shoota +--- + +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](https://github.com/crystal-lang/crystal/releases/tag/1.17.0) and our +official distribution channels. See +[crystal-lang.org/install](https://crystal-lang.org/install/) for installation +instructions. + +## Stats + +This release includes [187 changes since +1.16.3](https://github.com/crystal-lang/crystal/pulls?q=is%3Apr+milestone%3A1.17.0) +by 19 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](https://github.com/crystal-lang/crystal/releases/tag/1.17.0). + +### 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`](https://no-color.org/) environment variable was 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 [`Colorize.enabled=`] explicitly: + +```cr +Colorize.enabled = !ENV["NO_COLOR"]?.try(&.empty?.!) +``` + +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]). + +```cr +require "colorize" + +# Prints "world" in color in a shell, but not when piped to a file: +puts "Hello #{"world".colorize(:red)}" + +# This resets to 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]_ + +[`Colorize.on_tty_only!`]: https://crystal-lang.org/api/1.17.0/Colorize.html#on_tty_only!:Bool-class-method +[`Colorize.enabled=`]: https://crystal-lang.org/api/1.17.0/Colorize.html#enabled%3D%28enabled%3ABool%29-class-method +[`Colorize.default_enabled?`]: https://crystal-lang.org/api/1.17.0/Colorize.html#default_enabled%3F%28stdout%3AIO%2Cstderr%3AIO%3Dstdout%29%3ABool-class-method +[#15881]: https://github.com/crystal-lang/crystal/issues/15881 +[#15912]: https://github.com/crystal-lang/crystal/issues/15912 + +#### 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. As a first step, +we're introducing `XML::Document` as a subclass of `XML::Node` for document +nodes. This is not expected to break anything. +Follow the progress in [#15915]. + +We used to plug the garbage collector as memory allocator for `libxml2`. +This worked well so far, but with the newest release libxml 2.14 it causes segfaults when using +multi-threading and a GC cycle happens while executing 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. With this change, it is now properly supported. + +_Thanks, [@ysbaddaden]_ + +[#15906]: https://github.com/crystal-lang/crystal/issues/15906 +[#15915]: https://github.com/crystal-lang/crystal/issues/15915 + +#### Blocking behaviour of file descriptors + +The `blocking` parameter used in [`Socket`] and [`File`] 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], +[#15930], [#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 file descriptor to non-blocking anymore. There + really is no need for that with overlapped IO. Instead, file IO is now fully + async 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]_ + +[`Socket`]: https://crystal-lang.org/api/1.17.0/Socket.html +[`File`]: https://crystal-lang.org/api/1.17.0/File.html +[`IO.pipe`]: https://crystal-lang.org/api/1.17.0/IO.html#pipe(read_blocking=nil,write_blocking=nil):Tuple(IO::FileDescriptor,IO::FileDescriptor)-class-method +[`IO::Stapled.pipe`]: https://crystal-lang.org/api/1.17.0/IO/Stapled.html#pipe(read_blocking:Bool=false,write_blocking:Bool=false,&)-class-method +[`#blocking=`]: https://crystal-lang.org/api/1.17.0/Socket.html#blocking=(value)-instance-method +[#15804]: https://github.com/crystal-lang/crystal/issues/15804 +[#15930]: https://github.com/crystal-lang/crystal/issues/15930 +[#15823]: https://github.com/crystal-lang/crystal/issues/15823 +[#15925]: https://github.com/crystal-lang/crystal/issues/15925 +[#15924]: https://github.com/crystal-lang/crystal/issues/15924 + +#### `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]_ + +[`SystemError.from_errno`]: https://crystal-lang.org/api/1.17.0/IO/Error.html#from_errno(message,**opts)-macro +[`Errno.value`]: https://crystal-lang.org/api/1.17.0/Errno.html#value%3Aself-class-method +[#15874]: https://github.com/crystal-lang/crystal/issues/15874 + +### Execution Contexts and Multi-Threading + +Execution contexts from [RFC 0002] are still a preview feature, but getting more +and more into shape. + +In light of upcoming changes to the relation between schedulers and threads, we +want to make it clear that execution contexts are more about the intent ("how +fibers run") and threads are only a technical means for achieving parallelism. +As a result, we're renaming the execution context implementations ([#15936]): + +- [`Fiber::ExecutionContext::SingleThreaded`] to [`Fiber::ExecutionContext::Concurrent`] +- [`Fiber::ExecutionContext::MultiThreaded`] to [`Fiber::ExecutionContext::Parallel`] + +`Fiber::ExecutionContext::Isolated` keeps its name, it does not have a +connection to threads. + +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`][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]_ + +[RFC 0002]: https://github.com/crystal-lang/rfcs/pull/2 +[ongoing project to improve multi-threading support]: /2024/02/09/84codes-manas-mt/ +[ysbaddaden/sync]: https://github.com/ysbaddaden/sync/ + +[`Fiber::ExecutionContext::SingleThreaded`]: https://crystal-lang.org/api/1.17.0/Fiber/ExecutionContext/SingleThreaded.html +[`Fiber::ExecutionContext::Concurrent`]: https://crystal-lang.org/api/1.17.0/Fiber/ExecutionContext/Concurrent.html +[`Fiber::ExecutionContext::MultiThreaded`]: https://crystal-lang.org/api/1.17.0/Fiber/ExecutionContext/MultiThreaded.html +[`Fiber::ExecutionContext::Parallel`]: https://crystal-lang.org/api/1.17.0/Fiber/ExecutionContext/Parallel.html +[#15936]: https://github.com/crystal-lang/crystal/issues/15936 +[#15871]: https://github.com/crystal-lang/crystal/issues/15871 + +### 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]_ + +[#15590]: https://github.com/crystal-lang/crystal/issues/15590 +[#15891]: https://github.com/crystal-lang/crystal/issues/15891 +[#15914]: https://github.com/crystal-lang/crystal/issues/15914 +[#15820]: https://github.com/crystal-lang/crystal/issues/15820 +[#15850]: https://github.com/crystal-lang/crystal/issues/15850 + +### Language + +{% raw %} +Macro expressions now support further expressions after an `if` expression: `{% +if ...; end; ... %}` ([#15917]). +{% endraw %} + +_Thanks, [@HertzDevil]_ + +[#15917]: https://github.com/crystal-lang/crystal/issues/15917 + +### 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]_ + +[`Struct.pre_initialize`]: https://crystal-lang.org/api/1.17.0/Struct.html#pre_initialize(address:Pointer):Nil-class-method +[`Enum.from_value`]: https://crystal-lang.org/api/1.17.0/Enum.html#from_value(value:Int):self-class-method +[#15896]: https://github.com/crystal-lang/crystal/issues/15896 +[#15624]: https://github.com/crystal-lang/crystal/issues/15624 + +#### 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]_ + +[#15825]: https://github.com/crystal-lang/crystal/issues/15825 +[#15863]: https://github.com/crystal-lang/crystal/issues/15863 +[#15792]: https://github.com/crystal-lang/crystal/issues/15792 + +#### 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`][`Socket::IPAddress#link_local_interface`] the zone +name. + +```cr +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]_ + +[RFC4007]: https://datatracker.ietf.org/doc/html/rfc4007 +[`Socket::IPAddress#zone_id`]: https://crystal-lang.org/api/1.17.0/Socket/IPAddress.html#zone_id:Int32-instance-method +[`Socket::IPAddress#link_local_interface`]: https://crystal-lang.org/api/1.17.0/Socket/IPAddress.html#link_local_interface:String|Nil-instance-method +[#15263]: https://github.com/crystal-lang/crystal/issues/15263 + +#### 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]): + +```cr +"foo".ensure_suffix("/") # => "foo/" +"foo/".ensure_suffix("/") # => "foo/" + +"Foo".ensure_prefix("::") # => "::Foo" +"::Foo".ensure_prefix("::") # => "::Foo" +``` + +_Thanks, [@MatheusRich]_ + +[`String#ensure_suffix`]: https://crystal-lang.org/api/1.17.0/String.html#ensure_suffix%28suffix%3AString%7CChar%29%3Aself-instance-method +[`String#ensure_prefix`]: https://crystal-lang.org/api/1.17.0/String.html#ensure_prefix(prefix:String|Char):self-instance-method +[#15782]: https://github.com/crystal-lang/crystal/issues/15782 + +#### `Time.month_week_date` + +New method [`Time.month_week_date`] ([#15620]) + +```cr +Time.month_week_date(2025, 7, 3, 3, location: Time::Location::UTC) # => 2025-07-16 00:00:00.0 UTC +``` + +_Thanks, [@HertzDevil]_ + +[`Time.month_week_date`]: https://crystal-lang.org/api/1.17.0/Time.html#month_week_date(year:Int32,month:Int32,week:Int32,day_of_week:Int32|DayOfWeek,hour:Int32=0,minute:Int32=0,second:Int32=0,*,nanosecond:Int32=0,location:Location=Location.local):self-class-method +[#15620]: https://github.com/crystal-lang/crystal/issues/15620 + +#### 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]_ + +[`StaticFileHandler#call`]: https://crystal-lang.org/api/1.17.0/HTTP/StaticFileHandler.html#call(context):Nil-instance-method +[`WebSocket`]: https://crystal-lang.org/api/1.17.0/HTTP/WebSocket.html +[#15678]: https://github.com/crystal-lang/crystal/issues/15678 +[#15545]: https://github.com/crystal-lang/crystal/issues/15545 + +### 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]_ + +[#15612]: https://github.com/crystal-lang/crystal/issues/15612 +[#15568]: https://github.com/crystal-lang/crystal/issues/15568 +[#15566]: https://github.com/crystal-lang/crystal/issues/15566 +[#15691]: https://github.com/crystal-lang/crystal/issues/15691 +[#15692]: https://github.com/crystal-lang/crystal/issues/15692 +[#15919]: https://github.com/crystal-lang/crystal/issues/15919 +[#15918]: https://github.com/crystal-lang/crystal/issues/15918 +[#15614]: https://github.com/crystal-lang/crystal/issues/15614 +[#15709]: https://github.com/crystal-lang/crystal/issues/15709 +[#15801]: https://github.com/crystal-lang/crystal/issues/15801 + +### Compiler tools + +We have a new compiler tool, `macro_code_coverage`, which generate a code +coverage report for macros ([#15738]). + +_Thanks, [@Blacksmoke16]_ + +[#15738]: https://github.com/crystal-lang/crystal/issues/15738 + +### 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]_ + +[ameba]: https://crystal-ameba.github.io/ +[typos]: https://github.com/crate-ci/typos + +[#15875]: https://github.com/crystal-lang/crystal/issues/15875 +[#15873]: https://github.com/crystal-lang/crystal/issues/15873 + +### Dependencies + +- Support for LLVM 21 ([#15771]) +- Allow `LLVM_VERSION` override inside `Makefile` ([#15765]) +- Support for LibXML2 2.14 ([#15899], [#15906]) + +_Thanks, [@HertzDevil], [@ysbaddaden]_ + +[#15771]: https://github.com/crystal-lang/crystal/issues/15771 +[#15765]: https://github.com/crystal-lang/crystal/issues/15765 +[#15899]: https://github.com/crystal-lang/crystal/issues/15899 + +### Deprecations + +- [`Fiber::ExecutionContext::SingleThreaded`] and + [`Fiber::ExecutionContext::MultiThreaded`] are deprecated and replaced by + [`Fiber::ExecutionContext::Concurrent`] and + [`Fiber::ExecutionContext::Parallel`], respectively. The old names still work + as aliases. + Please note that the compiler currently does not emit deprecation warnings for + aliases. This will be available in 1.18.0. + +--- + +> **THANKS:** We have been able to do all of this thanks to the continued +> support of [84codes](https://www.84codes.com/) and every other +> [sponsor](/sponsors). To maintain and increase the development pace, donations +> and sponsorships are essential. +> [OpenCollective](https://opencollective.com/crystal-lang) is available for +> that. +> +> Reach out to [crystal@manas.tech](mailto:crystal@manas.tech) if you’d like to +> become a direct sponsor or find other ways to support Crystal. We thank you in +> advance! + +[issue tracker]: https://github.com/crystal-lang/crystal/issues +[forum]: https://forum.crystal-lang.org/ +[84codes]: https://www.84codes.com/ +[@Blacksmoke16]: https://github.com/Blacksmoke16 +[@HertzDevil]: https://github.com/HertzDevil +[@MatheusRich]: https://github.com/MatheusRich +[@foxxx0]: https://github.com/foxxx0 +[@luislavena]: https://github.com/luislavena +[@straight-shoota]: https://github.com/straight-shoota +[@ysbaddaden]: https://github.com/ysbaddaden