Skip to content
GitHub Repository Forum RSS-Newsfeed

Crystal 1.18.0 is released!

Johannes Müller

We are announcing a new Crystal release 1.18.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 172 changes since 1.17.1 by 31 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. If you notice any unexpected issues, please let us know in the issue tracker or forum.

Execution contexts from RFC 0002 continue as a preview feature with opt-in with compiler flags -Dpreview_mt -Dexecution_context. It might move out of preview in the next release.

The default execution context is now parallel, but with maximum: 1 (#16136). So it starts with a single thread, but it can grow to more with Fiber::ExecutionContext::Parallel#resize (#15956).

There are also a number of performance improvements for schedulers and event loops (#15961, #16063) and we dropped the custom implementation of Fiber::ExecutionContext::Concurrent (#16135). It’s now based on the parallel implementation.

Fiber::ExecutionContext.default_workers_count has been improved (#16149) to respect the effective CPU count available to the process (#16148).

The preview of synchronization primitives in ysbaddaden/sync has seen some improvements as well.

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

Thanks, @ysbaddaden

Deprecation warnings are now available on types and aliases (#15962) as well as individual method parameters (#15999). Deprecated types only trigger warnings when they are actually used (e.g. calling a class method), not when they’re just part of a type restriction, for example. Deprecated parameters only trigger a warning when the particular parameter is used in a call. Calls without this parameter are unaffected.

@[Deprecated("Here may be dragons")]
class Foo
end

def foo(bar, @[Deprecated("Do not try this at home")] baz)
end

Thanks @ysbaddaden

The format of Time#inspect has been adjusted slightly to align with the Internet Extended Date/Time Format (IXDTF) defined in RFC 9557 (#16039).

Changes from the previous format:

  • Replace UTC location by Z offset
  • Skip zero nanoseconds entirely even when with_nanoseconds is true (see https://github.com/crystal-lang/crystal/pull/16039#discussion_r2250863021)
  • Remove whitespace between time and offset
  • Wrap location in square brackets to indicate an IXDTF time-zone suffix

Some examples show the differences:

a = Time.utc(2014, 1, 2, 3, 4, 5, nanosecond: 123_456_789)
b = Time.local(2014, 1, 2, 3, 4, 5, nanosecond: 123_456_789, location: Time::Location.load("Europe/Berlin"))
c = Time.local(2014, 1, 2, 3, 4, 5, nanosecond: 123_456_789, location: Time::Location.fixed(3600))

# Crystal 1.17:
a.inspect # => "2014-01-02 03:04:05.123456789 UTC"
b.inspect # => "2014-01-02 03:04:05.123456789 +01:00 Europe/Berlin"
c.inspect # => "-2014-01-02 03:04:05.123456789 +01:00"

# Crystal 1.18:
a.inspect # => "2014-01-02 03:04:05.123456789Z"
b.inspect # => "2014-01-02 03:04:05.123456789+01:00[Europe/Berlin]"
c.inspect # => "+2014-01-02 03:04:05.123456789+01:00"

Time::Location.local now follows symlink names, so that it resolves to the same timezone by name as Time::Location.load, even if /etc/localtime points to a zoneinfo database (#16002, #16022).

Time::Location.load? is a new, non-raising variant to .load (#16121).

Thanks, @straight-shoota

The local Windows system time zone now uses the canonical IANA name (#15967).

Thanks, @HertzDevil

YAML::Any now resolves YAML aliases (#15941).

Thanks, @willhbr

The output of JSON::Any#inspect got a wrapper to indicate the Any type and differentiate from the wrapped type (#15979). The equivalent for YAML::Any is still pending.

require "json"

JSON::Any.new(1).inspect # => "JSON::Any(1)"

Thanks, @jneen

Time::Location can now be used as a JSON object key (#15957).

Thanks, @Sija

The private constructors of *::Serializable moved into the macro included hook (#16147). They’re now defined on the including types which is more robust in terms of #initialize overload ordering. This also allows referring generic type variables inside converters.

Thanks, @HertzDevil

New methods:

Thanks, @HertzDevil, @straight-shoota, @devnote-dev, @ysbaddaden, @kojix2

Colorize now uses scoped ANSI reset codes which only reset the respective property, instead of resetting everything (#16052).

Thanks, @Blacksmoke16

StaticFileHandler preserves query params in redirects (#15789)

Thanks, @syeopite

StaticFileHandler returns 404 on file error (#16025, #16077)

Thanks, @straight-shoota

HTTP::Client runs the #before_request callback only once, even if a request is retried (#16064).

Thanks @straight-shoota

URI#host= wraps IPv6 addresses in brackets (#16164)

Thanks, @stakach

We added type restrictions to many API methods. This improves the documentation. This is a semi-automatic effort, assisted by a tool that automatically extracts typing information from the semantic analysis of a program (#15682).

MIME (#15834), Log (#15777), OAuth (#15687), JSON (#15840, #16142), Benchmark (#15688), Crypto (#15694), IO (#15698), HTTP (#15710), Big* (#15689), Process (#16031).

Thanks, @Vici37

Multi-line strings containing source code in compiler specs have had two different formats: Some of them were using regular string literals, others used heredocs. The latter was usually preferred in new code additions because line numbers and indents are more sensible. And with an appropriate heredoc identifier to denote the language, we even get nested syntax highlighting. In this release we’ve converted all compiler specs to use heredocs (#11291).

Thanks, @HertzDevil

With the help of ameba, we’ve enabled and applied a couple more linter rules: Style/UnlessElse (#16010), Style/RedundantBegin (#16011), and Lint/UselessAssign (#16014).

Thanks, @straight-shoota

New methods:

Thanks, @jneen, @Sija

Empty NamedTupleLiteral and TupleLiteral expand to NamedTuple.new and Tuple.new, instead of {} (#16108).

Thanks, @spuun

The compiler can dump type information to a JSON file when the environment variable CRYSTAL_DUMP_TYPE_INFO is set (#16027).

Thanks, @HertzDevil

Resolve types when guessing return type from class method overloads (#16118).

In the following snippet, the compiler is able to infer @foo’s type to be Foo, since both overloads of Foo.foo have a return type of Foo:

class Foo
  def self.foo(x : Int32) : Foo
    new
  end

  def self.foo(x : String) : Foo
    new
  end
end

class Bar
  def initialize
    @foo = Foo.foo(1)
  end
end

Bar.new

Thanks, @HertzDevil

Guess instance variable types from global method calls (#16119).

This allows the following to compile:

def foo(x : Int32) : Float64
  # ...
end

class Bar
  def initialize
    @foo = ::foo(1) # okay, `@foo` has the type `Float64`
  end
end

The call needs to be global; other than that, type guessing for top-level methods has the same limitations as for class methods.

Thanks, @HertzDevil

Hash literals are evaluated from left to right, fixing a regression from 1.6.0 (#16124).

Thanks, @HertzDevil

Temporary variables are now grouped by file name in order to increase the chance of reusing previous macro expansions (#16122).

Thanks, @HertzDevil

  • Fully exit the process on exit! from REPL (#16171)
  • Fixed interpreter hanging on the signal pipe (#16167)

Thanks @jneen, @straight-shoota

Thanks @straight-shoota, @HertzDevil

  • Support for LLVM 21.1 and 22.0 (#16062)
  • Update Unicode to 17.0.0 (#16160)

Thanks @HertzDevil, @ysbaddaden

Thanks @ysbaddaden, @Blacksmoke16, @HertzDevil, @straight-shoota


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