Skip to content
GitHub Repository Forum RSS-Newsfeed

Crystal 0.34.0 released!

Brian J. Cardiff

Crystal 0.34.0 has been released!

Get excited because this release comes with some of the final touches to the language before 1.0: a better exception hierarchy to replace Errno, a new logging module, cleanups and fixes aiming for a better, more stable release, one that should make us all proud.

There are 183 commits since 0.33.0 by 26 contributors.

Let’s get right into some of the highlights in this release. But don’t miss out on the rest of the release changelog which has a lot of valuable information.

Language changes

Exhaustive Case

From now on a case expression will no longer have an implicit else nil. This is useful to enable an exhaustive check across the when branches within the case. If you are doing case exp over a union or an enum value, and you are missing a when to cover some type or value, the compiler will let you know. Unless you have an else at the end of the case.

Note: In this version, when the case does not cover all the possibilities, a warning is generated, and the else nil is implicitly added. In the next version it will produce a compile-time error and the implicit else nil will be gone.

The following snippet complains about the missing when Char

a = 1 || 'x' || "foo"
case a
when Int32
  # ...
when String
  # ...

And the following snippet complains about the missing when West

enum Direction
  North; South; East; West

d = Direction::North
case d
when .north?, .south?
  # ...
when .east?
  # ...

The only case that will still have an implicit else nil is when there is no expression and only a list of when statements. This construction is equivalent to multiple if/elseif where there is an implicit else nil also.

x = 1
y = 2
when x.even?  # if x.even?
  # ...
when y >= 10  # elsif y >= 0
  # ...
end           # end

Read more at #8424.

Procs subtyping

While dealing with Procs and callbacks it is common to not use the return value. In Crystal, that usually means returning nil. In regular methods you can specify the return type : Nil to ignore the value of the last expression.

The counterpart in Procs is harder because there usually is no type annotation for the return type.

For ease of use, we make it that any Proc(T) should be able to be used as a Proc(Nil). That is, ignoring the return value in runtime. So, for those that like formality, Proc(T) < Proc(Nil) is a valid subtyping rule now.

There was a previous attempt to achieve something similar, but in this version, a better handling of that affair was implemented. Read more at #8970.


The disable_overflow compiler flag is dropped. This means that the usual arithmetic operators will always have the overflow check. Use &+ and others to skip overflow checks. Read more at #8772.

The CRYSTAL_OPTS environment variable can now be used to inject compiler options and flags implicitly. This is useful, for example, when the compiler is used in post_install steps of shards and you want to enforce --error-on-warnings. Read more at #8900.

LLVM 10 has just been released and we added support for it. Read more at #8940.

The codegen for Windows has been improved to work without --single-module. Read more at #8978.


A new version of Shards (0.10.0) has been released. Until now you probably have been using Shards 0.8.1 which lacks some features. Shards 0.9.0 polished many use cases, but it uses a SAT solver, which doesn’t scale. For Shards 0.10.0 we created crystal-molinillo a port of the dependency resolution algorithm used by Bundler and CocoaPods.

You can read the rest of the updates in the release changelog.

We will be eagerly waiting for feedback from you on Shards to polish it before 1.0.

Standard library

Errno no more

Having as much as possible portable code is part of the goal of the std-lib. One of the areas that were in need of polishing was how Errno and WinError were handled. The Errno and WinError exceptions are now gone, and were replaced by a new hierarchy of exceptions. Unfortunately, there is no easy way to make a smooth transition here with deprecation warnings. The IO::Timeout exception was renamed to IO::TimeoutError to match the new hierarchy:

  • Exception
    • RuntimeError
    • IO::Error
      • IO::TimeoutError (inherits IO::Error)
      • File::Error (inherits IO::Error)
        • File::NotFoundError
        • File::AccessDeniedError
        • File::AlreadyExistsError
      • Socket::Error (inherits IO::Error)
        • Socket::ConnectError
        • Socket::BindError

So, you can now use these new types to catch specific errors instead of checking Errno values. We included the most used errors as classes. If there is no specific class, the base File::Error or Socket::Error will be raised with a meaningful description.

The Errno or WinError underlying value is still present if you need it, via the SystemError module included in this new hierarchy. But it is better if you avoid using it.

Read more at #8885.


The former Logger module is deprecated and will be removed soon. Its replacement is the Log module: it’s shorter, more flexible and convenient.

You can use the top-level Log constant to emit log entries, or you can declare one inside your module or class. This allows the entries to be emitted from a source.

Each source will be configured to send the entries to different backends depending on the severity level. If you initialize the logging with Log.setup_from_env you will be able to filter the level and the sources using the CRYSTAL_LOG_LEVEL and CRYSTAL_LOG_SOURCES environment variables.

# file
require "log"


class MyApp
  Log = ::Log.for(self)

  def run
	Log.debug { "the app is running" } # log from myapp source
end { "finished" } # log from the top-level source

If you want to log see all the log entries of the app above, you will need to set both environment variables, since their default values are CRYSTAL_LOG_LEVEL=INFO CRYSTAL_LOG_SOURCES="" (only top-level).

D, [2020-03-30T21:54:50.079554000Z #26206]   DEBUG -- app:my_app: the app is running
I, [2020-03-30T21:54:50.079624000Z #26206]    INFO -- app:: finished

Read more at #8847 and check the docs for how you can define your own backends and use more advanced features of this module.

Top level cleanup

As we prepare for 1.0, we wanted to iterate and clean up some of the top-level of the std-lib and prelude. That is the reason behind many deprecations that involved part of Colorize in #8892, Iconv in #8890, DL #8882.

Some modules were moved out of the top-level: Adler32 and CRC32 are inside Digest #8881, and AtExitHandlers inside Crystal #8883.

There might be some more cleanups/renames before 1.0 to avoid wanting some trivial early breaking-changes.


On the performance corner of this release, when using Array#fill for writing all zero values, it will now use memset for the entire underlying buffer, instead of iterating every position. Read more at #8903.


There is a small breaking change in YAML in order to align the API of all builders. with block was renamed to in #8896.

When using the different format builders, IO#flush will be called to ensure all the content will get through in case you are not closing properly the destination file. This applies to CSV, INI, JSON, XML, and YAML builders. Read more at #8876.


It’s time for more breaking-changes in favor of less error-prone code. The Time::Span initialization API will use mandatory named arguments, like minutes: 2, seconds: 3. Read more at #8257.


When closing a File or Socket the internal fd is set to -1 to force an invalid file descriptor and avoid mixing fd from different IOs. In single-thread, this was never an issue, but on multi-thread, as usual, issues like this one can cause big headaches. Read more at #8873.

The IO::Buffered#flush_on_newline is back. And its default value will be helpful for building CLI tools and pipe them into other commands. Read more at #8935.


The WebSocket support was lacking the proper handling of close code. In order to implement them, a breaking-change on the server-side and in client-side parts was needed. Read more at #8975 and #8981.


The windows support is moving forward while enabling more specs, and more contributors are jumping into the adventure. Check out #8683 and #8822, #8885, #8958.


The crystal init tool got some polishing. The name of the shard is validated with respect shards spec and it can be inferred from the directory. Read more at #8737.

The crystal docs tool will now show warnings. In previous releases we switched to :ditto: and :nodoc: as magic comments. But we missed showing you the warnings in case you forget to add the colons. Read more at #8880.

Next steps

Please update your Crystal and report any issues. We will keep moving forward and start the development focusing on 0.35. There won’t be many more 0.x releases. We are getting super close to 1.0!.

Again, we will be eagerly waiting for feedback from you on Shards to polish it before 1.0.

All deprecation warnings will soon be gone, and there will be errors in the next release. We want a clean 1.0.


We have been able to do all of this thanks to the continued support of 84codes, Nikola Motor Company and every other sponsor. It is extremely important for us to sustain the support through donations, so that we can maintain this development pace. OpenCollective and Bountysource are two available channels for that.

Reach out to if you’d like to become a direct sponsor or find other ways to support Crystal. We thank you in advance!