Crystal 0.36.0 has been released!
Since 0.35.1 there has been lots and lots of polishing, new features, and important fixes. This created a bigger delta than we wanted to transition from the last 0.x release to the first 1.0-preX. As a result, we are releasing a 0.36.0. This should help the community migrate their packages with less friction to the changes that will appear in 1.0. It will also give us a chance to get rid of a couple of recently introduced deprecations.
Note that this release will be available in the new apt and rpm repositories post, as well as GitHub release artifacts. You might need to update your package repositories.
There were 346 commits since 0.35.1 by 54 contributors.
Let’s get right into some highlights in this release. Don’t miss out on the rest of the release changelog.
Language changes
Although instance variables can have annotations, we must apply them in the base class that declares them. So we reject annotations on instance variables redefined in a child class. We might lift this restriction in the future, but we are going to play it safe first. Read more at #9502.
The **
operator is right-associative from now on. This matches Ruby and a couple of other programming languages. So 2 ** 2 ** 3 == 2 ** (2 ** 3) == 256
. But note that the negation is done before the exponentiation because of how we parse negative number literals. So -2 ** 2 == (-2) ** 2 == 4
, which is different to Ruby. Read more at #9684.
Some releases ago TypeNode#annotation
was added. The former less powerful TypeNode#has_attribute?
is deprecated now. Read more at #9950.
Compiler
There are several fixes that, although formally lead to breaking-changes, are mostly edge cases that required some polishing.
- You can’t use keywords as block arguments names. #9704
- The
a-b -c
expression is now correctly parsed as(a - b) - c
. #9652, #9884.
When using abstract def
s, the compiler will enforce a couple of additional rules that should improve the maintenance of the code base.
- Abstract
def
implementations must honor abstract declaration regarding type restrictions, splats, default values, and keyword arguments. #9585, #9634, #9633. - Abstract
def
implementations must honor return type abstract declaration. #9810
A simple example of what this enforce would be:
abstract class Foo
abstract def m(x = 1)
end
class Bar < Foo
def m(x) # Error: because Foo#m can be called with no argument.
# Declare it as def m(x = 1) to make the compiler happy.
end
end
Regarding typing, we improved a couple of stories.
- The type variables unification is more correct regarding union types. (#10267, thanks @HertzDevil)
- Exhaustive case expressions types to non-nilable value if all the cases allow it. #9659
- Auto-casting deals better when the type restriction is a union by using, if possible, the exact type of the literal argument. #9610
- Typing rules involving closured variables got smarter. #9986
- Typing rules involving type restrictions, unions and boolean operators are really smarter. #10147.
How smarter you would like to know. Here is a sneak peak of what is now possible but it wasn’t before.
x = 1 || 1.0 || 'a' || "" # x : Int32 | Float64 | Char | String
# when-clauses of >= 3 types now work, including else-branches of the case statement
case x
when Int32, Float64, Char
typeof(x) # => Int32 | Float64 | Char
else
typeof(x) # => String
end
# negations of disjunctions now work
if !(x.is_a?(Int32) || x.is_a?(Float64))
typeof(x) # => Char | String
else
typeof(x) # => Int32 | Float64
end
# the de Morgan equivalent of the above also works now
if !x.is_a?(Int32) && !x.is_a?(Float64)
typeof(x) # => Char | String
else
typeof(x) # => Int32 | Float64
end
You can also declare def
s overloads with different named tuple types. Really handy if you are into using tuples a lot. Read more at #10245.
A breaking-change that affects C bindings is introduced in #10254. Callbacks within lib
should be declared now as alias
instead of type
. One less quirk in the compiler.
lib YourLib
--- type ACallback = Int32 -> Int32
+++ alias ACallback = Int32 -> Int32
end
A second breaking-change that affects C bindings is that a Crystal nil value is no longer translated to a null pointer value. Read more at #9872.
Standard library
The following top-level deprecated definitions are dropped: CRC32
, Adler32
, Flate
, Gzip
, Zip
, Zlib
, with_color
. You will need to migrate to the Compress
and Digest
namespace. Read more at #9530, #9529, and #9531.
The SemanticVersion
obeys the standard (as it should). This causes some breaking-changes if you rely on invalid version names, but otherwise you are safe. Read more at #9868.
Unfortunately some breaking-changes are silent. There is no simple way to show a warning. Such a case is when the return type of a method is changed. This is what happens to File.size
and FileInfo#size
since they now return Int64 instead of UInt64. Read more at #10015.
Numeric
If you use Complex
, you will need to rewrite some operations like Complex#exp
, Complex#log
, etc. to Math.exp
, Math.log
. This is the same API as for other numerical types. Read more at #9739.
Text
String
can hold arbitrary byte sequences. When these turned out to be invalid UTF-8 sequences, operations like String#index
, String#includes?
, and Char::Reader
were misbehaving. In the presence of an invalid UTF-8 sequence, the data is now iterated a bit more slowly, like 1 byte at a time, until a valid sequence is found. Read more at #9713.
Collections
There is a set of changes seeking consistency over the collection types. Unfortunately some are silent breaking changes.
Hash#reject!
,Hash#select!
, andHash#compact!
returnself
always, asArray
. Bonus point: you can method chain. #9904.Set#delete
returnsBool
to indicate if the element was present. #9590.- Use
Hash#reject!
instead ofHash#delete_if
. #9878. - Use
Enumerable#select
instead ofEnumerable#grep
. #9711. Hash#key_index
was removed. #10016
But not every change is a removal, the following additions are also part of the release!
- Allow
Iterator#zip
to take multipleIterator
arguments. #9944 Array#shift
andArray#unshift
got faster. #10081
Serialization
We try to have one way to do things. This comes at the expense of needing to migrate old solutions that served us well. JSON.mapping
and YAML.mapping
are no longer part of the std-lib since there is a more flexible alternative: JSON::Serializable
, YAML::Serializable
. You can either migrate or, if you still want them, you can use github:crystal-lang/json_mapping.cr and github:crystal-lang/yaml_mapping.cr. Read more at #9527 and #9526.
You can now declare properties that will be used only on serialization or deserialization. If your model has a field that should be hashed for serialization this comes in really handy. Read more at #9567.
Another addition is that you can use use_json_discriminator
and use_yaml_discriminator
with other types than String
. For example, you can use numbers or enums to map the corresponding type. Read more at #9222 and #10149.
Time
It’s time to mention some small breaking-changes:
Time::Span.new
deprecated variants are no longer available. (#10051Time::Span#duration
is deprecated in favor of#abs
.Time::Span
is already a duration, right? #10144
Files
Some breaking-changes are almost bug-fixes, but since they mean changing a known behavior, it is worth mentioning them as such. After #10180, FileUtils.cp_r
will behave as expected when destination is a directory: the destination is calculated with File.join(dest_path, File.basename(src_path))
.
Networking
The HTTP::Params
is renamed to URI::Params
. In this release there is a deprecated alias that will allow you to deal with this migration more gradually. But I would not count on this alias to remain for long. Read more at #10098.
Another breaking-change is the renaming of URI#full_path
to URI#request_target
. Some edge-case behaviors also change to match the expected definition. Read more at #10099.
URI::Params
(former HTTP::Params
), as you know, is used to represent query strings that can hold multiple values for a key. When assigning a new value to a key you will override all values of that key. Read more at #9605
params = URI::Params.parse("color=red&color=blue&console=gameboy")
params["color"] = "green"
params # => => "color=green&console=gameboy"
The HTTP::Client
can now be used with any IO
instead of TCPSocket
. If you happen to have an application that uses HTTP as protocol over UNIX Sockets, like Docker, you can now talk to it. Read more at #9543
require "http"
io = UNIXSocket.new("/var/run/docker.sock")
client = HTTP::Client.new(io, "localhost")
response = client.get "/v1.40/images/json"
Logging
To have only one module to log them all, the former Logger
is no longer in the std-lib. Migrate to Log
or, for just a little longer, you can keep using github:crystal-lang/logger.cr. Read more at #9525.
If you need more excuses to migrate to Log
, the Log::Backend
can now emit their entries with different strategies that should help keeping the throughput of the app in the presence of logging. Read more at #9432.
Crypto
Time for some security alerts. The std-lib prevents by default the Secure Client-Initiated Renegotiation vulnerability attack. In #9815 you can find more info and a small patch if you need to secure existing apps. Also, if you use verify_mode=force-peer
, it is now correctly set up. Read more at #9668.
The Digest
module is now backed by OpenSSL
. Most used algorithms have their own class that is easy to use: Digest::MD5
, Digest::SHA1
, Digest::SHA256
, Digest::SHA512
. Bonus: Digest#file
and Digest#update(IO)
are available to compute digest from file and IO
content. Read more at #9864.
Concurrency
We hid some Channel
API that should have been internal since the beginning. I doubt this will cause any issues. Read more #9564.
Channel#close
now returns true
unless the channel was already closed. This is handy in multi-thread, because you might need to know if the current fiber is the one that actually closed the channel. Read more at #9443.
System
Process.parse_arguments
will allow you to parse a String
in the same way a POSIX shell does. This is internally used to improve CRYSTAL_OPTS
parsing. Read more at #9518.
Spec
The #should
and #should_not
methods can now take an additional argument for custom failure messages. Read more at #10127.
describe "something" do
it "something" do
true.should eq(false), "Oh no!"
end
end
Others
The CI infrastructure for AArch64 hosted by works on arm is making progress. We are still not releasing official packages. Read more at #9508.
The doc generator got many improvements. In case you weren’t aware, besides generating HTML, it can also export the documentation information as JSON. This allows for the use of other documentation generation tools. With some of the recent additions listed in the changelog you can use mkdocs via some community development.
Next steps
Please update your Crystal and report any issues. We will keep moving forward and focusing on releasing 1.0.0-pre1 which should be a more stabilized version of 0.36 without many new additions.
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 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!