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 -cexpression is now correctly parsed as(a - b) - c. #9652, #9884.
When using abstract defs, the compiler will enforce a couple of additional rules that should improve the maintenance of the code base.
- Abstract
defimplementations must honor abstract declaration regarding type restrictions, splats, default values, and keyword arguments. #9585, #9634, #9633. - Abstract
defimplementations 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 defs 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!returnselfalways, asArray. Bonus point: you can method chain. #9904.Set#deletereturnsBoolto indicate if the element was present. #9590.- Use
Hash#reject!instead ofHash#delete_if. #9878. - Use
Enumerable#selectinstead ofEnumerable#grep. #9711. Hash#key_indexwas removed. #10016
But not every change is a removal, the following additions are also part of the release!
- Allow
Iterator#zipto take multipleIteratorarguments. #9944 Array#shiftandArray#unshiftgot 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.newdeprecated variants are no longer available. (#10051Time::Span#durationis deprecated in favor of#abs.Time::Spanis 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!
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. So2 ** 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#annotationwas added. The former less powerfulTypeNode#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.
a-b -cexpression is now correctly parsed as(a - b) - c. #9652, #9884.When using abstract
defs, the compiler will enforce a couple of additional rules that should improve the maintenance of the code base.defimplementations must honor abstract declaration regarding type restrictions, splats, default values, and keyword arguments. #9585, #9634, #9633.defimplementations must honor return type abstract declaration. #9810A simple example of what this enforce would be:
Regarding typing, we improved a couple of stories.
How smarter you would like to know. Here is a sneak peak of what is now possible but it wasn’t before.
You can also declare
defs 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
libshould be declared now asaliasinstead oftype. One less quirk in the compiler.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 theCompressandDigestnamespace. Read more at #9530, #9529, and #9531.The
SemanticVersionobeys 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.sizeandFileInfo#sizesince they now return Int64 instead of UInt64. Read more at #10015.Numeric
If you use
Complex, you will need to rewrite some operations likeComplex#exp,Complex#log, etc. toMath.exp,Math.log. This is the same API as for other numerical types. Read more at #9739.Text
Stringcan hold arbitrary byte sequences. When these turned out to be invalid UTF-8 sequences, operations likeString#index,String#includes?, andChar::Readerwere 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!returnselfalways, asArray. Bonus point: you can method chain. #9904.Set#deletereturnsBoolto indicate if the element was present. #9590.Hash#reject!instead ofHash#delete_if. #9878.Enumerable#selectinstead ofEnumerable#grep. #9711.Hash#key_indexwas removed. #10016But not every change is a removal, the following additions are also part of the release!
Iterator#zipto take multipleIteratorarguments. #9944Array#shiftandArray#unshiftgot faster. #10081Serialization
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.mappingandYAML.mappingare 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_discriminatoranduse_yaml_discriminatorwith other types thanString. 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.newdeprecated variants are no longer available. (#10051Time::Span#durationis deprecated in favor of#abs.Time::Spanis already a duration, right? #10144Files
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_rwill behave as expected when destination is a directory: the destination is calculated withFile.join(dest_path, File.basename(src_path)).Networking
The
HTTP::Paramsis renamed toURI::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_pathtoURI#request_target. Some edge-case behaviors also change to match the expected definition. Read more at #10099.URI::Params(formerHTTP::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 #9605The
HTTP::Clientcan now be used with anyIOinstead ofTCPSocket. 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 #9543Logging
To have only one module to log them all, the former
Loggeris no longer in the std-lib. Migrate toLogor, 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, theLog::Backendcan 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
Digestmodule is now backed byOpenSSL. Most used algorithms have their own class that is easy to use:Digest::MD5,Digest::SHA1,Digest::SHA256,Digest::SHA512. Bonus:Digest#fileandDigest#update(IO)are available to compute digest from file andIOcontent. Read more at #9864.Concurrency
We hid some
ChannelAPI that should have been internal since the beginning. I doubt this will cause any issues. Read more #9564.Channel#closenow returnstrueunless 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_argumentswill allow you to parse aStringin the same way a POSIX shell does. This is internally used to improveCRYSTAL_OPTSparsing. Read more at #9518.Spec
The
#shouldand#should_notmethods can now take an additional argument for custom failure messages. Read more at #10127.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!