面向 Ruby 开发者的 Crystal¶
虽然 Crystal 拥有类似 Ruby 的语法,但 Crystal 是一种不同的语言,而不是另一个 Ruby 实现。出于这个原因,主要是因为它是一种编译的静态类型语言,与 Ruby 相比,该语言有一些重大的差异。
Crystal 作为一种编译语言¶
使用 crystal 命令¶
如果您有一个程序 foo.cr
# Crystal
puts "Hello world"
当您执行其中一个命令时,您将获得相同的输出
$ crystal foo.cr
Hello world
$ ruby foo.cr
Hello world
看起来 crystal 解释了文件,但实际上发生的是,文件 foo.cr 首先被编译成一个临时可执行文件,然后运行该可执行文件。这种行为在开发周期中非常有用,因为您通常会编译一个文件并希望立即执行它。
如果您只想编译它,可以使用 build 命令
$ crystal build foo.cr
这将创建一个 foo 可执行文件,您可以使用 ./foo 运行它。
请注意,这将创建一个未经优化的可执行文件。要对其进行优化,请传递 --release 标志
$ crystal build foo.cr --release
在编写基准测试或测试性能时,请务必在发布模式下进行编译。
您可以通过在没有参数的情况下调用 crystal 或在没有参数的情况下使用命令调用 crystal(例如 crystal build 将列出可与该命令一起使用的所有标志)来检查其他命令和标志。或者,您可以阅读 手册。
类型¶
Bool¶
true 和 false 的类型为 Bool,而不是类 TrueClass 或 FalseClass 的实例。
整数¶
对于 Ruby 的 Fixnum 类型,请使用 Crystal 的其中一个整数类型 Int8、Int16、Int32、Int64、UInt8、UInt16、UInt32 或 UInt64。
如果对 Ruby Fixnum 的任何操作超过其范围,则该值将自动转换为 Bignum。Crystal 则会在溢出时引发 OverflowError。例如
x = 127_i8 # An Int8 type
x # => 127
x += 1 # Unhandled exception: Arithmetic overflow (OverflowError)
Crystal 的标准库提供了具有任意大小和精度的数字类型:BigDecimal、BigFloat、BigInt、BigRational。
请参阅有关 整数 的语言参考。
正则表达式¶
不支持全局变量 $` 和 $'(但 $~ 和 $1、$2 等仍然存在)。使用 $~.pre_match 和 $~.post_match。了解更多。
简化的实例方法¶
在 Ruby 中,有多种方法可以完成同一件事,而在 Crystal 中可能只有一种。具体而言
| Ruby 方法 | Crystal 方法 |
|---|---|
Enumerable#detect |
Enumerable#find |
Enumerable#collect |
Enumerable#map |
Object#respond_to? |
Object#responds_to? |
length、size、count |
size |
省略的语言结构¶
在 Ruby 中,有一些替代结构,而 Crystal 只有一个。
- 缺少尾随
while/until。但请注意,if 作为后缀 仍然可用 and和or:使用&&和||代替,并使用适当的括号来指示优先级- Ruby 有
Kernel#proc、Kernel#lambda、Proc#new和->,而 Crystal 使用Proc(*T, R).new和->(请参阅 这里 以供参考)。 - 对于
require_relative "foo",使用require "./foo"
数组没有自动 splat 以及强制最大代码块参数数量¶
[[1, "A"], [2, "B"]].each do |a, b|
pp a
pp b
end
将生成类似以下的错误消息
in line 1: too many block arguments (given 2, expected maximum 1)
但是,省略不需要的参数是可以的(就像在 Ruby 中一样),例如
[[1, "A"], [2, "B"]].each do # no arguments
pp 3
end
或者
def many
yield 1, 2, 3
end
many do |x, y| # ignoring value passed in for "z" is OK
puts x + y
end
元组有自动 splat
[{1, "A"}, {2, "B"}].each do |a, b|
pp a
pp b
end
将返回您期望的结果。
您还可以显式解包以获得与 Ruby 的自动 splat 相同的结果
[[1, "A"], [2, "B"]].each do |(a, b)|
pp a
pp b
end
以下代码也适用,但更倾向于前者。
[[1, "A"], [2, "B"]].each do |e|
pp e[0]
pp e[1]
end
#each 返回 nil¶
在 Ruby 中,.each 为许多内置集合(如 Array 和 Hash)返回接收器,这允许对该接收器进行方法链,但这可能会在 Crystal 中导致一些性能和代码生成问题,因此不支持该功能。或者,可以使用 .tap。
Ruby
[1, 2].each { "foo" } # => [1, 2]
Crystal
[1, 2].each { "foo" } # => nil
[1, 2].tap &.each { "foo" } # => [1, 2]
反射和动态评估¶
省略了 Kernel#eval() 和奇怪的 Kernel#autoload()。还省略了对象和类内省方法 Object#kind_of?()、Object#methods、Object#instance_methods 和 Class#constants。
在某些情况下,可以使用 宏 进行反射。
语义差异¶
单引号和双引号字符串¶
在 Ruby 中,字符串字面量可以使用单引号或双引号分隔。在 Ruby 中,双引号字符串会受到字面量内变量插值的影響,而单引号字符串则不会。
在 Crystal 中,字符串字面量只用双引号分隔。单引号就像 C 类语言一样充当字符字面量。与 Ruby 一样,字符串字面量中存在变量插值。
总之
X = "ho"
puts '"cute"' # Not valid in crystal, use "\"cute\"", %{"cute"}, or %("cute")
puts "Interpolate #{X}" # works the same in Ruby and Crystal.
不支持 Ruby 或 Python 的三引号字符串字面量,但字符串字面量可以包含换行符
"""Now,
what?""" # Invalid Crystal use:
"Now,
what?" # Valid Crystal
不过,Crystal 支持许多 百分比字符串字面量。
[] 和 []? 方法¶
在 Ruby 中,[] 方法通常在找不到该索引/键的元素时返回 nil。例如
# Ruby
a = [1, 2, 3]
a[10] #=> nil
h = {a: 1}
h[1] #=> nil
在 Crystal 中,这些情况下会抛出异常
# Crystal
a = [1, 2, 3]
a[10] # => raises IndexError
h = {"a" => 1}
h[1] # => raises KeyError
此更改背后的原因是,如果每个 Array 或 Hash 访问都可能返回 nil 作为潜在值,那么以这种方式编程将非常令人讨厌。这将不起作用
# Crystal
a = [1, 2, 3]
a[0] + a[1] # => Error: undefined method `+` for Nil
如果确实想要在找不到索引/键时获取 nil,可以使用 []? 方法
# 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"
end
[]? 只是一个普通方法,可以(也应该)为类似容器的类定义。
需要了解的另一件事是,当执行以下操作时
# Crystal
h = {1 => 2}
h[3] ||= 4
程序实际上会被转换为以下内容
# Crystal
h = {1 => 2}
h[3]? || (h[3] = 4)
也就是说,[]? 方法用于检查索引/键是否存在。
正如 [] 不返回 nil 一样,一些 Array 和 Hash 方法也不返回 nil 并抛出异常(如果找不到元素):first、last、shift、pop 等。对于这些方法,也提供了疑问方法来获取 nil 行为:first?、last?、shift?、pop? 等。
约定是 obj[key] 返回一个值,或者如果 key 丢失则抛出异常(“丢失”的定义取决于 obj 的类型),而 obj[key]? 返回一个值,或者如果 key 丢失则返回 nil。
对于其他方法,则取决于情况。如果存在名为 foo 的方法以及同一个类型的另一个 foo? 方法,则意味着 foo 在某些条件下会抛出异常,而 foo? 在相同条件下会返回 nil。如果只有 foo? 变体而没有 foo,则它返回一个真值或假值(不一定是 true 或 false)。
以上所有内容的示例
Array#[](index)在越界时抛出异常,Array#[]?(index)在这种情况下返回 nil。Hash#[](key)如果键不在哈希表中则抛出异常,Hash#[]?(key)在这种情况下返回 nil。Array#first如果数组为空则抛出异常(没有“第一个”,因此“第一个”丢失),而Array#first?在这种情况下返回 nil。pop/pop?、shift/shift?、last/last? 也一样。- 有
String#includes?(obj)、Enumerable#includes?(obj)和Enumerable#all?,它们都没有非疑问变体。前面的方法确实返回 true 或 false,但这并不是必要条件。
for 循环¶
不支持 for 循环。建议使用 Enumerable#each。如果仍然想要使用 for,可以通过宏添加它们
macro for(expr)
{{expr.args.first.args.first}}.each do |{{expr.name.id}}|
{{expr.args.first.block.body}}
end
end
for i ∈ [1, 2, 3] do # You can replace ∈ with any other word or character, just not `in`
puts i
end
# note the trailing 'do' as block-opener!
方法¶
在 ruby 中,以下操作将引发参数错误
def process_data(a, b)
# do stuff...
end
process_data(b: 2, a: "one")
这是因为,在 ruby 中,process_data(b: 2, a: "one") 是 process_data({b: 2, a: "one"}) 的语法糖。
在 Crystal 中,编译器将把 process_data(b: 2, a: "one") 视为使用命名参数 b: 2 和 a: "one" 调用 process_data,这与 process_data("one", 2) 相同。
属性¶
ruby 的 attr_accessor、attr_reader 和 attr_writer 方法被使用不同名称的宏取代
| Ruby 关键字 | Crystal |
|---|---|
attr_accessor |
property |
attr_reader |
getter |
attr_writer |
setter |
示例
getter :name, :bday
此外,Crystal 为可空或布尔实例变量添加了访问器宏。它们在名称中包含问号 (?)
| Crystal |
|---|
property? |
getter? |
示例
class Person
getter? happy = true
property? sad = true
end
p = Person.new
p.sad = false
puts p.happy?
puts p.sad?
即使这是针对布尔值的,也可以指定任何类型
class Person
getter? feeling : String = "happy"
end
puts Person.new.feeling?
# => happy
在文档中详细了解 getter? 和/或 property?。
一致的点符号¶
例如,Ruby 中的 File::exists? 在 Crystal 中变为 File.exists?。
Crystal 关键字¶
Crystal 添加了一些新的关键字,这些关键字仍然可以作为方法名使用,但需要使用点显式调用:例如 self.select { |x| x > "good" }。
可用关键字¶
abstract do if nil? select union
alias else in of self unless
as elsif include out sizeof until
as? end instance_sizeof pointerof struct verbatim
asm ensure is_a? private super when
begin enum lib protected then while
break extend macro require true with
case false module rescue type yield
class for next responds_to? typeof
def fun nil return uninitialized
私有方法¶
Crystal 要求每个私有方法都以 private 关键字为前缀
private def method
42
end
从 Ruby 到 Crystal 的哈希语法¶
Crystal 引入了一种 Ruby 中没有的数据类型,即 NamedTuple。
通常在 Ruby 中,可以使用多种语法定义哈希表
# A valid ruby hash declaration
{
key1: "some value",
some_key2: "second value"
}
# This syntax in ruby is shorthand for the hash rocket => syntax
{
:key1 => "some value",
:some_key2 => "second value"
}
在 Crystal 中,情况并非如此。Hash 火箭 => 语法是在 Crystal 中声明哈希表的必要条件。
但是,Ruby 中的 Hash 简写语法在 Crystal 中创建了一个 NamedTuple。
# Creates a valid `Hash(Symbol, String)` in Crystal
{
:key1 => "some value",
:some_key2 => "second value",
}
# Creates a `NamedTuple(key1: String, some_key2: String)` in Crystal
{
key1: "some value",
some_key2: "second value",
}
NamedTuple 和常规 Tuple 的大小固定,因此它们最适合在编译时已知的那些数据结构。
伪常量¶
Crystal 提供了一些伪常量,它们提供有关正在执行的源代码的反射数据。
| Crystal | Ruby | 描述 |
|---|---|---|
__FILE__ |
__FILE__ |
正在执行的 Crystal 文件的完整路径。 |
__DIR__ |
__dir__ |
正在执行的 Crystal 文件所在的目录的完整路径。 |
__LINE__ |
__LINE__ |
正在执行的 Crystal 文件中的当前行号。 |
__END_LINE__ |
- | 调用块结束的行号。只能用作方法参数的默认值。 |
提示
有关 __DIR__ 与 __dir__ 的更多信息
用于 Ruby Gems 的 Crystal Shards¶
许多流行的 Ruby gems 已在 Crystal 中移植或重写。 以下是 Ruby Gems 的等效 Crystal Shards 列表。
有关 Ruby 和 Crystal 之间差异的其他问题,请访问 常见问题解答。