---
title: Crystal 1.14.0 is released!
version: 1.14.0
date: 2024-10-09
author: straight-shoota
---
We are announcing a new Crystal release with several new features and bug fixes.

Pre-built packages are available on [GitHub Releases](https://github.com/crystal-lang/crystal/releases/tag/1.14.0)
and our official distribution channels.
See [crystal-lang.org/install](https://crystal-lang.org/install/) for
installation instructions.

## Stats

This release includes [134 changes since 1.13.3](https://github.com/crystal-lang/crystal/pulls?q=is%3Apr+milestone%3A1.14.0)
by 13 contributors. We thank all the contributors for all the effort put into
improving the language! ❤️

## Advances in multi-threading support

The [project to improve multi-threading support](/2024/02/09/84codes-manas-mt) with the help of [84codes] is still ongoing.
This release doesn't include any big changes. There are a couple of smaller concurrency improvements, though.

We expect to roll out a major performance upgrade to the event loop for the 1.15 development cycle.
See [New Event Loop (UNIX): call for reviews & tests](https://forum.crystal-lang.org/t/new-event-loop-unix-call-for-reviews-tests/7207)
for a heads up.

The upcoming execution contexts API from [RFC 2](https://github.com/crystal-lang/rfcs/pull/2) is available as a standalone shard for testing:
[`ysbaddaden/execution_context`](https://github.com/ysbaddaden/execution_context).

## 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.14.0).

### Breaking

⚠️ [`Slice#[start, count]`][slice_accessor] now accepts a negative index for `start`, like similar methods already do.
This would break existing code that depends on the current behaviour that a negative start index raises `IndexError` ([#14778]).

[slice_accessor]: https://crystal-lang.org/api/1.14.0/Slice.html#%5B%5D%28start%3AInt%2Ccount%3AInt%29%3ASlice%28T%29-instance-method
[#14778]: https://github.com/crystal-lang/crystal/pull/14778

⚠️ Finalizers for [`Socket`] and [`IO::FileDescriptor`] do no longer flush.
We realized that flushing is too heavy for a finalizer, as it might involve the event loop and even memory allocations which must be strictly avoided in a finalizer.
Be sure to always flush before letting a stream go out of scope, ideally with an explicit `#close` ([#14882]).

[`Socket`]: https://crystal-lang.org/api/1.14.0/Socket.html
[`IO::FileDescriptor`]: https://crystal-lang.org/api/1.14.0/IO/FileDescriptor.html
[#14882]: https://github.com/crystal-lang/crystal/pull/14882

⚠️ [`XML::Error.errors`] had been deprecated since [1.7.0](/_releases/2023-01-09-1.7.0-released.md), but continued to work.
This unconditioned availability causes a serious memory leak, which cannot be fixed without disabling `XML::Error.errors`.
In order to make this obvious, calling the method causes a compile time error now. ([#14936]).

[`XML::Error.errors`]: https://crystal-lang.org/api/1.14.0/XML/Error.html#errors:Array(XML::Error)|Nil-class-method
[#14936]: https://github.com/crystal-lang/crystal/pull/14936

⚠️ `Hash::Entry` has been removed from public API docs. It was never intended to be a public type. ([#14881]).

[#14881]: https://github.com/crystal-lang/crystal/pull/14881

*Thanks [@ysbaddaden], [@straight-shoota] and [@Blacksmoke16]*

### Language features

Allow `^` in constant numeric expressions ([#14951]). This operator was oddly missing even though `|` and `&` were already supported.

*Thanks [@HertzDevil]*

[#14951]: https://github.com/crystal-lang/crystal/pull/14951

`HashLiteral` and `NamedTupleLiteral` respond to `#has_key?`, just like their regular counterparts `Hash` and `NamedTuple` ([#14890]).

*Thanks [@kamil-gwozdz]*

[#14890]: https://github.com/crystal-lang/crystal/pull/14890

### Standard library

The [`WaitGroup`] concurrency primitive gains some convenience methods,
[`WaitGroup.wait`] and [`WaitGroup#spawn`] ([#14837]).

```crystal
require "wait_group"
WaitGroup.wait do |wg|
  10.times do
    wg.spawn do
      sleep 5.seconds
    end
  end
end
```

*Thanks [@jgaskins]*

[`WaitGroup`]: https://crystal-lang.org/api/1.14.0/WaitGroup.html
[`WaitGroup.wait`]: https://crystal-lang.org/api/1.14.0/WaitGroup.html#wait%3ANil-instance-method
[`WaitGroup#spawn`]: https://crystal-lang.org/api/1.14.0/WaitGroup.html#spawn%28%26block%29%3AFiber-instance-method
[#14837]: https://github.com/crystal-lang/crystal/pull/14837

There are two new methods for working with slices: [`Slice#same?`] checks
if two slices point to the same memory ([#14728]).
And [`Pointer::Appender#to_slice`] ([#14874]) makes it easy to create a slice
containing the items from an appender.

[`Slice#same?`]: https://crystal-lang.org/api/1.14.0/Slice.html#same?(other:self):Bool-instance-method
[`Pointer::Appender#to_slice`]: https://crystal-lang.org/api/1.14.0/Pointer/Appender.html#to_slice:Slice(T)-instance-method
[#14728]: https://github.com/crystal-lang/crystal/pull/14728
[#14874]: https://github.com/crystal-lang/crystal/pull/14874

*Thanks [@straight-shoota]*

A minor fix turning an eager class getter into a lazy one avoids linking `libpcre`
for programs that do not use `Regex` ([#14891]).

*Thanks [@kojix2]*

[#14891]: https://github.com/crystal-lang/crystal/pull/14891

### Windows

Windows support is making good progress.

The interpreter runs on Windows ([#14964]). There is still a limitation:
networking does not work due to [#12495].

And the compiler can now target ARM64 Windows, i.e. `aarch64-windows-msvc`.
It's not 100% polished, but looking pretty well. Read [#14911] for details on how to test it out.
Currently we still need to cross-compile because the compiler itself does not run on ARM64 Windows yet.

Starting with this release, DNS requests resolve asynchronously on Windows ([#14979]).
It's actually the first platform to support that.

There are also a number of improvements regarding non-blocking IO:

- Support non-blocking `File#read` and `#write` ([#14940]), `File#read_at` ([#14958]), `Process.run` standard streams ([#14941]), `IO::FileDescriptor#flock_*` ([#14943]).
- Emulate non-blocking `STDIN` console ([#14947]).
- Open non-blocking regular files as overlapped ([#14921]).

And we add implementations of `System::User` ([#14933]) and `System::Group` on Windows ([#14945]).

*Thanks [@HertzDevil]*

[#14911]: https://github.com/crystal-lang/crystal/pull/14911
[#14921]: https://github.com/crystal-lang/crystal/pull/14921
[#14940]: https://github.com/crystal-lang/crystal/pull/14940
[#14958]: https://github.com/crystal-lang/crystal/pull/14958
[#14941]: https://github.com/crystal-lang/crystal/pull/14941
[#14943]: https://github.com/crystal-lang/crystal/pull/14943
[#14947]: https://github.com/crystal-lang/crystal/pull/14947
[#14979]: https://github.com/crystal-lang/crystal/pull/14979
[#14933]: https://github.com/crystal-lang/crystal/pull/14933
[#14945]: https://github.com/crystal-lang/crystal/pull/14945
[#14964]: https://github.com/crystal-lang/crystal/pull/14964
[#12495]: https://github.com/crystal-lang/crystal/issues/12495

### `URI::Params`

[`URI::Params::Serializable`] is a new serialization API which works similar to
the JSON and YAML variants, but for the URI query parameters format ([#14684]).

```crystal
require "uri/params/serializable"

record Applicant,
  first_name : String
  last_name : String
  qualities : Array(String) do
  include URI::Params::Serializable
end

applicant = Applicant.from_www_form "first_name=John&last_name=Doe&qualities=kind&qualities=smart"
applicant # => Applicant(@first_name="John", @last_name="Doe", @qualities=["kind", "smart"])
applicant.to_www_form # => "first_name=John&last_name=Doe&qualities=kind&qualities=smart"
```

[`URI::Params::Serializable`]: https://crystal-lang.org/api/1.14.0/URI/Params/Serializable.html

*Thanks [@Blacksmoke16]*

In a related matter, `URI` is now applicable as a key in JSON objects via [`URI.from_json_object_key?`] ([#14834]).

*Thanks [@nobodywasishere]*

[`URI.from_json_object_key?`]: https://crystal-lang.org/api/1.14.0/URI.html#from_json_object_key%3F%28key%3AString%29%3AURI%7CNil-class-method

[#14834]: https://github.com/crystal-lang/crystal/pull/14834
[#14684]: https://github.com/crystal-lang/crystal/pull/14684

### Compiler tools

The compiler binary can now execute external programs as subcommands:
`crystal foo` tries to run `crystal-foo` if `foo` is not an internal command.
This allows us to split the compiler binary into separate executables which helps
improve iteration speed ([#14953]).

*Thanks [@bcardiff]*

[#14953]: https://github.com/crystal-lang/crystal/pull/14953

### Performance

This release includes some minor performance improvements, particularly in the
compiler ([#14748], [#14992], [#15002]).

*Thanks [@ysbaddaden], [@HertzDevil], [@ggiraldez]*

[#15002]: https://github.com/crystal-lang/crystal/pull/15002
[#14992]: https://github.com/crystal-lang/crystal/pull/14992
[#14748]: https://github.com/crystal-lang/crystal/pull/14748

### Dependency Updates

- `LibCrypto` bindings now support LibreSSL 3.5+ ([#14872]).
- Support for Unicode 16.0.0 ([#14997]).
- Support for LLVM 19.1 ([#14842])

*Thanks [@straight-shoota], [@HertzDevil]*

[#14842]: https://github.com/crystal-lang/crystal/pull/14842
[#14872]: https://github.com/crystal-lang/crystal/pull/14872
[#14997]: https://github.com/crystal-lang/crystal/pull/14997

## Deprecations

[`Pointer.new(Int)`] was deprecated in favour of `Pointer.new(UInt64)` ([#14875]).
Deprecation warnings for argument type that autocast to `UInt64` can be ignored
or disabled by explicitly casting to `UInt64`.

*Thanks [@straight-shoota]*

[`Pointer.new(Int)`]: https://crystal-lang.org/api/1.14.0/Pointer.html#new(address:Int)-class-method
[#14875]: https://github.com/crystal-lang/crystal/pull/14875

We updated a couple of APIs that receive a time span argument.
[`Benchmark.ips`] ([#14805]) and [`::sleep`] ([#14962]) now explicitly require
[`Time::Span`]. The overloads with `Number` are deprecated.
You can convert bare numbers with [`Int#seconds`] to use the valid overload.

*Thanks [@HertzDevil]*

[#14805]: https://github.com/crystal-lang/crystal/pull/14805
[#14962]: https://github.com/crystal-lang/crystal/pull/14962
[`Benchmark.ips`]: https://crystal-lang.org/api/1.14.0/Benchmark.html#ips%28calculation%3ATime%3A%3ASpan%3D5.seconds%2Cwarmup%3ATime%3A%3ASpan%3D2.seconds%2Cinteractive%3ABool%3DSTDOUT.tty%3F%2C%26%29-instance-method
[`::sleep`]: https://crystal-lang.org/api/1.14.0/toplevel.html#sleep%28time%3ATime%3A%3ASpan%29%3ANil-class-method
[`Time::Span`]: https://crystal-lang.org/api/1.14.0/Time/Span.html
[`Int#seconds`]: https://crystal-lang.org/api/1.14.0/Int.html#seconds:Time::Span-instance-method
---

> **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!

[@bcardiff]: https://github.com/bcardiff
[@Blacksmoke16]: https://github.com/Blacksmoke16
[@ggiraldez]: https://github.com/ggiraldez
[@HertzDevil]: https://github.com/HertzDevil
[@jgaskins]: https://github.com/jgaskins
[@kamil-gwozdz]: https://github.com/kamil-gwozdz
[@kojix2]: https://github.com/kojix2
[@nobodywasishere]: https://github.com/nobodywasishere
[@straight-shoota]: https://github.com/straight-shoota
[@ysbaddaden]: https://github.com/ysbaddaden
[84codes]: https://www.84codes.com/
