Crystal 0.28.0 has been released!
This is a big release that includes some new language constructs, tidying up some existing features, many interesting additions to the std-lib and important changes for the much awaited multi-threading and windows features.
There were 221 commits since 0.27.2 by 31 contributors.
Let’s review some of the most relevant changes in this release. But don’t miss the rest of the release changelog which has a lot of valuable information.
Language changes
Enums
Enums are usually declared with one line per each member.
enum State
On
Off
end
Sometimes you might want to declare it in one line, and since ;
is basically a new line for the parser you can do:
enum State
On; Off
end
In previous versions using spaces or commas were allowed. From now on ;
is the required one. The formatter will migrate commas to ;
in this version since that construct was used somewhat frequently. Read more at #7607 and #7618.
Ranges
Sometimes you don’t know where to start or finish. The same happens to a Range
. Ranges can now be begin-less and end-less ranges.
Let’s see some basic constructs for iterating ranges:
(3..).each do |x|
puts x
break if some_condition
end
(..3).reverse_each do |x|
# ... eventually yields 0, -1, -2
end
Make sure that they stop at some point, please.
The good stuff comes from its integration in std-lib. Check #7179 to learn all about them. I will leave here some of my favorites:
numbers = [1, 10, 3, 4, 5, 8]
numbers.select(6..) # => [10, 8]
numbers[..2] # => [1, 10, 3]
numbers[...2] # => [1, 10]
[1, 2, 3].zip(6..) # => [[1, 6], [2, 7], [3, 8]]
5.clamp(10..) # => 10
offsetof
The expression offsetof(Type, @ivar)
was introduced to the language in order to return the offset of the @ivar
within the memory representation of Type
. One use case for that is working with vertex.
Another awesome aspect of this feature is that it went from initial idea to final implementation in only 1 week by the same person. Going from the need to the full implementation of all the aspects in the lexer, parser, compiler, internals, highlighter, formatter is pretty impressive. Read the full story at #7589.
Macros
Up until now Type#annotation
was available to get the last annotation of a given kind. In #7326 Type#annotations
is added to list all annotations.
Another addition is that ArrayLiteral#sort_by
was added in #3947.
With these two features, we can do something like:
annotation ReviewedBy
end
@[ReviewedBy("Kevin")]
@[ReviewedBy("Stuart")]
@[ReviewedBy("gru")]
class Foo
end
{% for reviewer in Foo.annotations(ReviewedBy).sort_by(&.[0].downcase) %}
puts {{reviewer[0]}}
{% end %}
# output
# gru
# Kevin
# Stuart
Note that macros and annotations should not be overused. There are many ways to make a program simple and declarative.
Compiler
Deprecated definitions
We are introducing some first-class citizen in the language to have a common idiom to mark methods and other constructs as deprecated. The code is able to compile as usual, but the compiler is able to warn the programmer about usages of deprecated definitions.
In this release, these warnings are off by default in order to gather feedback about what would be the best output and workflow for the regular usage.
Given the following program:
# main.cr
@[Deprecated("Use `#bar`")]
def foo
42
end
puts foo
You can build the program with the new options --warnings all|none --error-on-warnings
.
$ ./bin/crystal main.cr --warnings all --error-on-warnings
Warning in main.cr:6: Deprecated top-level foo. Use `#bar`
puts foo
^~~
A total of 1 warnings were found.
The warning options are also available in crystal spec
and its exit status will be a combination of the spec itself and the deprecation check.
When building the docs using crystal docs
the deprecated definitions will have a badge. Read more at #7653.
Library lookup
There was some development to simplify how some libraries and static libraries are looked up and therefore can be overridden in case it is needed. An env var CRYSTAL_LIBRARY_PATH
is now used in the process of determining the location of libraries to link to.
When using @[Link("awesome", static: true)]
, the first step will be to lookup libawesome.a
in the paths listed at CRYSTAL_LIBRARY_PATH
. If nothing is found awesome
will be looked up using pkg-config --static
. If still nothing is found, libawesome.a
will be looked up in /usr/lib
, /usr/local/lib
.
Essentially CRYSTAL_LIBRARY_PATH
has the highest priority in the lookup. But it is also added to the linker for looking up shared libraries.
The compiler package will set a default value of CRYSTAL_LIBRARY_PATH
to the embedded libraries where libgc.a
is located. If the default libraries don’t suit you, just prepend the location where the alternative is located to CRYSTAL_LIBRARY_PATH
.
Read about this change in #7562 and in the new entry on the docs
Semantic fixes
The compiler includes a bunch of fixes and improvements related to how some code constructs were handled.
A protected initialize
will now define a protected new
thanks to #7510.
Some corner cases related to method lookup were fixed in #7537, #7536, and #7529. There should be a couple of fewer surprises now.
The type inference was also improved a bit when dealing with procs in #7527 and #7568. If you store procs in structures it might be worth checking out, you might be able to remove a hack or two.
Some error messages were improved to clarify: the scope the user should have in mind thanks to #7384, and some common pitfalls when capturing blocks thanks to #7406.
Concurrency
We are really happy to include some refactors that move us forward in the way to parallelism and to include an extension to bdwgc that will enable its multi-threading support to work with the Crystal runtime.
Although you can’t use MT right now, the work done at #7546 will enable to compare alternatives in the GC and changes in the scheduler when -D preview_mt
is used. While the MT is being developed piece by piece we are trying to avoid slow down in the single-threaded apps.
Two notable refactors introduced were cleanups in Fiber
to extract a Fiber::StackPool
in #7417, and refactor IO::Syscall
as IO::Evented
in #7505.
Standard library
Given an Enum
with @[Flags]
calling .from_value(0)
or .from_value?(0)
will now return None
. Previously it was raising or returning nil
. This is a breaking-change introduced in #6516 in order to fix the semantics of those methods.
The module PartialComparable
is deprecated in #7664 since its behaviour has been fully integrated into Comparable
.
Numeric
Some time ago we decided to make Int#/
return Float
. That will happen in the 0.29.0
. In 0.28.0
, you will need to start changing the code to use Int#//
. Luckily there compiler can now assist you in that quest. Build your program and specs with --warnings all
, the usages of /
will pop-up. We’ve done it in #7639.
Another notable change is that we settle to remove the type suffixes of numbers in #7525. The output will be cleaner now. If, given an expression, you want to know the value, type, and static type you can use pp!(e, e.class, typeof(e))
.
Text
Numbers might be used to represent quantities (Seriously!). These can now be printed in a human-readable form using Number#humanize
, Int#humanize_bytes
and Number#format
. This feature was added in #6314.
Collections
The counterpart of deprecating PartialComparable
was that a Comparable#<=>
is now able to return nil
. This was done in order to allow better support of Array#sort
over types that don’t have a total ordering. Read more at #6611.
Iterator#rewind
is removed and #cycle
got some performance improvements because now the elements are stored in an array and can be cycled despite the nature of the Iterator
. Read more at #7440.
Indexable#zip
and Indexable#zip?
were moved to Enumerable
and now there are far more flexible. They can work with any number of Indexable
, Iterable
or Iterator
.
a = [1, 2, 3]
b = "a".."c"
c = 9.downto(1)
a.zip(b, c) # => [{1, "a", 9}, {2, "b", 8}, {3, "c", 7}]
Read more at #7453.
Time
Time
got some polishing again but given the impact of the changes we wanted to include, and the new deprecation support we are using that to offer a migration path.
Don’t forget to build with
--warnings all
your code at least once!
Although you should leave in the present, Time.now
is now deprecated. But only because you need to know where you are. The code will need to decide between Time.local
or Time.utc
, much more explicit. This also affects constructors. Time.new
is also deprecated and Time.local
or Time.utc
can be used to build specific values. Read more at #5346 and #7586.
Time#add_span
is renamed and improved in Time#shift
. It now allows changing a time instance by calendar units and handle other units thanks to #6598.
The last breaking-change in is in Time#date
that will now return a Tuple
of {year, month, day}
. You can keep using Time#at_beginning_of_day
if a Time
instance is wanted. Read more at #5822.
Networking
There were a couple of efforts to improve HTTP
and URI
. Although they are breaking-changes there should be easy to migrate.
There are a couple of fixes in URI
implementation. URI#opaque
is dropped. URI#path
no longer returns Nil
. The #parse
/#to_s
normalization and default port handling have changed. Read the full story at #6323.
HTTP::Status
is introduced to avoid, if wanted, the need to status code numbers. The only breaking change regarding statuses is that HTTP.default_status_message_for(Int)
was replaced by HTTP::Status.new(Int).description
. Read more at #7247.
HTTP::Multipart
was moved to MIME::Multipart
in #7085.
In OAuth2 errors we no longer expect a proper JSON to exist. Discover why in #7467.
RequestProcessor
connection reuse logic was fixed to deal properly with unconsumed payloads in #7055.
Crypto
An OpenSSL::Algorithm
instead of symbol is now used in digest
/hexdigest
to choose the algorithm to use. As a bonus track, LibCrypt’s PKCS5_PBKDF2_HMAC
. Read more at #7264.
Next steps
Please update your Crystal and report any issues. We will keep moving forward and start the development focusing on 0.29.
Once again, check your code with --warnings all
. This will help in the migration of your codebase and will probably push some feedback about that tool.
Don’t miss the rest of the release changelog which has a lot of valuable information.
The development is possible thanks to the community’s effort, 84codes’ support, and every supporter.