Crystal 1.18.0 is released!
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.
Stats
Section titled StatsThis 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! ❤️
Changes
Section titled ChangesBelow 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
Section titled Execution contextsExecution 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
Section titled Deprecation warningsDeprecation 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
Time
Section titled TimeTime#inspect
  
    
    Section titled Time#inspect
  
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 
UTClocation byZoffset - Skip zero nanoseconds entirely even when 
with_nanosecondsis 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
  
    
    Section titled Time::Location
  
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
Serialization
Section titled SerializationYAML::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
Standard library
Section titled Standard libraryNew methods:
Set#select!and#reject!(#16060)File.readlink?(#16004)SemanticVersion.valid?&SemanticVersion.parse?(#15051)Socket.set_blockingandIO::FileDescriptor#set_blocking(#16033, #16129)OptionParser#summary_widthandOptionParser#summary_indent(#15326)
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
Networking
Section titled NetworkingStaticFileHandler 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
Type restrictions
Section titled Type restrictionsWe 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
Code Cleanup
Section titled Code CleanupMulti-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
Macros
Section titled MacrosNew methods:
Empty NamedTupleLiteral and TupleLiteral expand to NamedTuple.new and
Tuple.new, instead of {} (#16108).
Thanks, @spuun
Compiler
Section titled CompilerThe 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
Interpreter
Section titled Interpreter- Fully exit the process on 
exit!from REPL (#16171) - Fixed interpreter hanging on the signal pipe (#16167)
 
Thanks @jneen, @straight-shoota
Infra
Section titled InfraREUSE.tomldescribes which licenses apply to which parts of the code base, following the Reuse Software Specification (#15992)- Default to LLVM 16 in 
shell.nix(#16023) 
Thanks @straight-shoota, @HertzDevil
Dependencies
Section titled DependenciesThanks @HertzDevil, @ysbaddaden
Deprecations
Section titled DeprecationsSocket#blockingandIO::FileDescriptor#blockingproperties (#16033, #16129)Atomic::Flag(#15805)- The 
blockingparameter ofFile.new,Socket.newandIO::FileDescriptor.newconstructors (#16034, #16047) Float::Printer::IEEE(#16050)Process::Status#exit_signal(#16003)
Thanks @ysbaddaden, @Blacksmoke16, @HertzDevil, @straight-shoota
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