Crystal 0.25.0 has been released!
As every release, it includes numerous bugfixes, cool features and performance improvements - in 400 commits since 0.24.2 from 47 contributors. There needs to be a special mention to @MakeNowJust, @straight-shoota, @Sija and @bew for their hard work in this release.
There were a ton of contributions merged in master even before 0.24.2 was released. But since 0.24.2 was already changing the release packaging for linux, changing the CI and fixing 0.24.1, some features needed to wait their turn a little longer.
Once again, we have tested this release by compiling some of the most popular crystal shards. This helps us catch and fix unintended breaking changes earlier in the release cycle as well as submitting PRs to the shards and contributing a bit more with the community. This process is codified using the scripts in the test-ecosystem repository, which is still fairly new, but so far it’s working well.
The least visible work usually goes in infrastructure and there are always improvements and things waiting to be done. The latest news regarding this area are:
- Docs in master are back. For every PR that is merged the docs at HEAD can be found at /api/master/.
- Improved SEO by adding a canonical url for online docs #5990.
- Also on docs, lots of improvements regarding navigation have been done in #5229.
- The automated release process now cares about 32 bits linux releases. As a bonus point the packaging has been aligned again with respect to the 64 bits packages. So some paths have changed.
- We’ve been contacted by Heroku to early register our buildpack. Stay tuned to future Heroku news to update to the
crystal-lang/crystal
buildpack in the registry. All in all it’s one more taste of the adoption of Crystal out there, and we are thrilled.
Nightly packages in nightly.crystal-lang.org
are still down. The workaround for now it to use the docker image crystallang/crystal:nightly
.
Exciting features
Shards is updated to 0.8.0
There are some performance improvements in shards for this release, by downloading less information when possible. A new global cache was added, so you don’t need to download your favorite shards over and over on all of your favorites projects. FYI you can use shards 0.8.0 with Crystal 0.24.2 if you want.
Read more here.
Automatic casts for literal values
If a method is defined as def foo(x : Int8)
or def bar(color : Color)
with
enum Color
Red
Green
Blue
end
up to 0.24 you would need to call them as foo(1i8)
or bar(Color::Blue)
. But since 0.25.0 you will be able to foo(1)
and bar(:blue)
. A note of caution: this only work with literal values. If the value is saved in a variable and used as an argument it won’t work.
This feature allows cleaner code without sacrificing safety. Read more at #6074.
User defined annotations and [JSON|YAML]::Serializable
This new language construct allows the user to define their own annotations, like [Link]
. Basically you will be able to annotate types declaration or instance variables, and later on query them to do something you wish in macros.
Before this feature metaprogramming usually involved calling one macro with all the information needed. From now on, a more decoupled mechanism between declaring and consuming can be used. Read more at #6063.
The new JSON::Serializable
and YAML::Serializable
modules use this annotations. Feedback is welcome since this feature is brand new. You can read more at #6082, JSON::Serializable, YAML::Serializable docs.
Another usage of annotations might be to declare a registry of classes, like the one used in DB
drivers or frameworks handlers. And it could enable the removal of mutating values of constants during compilation time in favor of a more declarative code.
Do not collapse unions for sibling types
Code is worth a thousand words (you know, like pictures):
class Foo
end
class Bar < Foo
end
class Baz < Foo
end
var = rand < 0.5 ? Bar.new : Baz.new
typeof(var) #=> Bar | Baz
Up to 0.24.2 the result was typeof(var) #=> Foo
.
Although the previous code already compiled fine in 0.24.2 this changes allow the type system to deal with some cases that would have ended in a compile-time error but that actually make sense. At the end of the day the type system is about identifying which programs will safely run and cutting the ones that won’t.
The following program is an example of that. It won’t compile in 0.24.2 but it now does in 0.25.0.
class Foo
end
class Bar < Foo
def do_it
end
end
class Baz < Foo
def do_it
end
end
class Qux < Foo
# there is no do_it
end
var = rand < 0.5 ? Bar.new : Baz.new
var.do_it
This is particularly useful in scenarios where there is a huge hierarchy of types but in a section of the code only a subset is used.
You can read more at #6024 and discover when the union of types are still collapsed to the common ancestor (spoiler, they need to not be siblings).
JSON::Any
and YAML::Any
changes
There were some subtle inconsistencies with JSON::Any
and YAML::Any
API. The bottom line is that over an ::Any
value you can use #[]
to traverse it and it will always return an ::Any
value. If you need a specific type for the ::Any
value (and be able to use Enumerable
methods if it was an array) you need to call the already known #as_a
, #as_h
, #as_s
methods.
We still encourage, when possible, the use of JSON.mapping
, JSON::Serializable
or JSON::PullParser
when finer control is needed.
Read more at #5183 and in the JSON::Any and YAML::Any docs.
HTTP::Server
can bind to multiple addresses
This will break lots of presentations and even the code shown in our own homepage but the benefits are great.
From now on if you use the built-in HTTP::Server
you first need to configure it, then bind to one or more addresses, and lastly you listen to all of them. These addresses can be TCP ports or Unix sockets.
require "http/server"
server = HTTP::Server.new do |context|
context.response.content_type = "text/plain"
context.response.print "Hello world, got #{context.request.path}!"
end
server.bind_tcp "0.0.0.0", 8080
server.bind_unix "/tmp/app.sock"
server.listen
There is still a shortcut to bind and listen, but it doesn’t avoid a breaking change. Read more at #5776, #5959, and the HTTP::Server docs
Welcome to the TimeZone Jungle
There was a huge refactor in Time
. If you hit a unicorn while opening the PR to read more about it, just try again.
Starting now Time
has #location
and #offset
properties to know the timezone exactly. Time.now
and Time.new
will return by default information in the local timezone, while Time.utc_now
and Time.utc
will return information in UTC.
Methods like #to_local
, #to_utc
, #utc?
, #local?
and #in(location : Location)
will help you to move around the globe faster than a plane.
The API even allows you to use custom timezones and fixed offsets with Time::Location.fixed
.
Another change in the Time
namespace are formatters. Better formatters for ISO 8601, RFC 3339, RFC 2822, HTTP enconded dates, YAML and other places where time was parsed or emitted now use a custom time formatter that deals with more cases as expected in each scenario.
Read more at #5324 and #5123 and Time, and Format docs.
Replace File::Stat
with File::Info
and other file API changes
Some time ago an abstraction for the running OS was introduced in the stdlib. The goal was to be able to run the Crystal compiler in a non POSIX platform and keep the stdlib as clean as possible. Feel free to check src/crystal/system, but keep in mind it is not intended as a public API.
This also required to pick names and abstractions in the stdlib that will fit everybody: POSIX and non POSIX.
The API was renamed and reworked for compare operations and accessing file properties and permissions. It is much clearer now. Hopefully it doesn’t affect too many users, since most of us use File.open
, File.write
and move on. Read more at #5584, #5333, #5553, #6161, File and File::Info docs.
Heredoc on every argument
If you use Heredoc a lot of you might be interested in this one. Up to 0.24.2 if you wanted to call a method on a string specified using Heredoc you would do:
puts <<-FOO
message
FOO.downcase
From now on the method needs to be at the initial delimiter
puts <<-FOO.downcase
message
FOO
It’s subtle but important, and it plays better with multiple Heredocs in a single call now that you can:
puts <<-ONE.upcase, <<-TWO.capitalize
hello world
ONE
second message
TWO
Read more at #5578.
Macro verbatim blocks
If you deal with escaped macros don’t miss #6108.
Macros are powerful and they should be used after there is a boilerplate pattern discovered.
This new language construct helps when the macro itself will define, for example, methods that have macro blocks that should be expanded later (i.e. nested macros).
It may result in a nicer way to express the same things you could before with some \{% escaping %}
.
Other notable changes (breaking or not)
crystal deps
is dead, long liveshards install
. #5544. Unless we removed it, you would never have updated your build scripts.- Use
Hash#key_for
to perform a reverse lookup in a hash #5444 #NamesAreHard. - The block argument from
loop
was removed #6026. - Fix
File.join
with empty path component #5915. Colorize#push
is dead, long liveColorize#surround
#4196. Bonus point, your#to_s
can use your favorite color now.- Punycode is a special encoding used to convert Unicode characters to ASCII and is used to encode internationalized domain names (IDN). And now they are available in Crystal thanks to #2543.
pp
no longer prints the expression. Butpp!
and the newp!
will.p
stands for print,pp
for pretty print and!
for show me themoneyexpression #6044.
Next step
Please update your Crystal and report any issues. If there are regression or blocking issues with 0.25.0, a 0.25.1 could be released earlier.
Don’t miss the rest of the release changelog information with lots of other fixes.
The development is possible thanks to the community’s effort, 84codes’ support, and every BountySource supporter.