Skip to content
GitHub Repository Forum RSS-Newsfeed

Crystal 1.5.0 is released!

We are delivering a new release with several bugfixes and improvements. Below we list the most important or interesting changes, without mentioning several bugfixes and smaller enhancements. For more details, visit the changelog. Breaking changes are marked with ⚠️.

Pre-built packages are available on GitHub Releases and our official distribution channels. See for installation instructions.


In this release we included 102 changes since the 1.4.1 release by 23 contributors. We thank all the effort put into improving the language! ❤️

Below we list the most remarkable changes in the language and stdlib.

⚠️ Parameters of methods implementing an abstract def must match the names

In order to provide better documentation and robustness, it is possible to explicitly associate arguments with their names (ref):

class Foo
  def foo(name : Int32) : Nil
    p name
end name: 42

As a consequence, it is sensible to consider that the name of an argument is part of its interface. Before 1.5.0, however, the compiler was not checking that the name of arguments matched between an implementation of an abstract method and its definition. That is, the following example compiled without error or warning:

abstract class FooAbstract
  abstract def foo(number : Int32) : Nil

class Foo < FooAbstract
  def foo(name : Int32) : Nil
    p name

Starting from 1.5.0 (#11915) the example above will raise a warning:

 6 | def foo(name : Int32) : Nil
Warning: positional parameter 'name' corresponds to parameter 'number' of the overridden method FooAbstract#foo(number : Int32), which has a different name and may affect named argument passing

Method restrictions from instance variables

When an instance variable is assigned the value of an untyped method argument, then the argument is restricted to share the same type as the instance variable.

For instance, consider this code:

class Foo
  @x : Int64

  def initialize(x)
    @x = x

Up to 1.4.1, x in initialize is unrestricted. This had several issues:

  1. If the user passes an incorrect argument, like 'a', instead of marking the error in the argument 'a', it blames x for not having the right type.

  2. No autocast is performed, for instance, if we pass an Int32 instead: 1 fails.

  3. The generated documentation doesn’t provide a hint of the type of the parameter x.

From 1.5.0, in an assignment like @x = x, parameter x gets the type of @x, effectively solving the three issues above. Details can be read from #12103.

Note: This new feature is expected to be backwards compatible. If for some reason it brings you a headache you can disable it passing -Dno_restrictions_augmenter in the build options. Let us know in the issue tracker about it.

Annotations allowed on method arguments

It is now possible to add an annotation to a parameter of a method or macro. As illustration, imagine a linter that warns if a parameter is not used.

def foo(x); end  # Warning: argument `x` is not used

Then, we could signal the linter to not warn us in a particular case. Assume the following annotation provided by the linter:

annotation MaybeUnused; end

Applying it to the parameter removes the warning (in this particular fictitious linter):

def foo(@[MaybeUnused] x); end  # OK

Details in #12039.

Constant indexers for tuples

When using a constant to index a tuple or named tuple, the typechecker will correctly infer the precise type of the value accessed (#12012).

KEY = "s"
foo = {s: "String", n: 0}

# Before 1.5.0 this failed; it would assume the type of foo[key] to be (String | Int32)
puts foo[KEY].size

Additions in File API

New method File#delete? that, instead of raising, returns false if the file doesn’t exist. Similarly, there’s a new Dir#delete?. Details in #11887.

Additionally, several File methods now also operate on an instance: File#chmod, File#chown, File#utime, and File#touch. Details in #11886.

Strengthening the security of File.tempfile

As per #12076, the creation of temporary files don’t allow null characters in the strings forming the name of the file.

NO_COLOR compliance

The compiler and the interpreter supports the NO_COLOR environment variable to disable colored output on the terminal. This is enabled by setting any non-empty value to NO_COLOR. Details in #11984.

A big step towards native Windows support

The concurrency runtime on Windows is now backed by a functioning event loop (#12149). This crossed an important check in the road to native Windows support. Additionally, we now have a Windows-compatible Makefile(#11773).

Improvements in the interpreter

The interpreter is coming to shape! A whole bunch of interpreter bugs got fixed. While it won’t be released in the binaries distributions yet, we will work towards incorporating it in the major platforms right after this release. Stay tuned! You can build the compiler with interpreter support locally with make interpreter=1, see the interpreter introduction post for more info.


We have been able to do all of this thanks to the continued support of 84codes, Nikola Motor Company and every other sponsor. To maintain and increase the development pace, donations and sponsorships are essential. OpenCollective is available 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!