-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Crystal for Rubyists
Although Crystal has a Ruby-like syntax, Crystal is a different language, not another Ruby implementation. For this reason, and mostly because it's a compiled, statically typed language, the language has some big differences when compared to Ruby.
If you have a program foo.cr:
# Crystal
puts "Hello world"When you execute one of these commands:
crystal foo.cr
ruby foo.cr
You will get this output:
Hello world
It looks like crystal interprets the file, but what actually happens is that the file foo.cr is first compiled to a temporary executable and then this executable is run. This behaviour is very useful in the development cycle as you normally compile a file and want to immediately execute it.
If you just want to compile it you can use the build command:
crystal build foo.cr
This will create a foo executable, which you can then run with ./foo.
Note that this creates an executable that is not optimized. To optimize it, pass the --release flag:
crystal build foo.cr --release
When writing benchmarks or testing performance, always remember to compile in release mode.
You can check other commands and flags by invoking crystal without arguments, or crystal with a command and no arguments (for example crystal build will list all flags that can be used with that command).
true and false are values in the Bool class rather than values in classes TrueClass or FalseClass.
For Ruby's Fixnum type, use one of Crystal's Integer types Int8, Int16, Int32, Int64, UInt8, UInt16, UInt32, or UInt64.
If any operation on a Ruby Fixnum exceeds its range, the value is automatically converted to a Bignum. Crystal will use modular arithmetic on overflow. For example:
x = 127_i8 # An Int8 type
puts x # 127
x += 1 # -128
x += 1 # -127See Integers
Global variables $` and $' are missing (yet $~ and $1, $2, ... are present). Use $~.pre_match and $~.post_match. read more
In Ruby where there are several methods for doing the same thing, in Crystal there may be only one. Specifically:
Ruby Method Crystal Method
----------------- --------------
Enumerable#detect Enumerable#find
Enumerable#collect Enumerable#map
Object#respond_to? Object#responds_to?
length, size, count size
Where Ruby has a a couple of alternative constructs, Crystal has one.
- trailing while/until. Note however that if as a suffix is still available
-
andandor: use&&and||instead with suitable parenthesis to indicate precedence - Ruby has
Kernel#proc,Kernel#lambda,Proc#newand->, while Crystal uses just-> - For
require_relative "foo"userequire "./foo"
[[1, "A"], [2, "B"]].each do |a, b|
pp a
pp b
endwill generate an error message like
in line 1: too many block arguments (given 2, expected maximum 1)
However omitting unneeded arguments is fine.
There is autosplat for tuples:
[{1, "A"}, {2, "B"}].each do |a, b|
pp a
pp b
endwill return the result you expect.
In Ruby .each returns the receiver for many built-in collections like Array and Hash, which allows for chaining methods off of that, but that can lead to some performance and codegen issues in Crystal, so that feature is not supported. Alternately, one can use .tap.
Ruby:
[1, 2].each { "foo" } # => [1, 2]Crystal:
[1, 2].each { "foo" } # => nil
[1, 2].tap &.each { "foo" } # => [1, 2]Kernel#eval() and the weird Kernel#autoload() are omitted. Object and class introspection methods Object#kind_of?(), Object#methods, Object#instance..., and Class#constants, are omitted.
In some cases macros can be used for reflection.
In Ruby, string literals can be delimited with single or double quotes. A double-quoted string in Ruby is subject to variable interpolation inside the literal, while a single-quoted string is not.
In Crystal, strings literals are delimited with double quotes only. Single quotes act as character literals the same as say C-like languages. As with Ruby, there is variable interpolation inside string literals.
In sum:
X = "ho"
puts '"cute"' # Not valid in crystal, use "\"cute\"", %{"cute"}, or %("cute")
puts "Interpolate #{X}" # works the same in Ruby and Crystal.Triple quoted strings literals of Ruby or Python are not supported, but string literals can have newlines embedded in them:
"""Now,
what?""" # Invalid Crystal use:
"Now,
what?" # Valid CrystalIn Ruby the [] method generally returns nil if an element by that index/key is not found. For example:
# Ruby
a = [1, 2, 3]
a[10] #=> nil
h = {a: 1}
h[1] #=> nilIn Crystal an exception is thrown in those cases:
# Crystal
a = [1, 2, 3]
a[10] #=> raises IndexError
h = {"a" => 1}
h[1] #=> raises KeyErrorThe reason behind this change is that it would be very annoying to program in this way if every Array or Hash access could return nil as a potential value. This wouldn't work:
# Crystal
a = [1, 2, 3]
a[0] + a[1] #=> Error: undefined method `+` for NilIf you do want to get nil if the index/key is not found, you can use the []? method:
# Crystal
a = [1, 2, 3]
value = a[4]? #=> return a value of type Int32 | Nil
if value
puts "The number at index 4 is : #{value}"
else
puts "No number at index 4"
endThe []? is just a regular method that you can (and should) define for a container-like class.
Another thing to know is that when you do this:
# Crystal
h = {1 => 2}
h[3] ||= 4the program is actually translated to this:
# Crystal
h = {1 => 2}
h[3]? || (h[3] = 4)That is, the []? method is used to check for the presence of an index/key.
Just as [] doesn't return nil, some Array and Hash methods also don't return nil and raise an exception if the element is not found: first, last, shift, pop, etc. For these a question-method is also provided to get the nil behaviour: first?, last?, shift?, pop?, etc.
The convention is for obj[key] to return a value or else raise if key is missing (the definition of "missing" depends on the type of obj) and for obj[key]? to return a value or else nil if key is missing.
For other methods, it depends. If there's a method named foo and another foo? for the same type, it means that foo will raise on some condition while foo? will return nil in that same condition. If there's just the foo? variant but no foo, it returns a truthy or falsey value (not necessarily true or false).
Examples for all of the above:
-
Array#[](index)raises on out of bounds,Array#[]?(index)returns nil in that case. -
Hash#[](key)raises if the key is not in the hash,Hash#[]?(key)returns nil in that case. -
Array#firstraises if the Array is empty (there's no "first", so "first" is missing), whileArray#first?returns nil in that case. Same goes for pop/pop?, shift/shift?, last/last? - There's
String#includes?(obj),Enumerable#includes?(obj)andEnumerable#all?, all of which don't have a non-question variant. The previous methods do indeed return true or false, but that is not a necessary condition.
for loops are currently missing but you can add them via macro:
macro for(expr)
{{expr.args.first.args.first}}.each do |{{expr.name.id}}|
{{expr.args.first.block.body}}
end
end
for i in [1,2,3] do
puts i
end
# note the trailing 'do' as block-opener!
The ruby attr_accessor, attr_getter and attr_setter methods are replaced with new keywords:
Ruby Keyword Crystal Keyword
------------- ---------------
attr_accessor property
attr_reader getter
attr_writer setter
Nice english operators for '&&' and '||' are currently not supported
In general you need some more brackets to compile
def brackets_needed(a)
a.is_a?(Array)
endRuby File::exists? becomes crystal File.exists? etc...
Crystal added some new keywords, these can still be used as function names, but need to be called explicitly with dot: e.g. self.select{ |x| x > "good" }
begin class def
do else elsif
end ensure fun
if lib macro
module rescue struct
loop case select
then when while
for return macro
require private protected
yield enum union
Crystal requires each private method to be prefixed with the private keyword:
private def method
42
end