注释

# This is a comment
# 目前仅支持单行

字面量

字面量用于给一些基础类型赋值

类型 示例
Nil nil
Bool true,false
Integers 18, -12, 19_i64, 14_u32,64_u8
Floats 1.0, 1.0_f32, 1e10, -0.5
Char 'a', '\n', 'あ'
String "foo\tbar", %("あ"), %q(foo #{foo})
Symbol :symbol, :"foo bar"
Array [1, 2, 3], [1, 2, 3] of Int32, %w(one two three)
Array-like Set{1, 2, 3}
Hash {"foo" => 2}, {} of String => Int32
Hash-like MyType{"foo" => "bar"}
Range 1..9, 1...10, 0..var
Regex /(foo)?bar/, /foo #{foo}/imx, %r(foo/)
Tuple {1, "hello", 'x'}
NamedTuple {name: "Crystal", year: 2011}, {"this is a key": 1}
Proc ->(x : Int32, y : Int32) { x + y }
- Nil #代表没有值 
nil  

- Bool 
true,false

- Integers
Int8~ Int64, UInt8~ UInt64 #支持下划线, 
1 #Int32
1_i8 # Int8
1_i64 # Int64
1_u64 # UInt64
1_000_000 # 1000000

- Floats
1.0 # Float64
1.0_f32 # Float32
1_f32 # Float32
1_000_000.222_333 # 1000000.222333

- Char
使用单引号表示

- String
使用双引号表示
可以使用#{var}进行变量插值
a, b = 1, 2 
"sum: #{a + b }"
如果不想使用变量插值, 可以 %q(str_val) 来定义 
百分比%, %支持多种边界符(<>,[],(),||)来定义字符串, 这样的好处是当字符串是有“时,不需要转义。
%<my name is "zhukf">   => my name is "zhukf"
多行字符串
"abc
   def"   # 这种写法会返回多行字符串  =>   abc\n      def
HereDoc, 以 <<-标志字符串 开始  , 以开头为标志字符串的行结束 
<<-SOME
hello
SOME.upcase # => "HELLO"
def upcase(string)
  string.upcase
end

- Symbol
symbol被当成一个不可重复的常量
:hello
:good_bye

- String
[1, 2, 3]          # => Array(Int32)
[1, "hello", 'x']  # => Array(Int32 | String | Char)
array_of_int_or_string = [1, 3, 4] of Int32 | String  # => Array(Int32 | String)
array_of_int_or_string + ["foo"]                      # => [1, 2, 3, "foo"]
空的数组必须要声明类型
[] of Int32  # => Array(Int32).new
数组类型的字面量 {1,2,344 }
可以用于任何数据类型,只要它支持 <<方法,并且构造子接受参数 

- Hash
{"one" => 1, "two" => 2}
{} of Int32 => Int32 # => Hash(Int32, Int32).new

- Range
x..y # 包含y
x...y # 不包含y

- Regex
使用 /分割, 并且使用PCRE语法。
foo_or_bar = /foo|bar/
heeello    = /h(e+)llo/
integer    = /\d+/

- 元组
tuple = {1, "hello", 'x'} # Tuple(Int32, String, Char)
tuple[0]                  #=> 1       (Int32)
tuple[1]                  #=> "hello" (String)
tuple[2]                  #=> 'x'     (Char)

- NamedTuple
tuple = {name: "Crystal", year: 2011} # NamedTuple(name: String, year: Int32)
tuple[:name] # => "Crystal" (String)
tuple[:year] # => 2011      (Int32)

- Proc
可以看作是一个带有上下文的函数入口
# 不带参数的Proc
->{ 1 } # Proc(Int32)
# 带一个参数的
->(x : Int32) { x.to_s } # Proc(Int32, String)
# 带两个参数的
->(x : Int32, y : Int32) { x + y } # Proc(Int32, Int32, Int32)
# 接收一个Int32 并返回一个String类型
Proc(Int32, String)
# 不接收参数也没有返回值
Proc(Void)
# 接收两个参数(Int32和String类型) ,并返回一个Char类型
Proc(Int32, String, Char)
# 调用Proc
proc = ->(x : Int32, y : Int32) { x + y }
proc.call(1, 2) #=> 3
# 调用定义好的方法
def plus_one(x)
  x + 1
end
proc = ->plus_one(Int32)
proc.call(41) #=> 42

赋值

local = 1  # 变量赋值
@instance = 2 # 实例属性赋值
@@class = 3 # 静态类属性赋值

#同时支持的一些操作符
local += 1
# +, -, *, /, %, |, &, ^, **, <<, >>   #这些也被上面的表达式支持
local ||= 1 # same as: local || (local = 1)
local &&= 1 # same as: local && (local = 1)
#类和对象的支持
person.name = "John"
objects.[]=(2, 3) # same as : objects[2] = 3
objects.[](2, 3) # same as: objects[2, 3]
person.name ||= "John" # same as: person.name || (person.name = "John")
person.name &&= "John" # same as: person.name && (person.name = "John")
objects[1] += 2        # same as: objects[1] = objects[1] + 2
objects[1] ||= 2       # same as: objects[1]? || (objects[1] = 2)
objects[1] &&= 2       # same as: objects[1]? && (objects[1] = 2)

多重赋值

name, age = "Crystal", 1
a, b = b, a
name, age, source = "Crystal,1,github".split(",")
names = "John", "Peter", "Jack" # same as : names = ["John", "Peter", "Jack"]
# 类和对象的支持
objects[1], objects[2] = 3, 4

本地变量

本地变量的类型是在使用时被确定而不是在初始化时。

flower = "Tulip"  # 这时类型为 String
flower = 1 # 这时为 Int32

下划线是一个保留的字符(定义类型时表示任意类型), 变量定义不要以下划线开头。

流程控制

if

a = 2 if some_condition

if 1 > 2
  3
else
  nil
end

# 三元玩法
a = 1 > 2 ? 3 : 4

a = if 1 > 2 # if作为表达式直接给变量赋值
      3
    else
      4
    end

if a = some_expression
  # here a is not nil
end

if a && b
  # here both a and b are guaranteed not to be Nil
end

# if表达式判断对于类和对象的属性无效
# 方法一: 需要先将属性值赋给一个本地变量
if a = @a
  # here a can't be nil
end

# 方法二: use `Object#try` found in the standard library
@a.try do |a|
  # here a can't be nil
end

if var.is_a?(...)  # 判断var的类型是否是参数指定的类型
if a.is_a(Number) && if a.is_a(String)

if var.responds_to?(...) # 判断var是否具有参数指定的方法
if a.responds_to(:abs ) 

if var.nil? # 判断var是否为空

if ! 
if !b.is_a?(Int32)

unless

同if相反

unless some_condition
  then_expression
else
  else_expression
end

case

...略,请自行参阅

while

支持next ,break进行控制

while some_condition
  if condition1
    next
  end

  do_this
  if condition 
    break
  end

end

util

...略

&& , ||

可用于链式操作

some_exp1 || some_exp2
some_exp1 && some_exp2

require调用外部文件

require只接收一个字符串类型参数

require "filename"  # 自动从标准项目的lib目录查找并引用文件
require "./filename" # 相对于当前文件路径引用外部文件
require "../src/project"

require "foo/bar/baz" # 将会查找 "foo/bar/baz.cr" 或"foo/bar/baz/baz.cr" 在require path.
require "../../foo/bar/baz" 将会查找 "foo/bar/baz.cr" 或"foo/bar/baz/baz.cr" 相对于当前文件

require "foo/*"  # 查找 foo目录下的文件
require "foo/**"  # 查找foo及其子目录中的文件

类型和方法

类和方法

Crystal中一切都是对象

person = Person.new  # 创建一个对象 

# 定义一个类
class Person
  def initialize(name : String)  # 构造子 ,没有赋于具体值的属性需要指定类型
    @name = name
    @age = 0
  end
  # 也可以这样写
  def initialize(@name : String) # 直接在此处定义了一个属性
    @age = 0
  end

  def name
    @name
  end

  def age
    @age
  end
end

# 调用 
john = Person.new "John"
peter = Person.new "Peter"

john.name #=> "John"
john.age #=> 0

peter.name #=> "Peter"

# getter 和 setter方法 。Crystal在标准库中已经定义了相关的宏简化操作 ,参看
[getter](https://crystal-lang.org/api/0.24.1/Object.html#getter%28%2Anames%29-macro)
[settter](https://crystal-lang.org/api/0.24.1/Object.html#setter%28%2Anames%29-macro)
[property](https://crystal-lang.org/api/0.24.1/Object.html#property%28%2Anames%29-macro)

# 方法重载,previous_def
class Person
  def become_older
    @age += 1
  end
end

class Person
  def become_older
    @age += 2
  end
end

person = Person.new "John"
person.become_older
person.age #=> 2

# 重载方法中调用之前的定义 
class Person
  def become_older
    @age += 1
  end
end

class Person
  def become_older
    previous_def
    @age += 2
  end
end

person = Person.new "John"
person.become_older
person.age #=> 3

类型推断

crystal的设计哲学是尽可能的不要求显式指定数据类型,但也有一些情况下是必须指定类型的。

class Person
  def initialize(@name)
    @age = 0
  end
end

在上面代码中@name没有指明类型,crystal编译器会扫描所有Person被调用的地方,这样会有以下问题:

  1. 代码可读性差
  2. 不利于编译优化,当代码变复杂时编译时间会变的很长

因此,需要指明类和实例的属性变量类型。

直接指定数据类型


class Person
  @name : String
  @age : Int32

  def initialize(@name)
    @age = 0
  end
end

不指定类型

编译器会使用一系列的规则去匹配,当没有规则匹配成功时变量的类型将会是一个包含各种类型的联合类型。在变量没有初始化的情况下,联合中还将包括Nil类型。 规则有很多,但常用的一般是前3个匹配,无需了解。当编译器报错时,显式的指定类型即可。 下面的规则对于类和实例都适用:

  1. 向变量赋于一个字面量值, 下面的例子@name将被推断为String类型,@age将是Int32类型。
class Person
  def initialize
    @name = "John Doe"
    @age = 0
  end
end
  1. 使用类的new方法进行变量赋值,下面的例子@address的类型将被推断为Address
class Person
  def initialize
    @address = Address.new("somewhere")
  end
end

# 普通类型也一样适用 
class Something
  def initialize
    @values = Array(Int32).new
  end
end
  1. 用指定类型的参数进行赋值
class Person
  def initialize(name : String)
    @name = name  # @name将被推断为String类型
  end
end

#精简语法
class Person
  def initialize(@name : String)
  end
end
# 这个例子,编译会认为@name是String类型, 在编译时将报错
class Person
  def initialize(name : String)
    name = 1
    @name = name
  end
end
  1. 使用一个有返回值类型的类方法赋值
class Person
  def initialize
    @address = Address.unknown
  end
end

class Address
  # No need for a return type annotation here
  def self.unknown
    new("unknown")
  end

  def initialize(@name : String)
  end
end
  1. 使用一个带有默认值的参数赋值
class Person
  def initialize(name = "John Doe")
    @name = name
  end
end

class Person
  def initialize(@name = "John Doe")
  end
end
  1. 使用Lib库的调用结果赋值
class Person
  def initialize
    @age = LibPerson.compute_default_age
  end
end

lib LibPerson
  fun compute_default_age : Int32
end
  1. 使用out lib expression 因为lib function必定有指定类型,编译器可以使用out指定参数类型(通常是指针)
# @age类型是Int32
class Person
  def initialize
    LibPerson.compute_default_age(out @age)
  end
end

lib LibPerson
  fun compute_default_age(age_ptr : Int32*)
end
  1. 其它规则 if: 将会对所有的流程分支进行扫描,推断的类型一般是所有分支产生类型的一个联合。 @abc ||= 55: @abc类型将被推断为 Int32 | Nil 常量赋值推断:
# @luck_number类型为Int32
class SomeObject
  DEFAULT_LUCKY_NUMBER = 42

  def initialize(@lucky_number = DEFAULT_LUCKY_NUMBER)
  end
end

联合类型(Union types)

表达式或变量可以由多种类型组成,称之为联合。

if 1 + 2 == 3
  a = 1
else
  a = "hello"
end

a # : Int32 | String

上面的代码片段,a将获取 Int32和String两种类型,这个是在编译时由编译器确定。在运行时,a只可能取得一种类型。

# The runtime type
a.class # => Int32

# The compile-time type
typeof(a) # => Int32 | String

如果有需要,变量可以在编译时被定义为联合类型

# set the compile-time type
a = 0.as(Int32|Nil|String)
typeof(a) # => Int32 | Nil | String

联合的类型规则 :一般情况下相T1和T2两种类型联合,联合的类型将会是T1 | T2 , 但也有例外。 1. 继承相同Class的类的联合,返回类型为 class+

class Foo
end

class Bar < Foo
end

class Baz < Foo
end

bar = Bar.new
baz = Baz.new

# Here foo's type will be Bar | Baz,
# but because both Bar and Baz inherit from Foo,
# the resulting type is Foo+
foo = rand < 0.5 ? bar : baz
typeof(foo) # => Foo+

  1. 相同大小的元组形成的联合,返回类型为所有元组数据类型的集合。
t1 = {1, "hi"}   # Tuple(Int32, String)
t2 = {true, nil} # Tuple(Bool, Nil)

t3 = rand < 0.5 ? t1 : t2
typeof(t3) # Tuple(Int32 | Bool, String | Nil)
  1. 带有相同Key的命名元祖构成的集合, 看例子
t1 = {x: 1, y: "hi"}   # Tuple(x: Int32, y: String)
t2 = {y: true, x: nil} # Tuple(y: Bool, x: Nil)

t3 = rand < 0.5 ? t1 : t2
typeof(t3) # NamedTuple(x: Int32 | Nil, y: String | Bool)

重载

类和类的方法都可以重载,方法重载可以写在同一个类定义中 也可以重复定义相同的类。最新的定义将覆盖之前的定义。

默认值和命名参数

class Person
  def become_older(by = 1)
    @age += by
  end
end

def some_method(x, y = 1, z = 2, w = 3)
  # do something...
end

some_method 10                   # x: 10, y: 1, z: 2, w: 3
some_method 10, z: 10            # x: 10, y: 1, z: 10, w: 3
some_method 10, w: 1, y: 2, z: 3 # x: 10, y: 2, z: 3, w: 1
some_method y: 10, x: 20         # x: 20, y: 10, z: 2, w: 3

some_method y: 10                # Error, missing argument: x

*与元组

通过使用*,方法可以接收不固定数量的参数。

def sum(*elements)
  total = 0
  elements.each do |value|
    total += value
  end
  total
end

sum 1, 2, 3    #=> 6
sum 1, 2, 3, 4.5 #=> 10.5

# 传入的参数在方法内部转化成一个元组
# elements is Tuple(Int32, Int32, Int32)
sum 1, 2, 3
# elements is Tuple(Int32, Int32, Int32, Float64)
sum 1, 2, 3, 4.5

# 如果方法还定义了其它的参数 ,在调用时 必须要指明参数名称 
def sum(*elements, initial = 0)  # 定义了参数的默认值
  total = initial
  elements.each do |value|
    total += value
  end
  total
end

sum 1, 2, 3 # => 6
sum 1, 2, 3, initial: 10 # => 16

# 方法多态
def foo(*elements, x)
  1
end

def foo(*elements, y)
  2
end

foo x: "something" # => 1
foo y: "something" # => 2

# *参数 也可以只用一个*表示 这时它并不是一个参数,它的意思是: 在方法调用时 *位之后的参数必须是带名称的
def foo(x, y, *, z)
end

foo 1, 2, 3    # Error, wrong number of arguments (given 3, expected 2)
foo 1, 2       # Error, missing argument: z
foo 1, 2, z: 3 # OK

# 在元组之前加* , 可以把元组展开 传入方法中
def foo(x, y)
  x + y
end

tuple = {1, 2}
foo *tuple # => 3

# 双*(**) ,可以展开一个命名的元组 传入方法中
def foo(x, y)
  x - y
end

tuple = {y: 3, x: 10}
foo **tuple # => 7

# 在定义时指定**
def foo(x, **other)
  # Return the captured named arguments as a NamedTuple
  other
end

foo 1, y: 2, z: 3    # => {y: 2, z: 3}
foo y: 2, x: 1, z: 3 # => {y: 2, z: 3}

类型限制

def add(x : Number, y : Number)
  x + y
end

# Ok
add 1, 2 # Ok

# Error: no overload matches 'add' with types Bool, Bool
add true, false

# self restriction
...略

# 字面量的类型限制
定义一个方法只接收参数Int32(不是实例), 可以使用 .class后缀
def foo(x : Int32.class)
end

foo Int32  # OK
foo String # Error

# 可变参数中的类型限制 
def foo(*args : Int32)
end

def foo(*args : String)
end

foo 1, 2, 3       # OK, invokes first overload
foo "a", "b", "c" # OK, invokes second overload
foo 1, 2, "hello" # Error
foo()             # Error , 空参数不能与上面的任意一个匹配,必须重写一个不接收参数的foo方法。

# 使用Object作为类型约束,是用来配对任意类型参数的简单方法。
def foo(*args : Object)
end

foo() # Error
foo(1) # OK
foo(1, "x") # OK

# 自由类型变量
通过使用forall , 可以使用一个或多个参数的类型来做类型限制
def foo(x : T) forall T
  T
end

foo(1)       #=> Int32
foo("hello") #=> String

返回类型

方法的返回类型是编译器自动确定的,但是也可以手动指定类型

def some_method : String
  "hello"
end

# 返回nil
指定Nil类型返回,程序就不需要在末尾再特别返回nil。使用Void也可以达到相同效果,在C绑定时,比较偏向使用Void
def some_method : Nil
  1 + 2
end

some_method # => nil


方法参数

...略

操作符

可以给类型赋于操作

struct Vector2
  getter x, y

  def initialize(@x : Int32, @y : Int32)
  end

  def +(other)
    Vector2.new(x + other.x, y + other.y)
  end
end

v1 = Vector2.new(1, 2)
v2 = Vector2.new(3, 4)
v1 + v2               #=> Vector2(@x=4, @y=6)

二进制操作符

+ – addition
- – subtraction
* – multiplication
/ – division
% – modulo
& – bitwise and
| – bitwise or
^ – bitwise xor
** – exponentiation
<< – shift left, append
>> – shift right
== – equals
!= – not equals
< – less
<= – less or equal
> – greater
>= – greater or equal
<=> – comparison
=== – case equality

下标操作符

[]  # array index (越界将报错)
[]? # array index (越界返回nil)
[]= # array index 赋值

# 例子
class MyArray
  def [](index)
    # ...
  end

  def [](index1, index2, index3)
    # ...
  end

  def []=(index, value)
    # ...
  end
end

array = MyArray.new

array[1]       # invokes the first method
array[1, 2, 3] # invokes the second method
array[1] = 2   # invokes the third method

array.[](1)       # invokes the first method
array.[](1, 2, 3) # invokes the second method
array.[]=(1, 2)   # invokes the third method

可见性

使用private关键词定义: private def xxx

1. 私有方法: 只能在类的内部以及子类中调用 并且不需要加 self
2. 私有类型: 
class Foo
  private class Bar
  end

  Bar      # OK
  Foo::Bar # Error
end

Foo::Bar # Error

受保护的(protected)方法: protected def xxx 只能被与当前类型同类型的,或与当前类型在相同命名空间中的(class, struct, module, etc.)实例调用。 例子, ...略

在最外层定义的私有方法 和私有类,只能在定义的当前文件中可见。

继承

class Person
  def initialize(@name : String)
  end

  def greet
    puts "Hi, I'm #{@name}"
  end
end

class Employee < Person
end

employee = Employee.new "John"
employee.greet # "Hi, I'm John"

如果子类中定义了initialize方法,父类的构造子将不被调用。

class Person
  def initialize(@name : String)
  end
end

class Employee < Person
  def initialize(@name : String, @company_name : String)
  end
end

Employee.new "John", "Acme" # OK
Employee.new "Peter" # Error: wrong number of arguments
                     # for 'Employee:Class#new' (1 for 2)

子类可以重载父类的方法(重写或者使用类型限制实现多态)。 可以使用super关键词来调用父类方法

class Person
  def greet(msg)
    puts "Hello, #{msg}"
  end
end

class Employee < Person
  def greet(msg)
    super # Same as: super(msg)
    super("another message")  # 可以另外指定参数
  end
end

抽象类

...略

类方法

不需要通过实例化就可以调用

module CaesarCipher
  def self.encrypt(string : String)
    string.chars.map{ |char| ((char.upcase.ord - 52) % 26 + 65).chr }.join
  end
end

CaesarCipher.encrypt("HELLO") # => "URYYB"

def CaesarCipher.decrypt(string : String)
  encrypt(string)
end

类属性

在变量名称前使用 @@ , 存在于静态类中 所有实例可以共享之

class Counter
  @@instances = 0

  def initialize
    @@instances += 1
  end

  def self.instances
    @@instances
  end
end

Counter.instances #=> 0
Counter.new
Counter.new
Counter.new
Counter.instances #=> 3

解构方法

在一个实例被回收时调用 finalize

class Foo
  def finalize
    # Invoked when Foo is garbage-collected
    # Use to release non-managed resources (ie. C libraries, structs)
  end
end

模块

模块有两个用途。1,用作命名空间用来隔离定义类和方法,常量等。2,可以当成代码片段插入其它代码中。 使用include 或extend

module ItemsSize
  def size
    items.size
  end
end

class Items
  include ItemsSize

  def items
    [1, 2, 3]
  end
end

items = Items.new
items.size #=> 3

泛型

...略

结构体

struct Point
  property x, y

  def initialize(@x : Int32, @y : Int32)
  end
end

结构体可以当成类来使用, 它和类的区别在于: 1. 存在于栈中,而不是堆 2. 传值调用 ,而不是引址 3. 继承自Struct 4. 不能通过一个抽象的struct来继承 ...略

常量

以大写字母开头的变量, 一般情况下全部使用大写。

PI = 3.14

module Earth
  RADIUS = 6_371_000
end

PI #=> 3.14
Earth::RADIUS #=> 6_371_000

# 可以通过调用表达式来定义常量
TEN = begin
  a = 0
  while a < 10
    a += 1
  end
  a
end

TEN #=> 10

内置常量

  1. LINE , 程序所在文件的当前行号
  2. END_LINE ,程序所在的代码块在当前文件中的结束行号
  3. FILE, 当前程序文件的完整路径
  4. DIR, 当前程序文件所在的目录

枚举类型

枚举是一组整型值的集合,每个值都有关联的名称。

enum Color
  Red
  Green
  Blue
end

枚举的值从0开始,每次增1, 值可以被覆盖, 见例子

enum Color
  Red         # 0
  Green       # 1
  Blue   = 5  # overwritten to 5
  Yellow      # 6 (5 + 1)
end

枚举中的常量的类型是枚举本身

Color::Red #:: Color

取值

Color::Green.value #=> 1

# 默认情况下 ,value的类型为Int32, 但是也可以被改变
enum Color : UInt8
  Red
  Green
  Blue
end

Color::Red.value #:: UInt8

枚举的数据类型只允许整型。 //todo

进程和块

下面的方法会输出 Hello两次 。

def twice  # 使用yield关键词定义块方法,编译器可以识别到 
  yield
  yield
end

twice do
  puts "Hello!"  # 块代码
end

# 更加直观的写法, 使用 &
def twice(&block)
  yield
  yield
end

调用块方法使用 do...end 或 {}, 两者一般情况下是等价的。

twice() do
  puts "Hello!"
end

twice do
  puts "Hello!"
end

twice { puts "Hello!" }

区别在于 do...end使用left most模式, {}使用right most模式。

foo bar do
  something
end

# The above is the same as
foo(bar) do
  something
end

foo bar { something } # same as : foo(bar { something })

# 多个参数 , 实际传入的参数入量可以少于yield后面的参数数量
def many
  yield 1, 2, 3
end

many do |x, y, z|
  puts x + y + z
end

# 单参数调用简写
method do |argument|
  argument.some_method
end

# 同上 
method &.some_method
method &.some_method(arg1, arg2)

# 操作符也行。。。
method &.+(2)
method &.[index]

# 类型限制
def transform_int(start : Int32, &block : Int32 -> Int32)
  result = yield start
  result * 2
end

transform_int(3) { |x| x + 2 } #=> 10
transform_int(3) { |x| "foo" } # Error: expected block to return Int32, not String

# break ...略
# next ...略
# with...yield  ...略

# 块参数展开, 块代码参数可以被小括号展开 
array = [{1, "one"}, {2, "two"}]
array.each do |(number, word)|
  puts "#{number}: #{word}"
end

# 上面的等价代码   
array = [{1, "one"}, {2, "two"}]
array.each do |arg|
  number = arg[0]
  word = arg[1]
  puts "#{number}: #{word}"
end

捕获(生成)块

块代码可以转换成一个带有上下文的进程。 必须把块当成一个方法的block参数并指定其输入输出的参数类型。

def int_to_int(&block : Int32 -> Int32)
  block
end

proc = int_to_int { |x| x + 1 }
proc.call(1) #=> 2

# 不指定返回类型,将没有返回 
def some_proc(&block : Int32 ->)
  block
end

proc = some_proc { |x| x + 1 }
proc.call(1) # void

# 使用下划线 返回任意类型值
def some_proc(&block : Int32 -> _)
  block
end

proc = some_proc { |x| x + 1 }
proc.call(1) # 2

proc = some_proc { |x| x.to_s }
proc.call(1) # "1"

注: return和break不能使用在block中。

Proc字面量

def some_proc(&block : Int32 -> Int32)
  block
end

x = 0
proc = ->(i : Int32) { x += i }  # 字面量
proc = some_proc(&proc)
proc.call(1)  #=> 1
proc.call(10) #=> 11
x #=> 11

# 进程也可以从现有的方法创建 
def add(x, y)
  x + y
end

adder = ->add(Int32, Int32)
adder.call(1, 2) #=> 3

块的转发执行

在yield可用的情况下,应避免使用闭包写法。并且break,next在捕获的闭包中无法使用。

# 使用yield进行转发 
def foo
  yield 1
end

def wrap_foo
  puts "Before foo"
  foo do |x|
    yield x
  end
  puts "After foo"
end

wrap_foo do |i|
  puts i
end

# 使用闭包转发, 效率会降低
def foo
  yield 1
end

def wrap_foo(&block : Int32 -> _)
  puts "Before foo"
  foo(&block)
  puts "After foo"
end

wrap_foo do |i|
  puts i
end

闭包

捕获块和proc字面量隔离self和本地变量形成闭包,下面的例子来说明

x = 0
proc = ->{ x += 1; x }
proc.call #=> 1
proc.call #=> 2
x         #=> 2

也可以是在方法中返回一个进程

def counter
  x = 0
  ->{ x += 1; x }
end

proc = counter
proc.call #=> 1
proc.call #=> 2
# 上面的代码, x被proc字面量捕获并放置于堆中作为proc的上下文。一般情况下本地变量被放入栈中 并且在方法结束时被释放。

别名

使用别名可以给类型指定一个其它名字, 用于方便书写。

alias PInt32 = Pointer(Int32)
ptr = PInt32.malloc(1) # : Pointer(Int32)

#
alias RecArray = Array(Int32) | Array(RecArray)

ary = [] of RecArray
ary.push [1, 2, 3]
ary.push ary
ary #=> [[1, 2, 3], [...]]


异常处理

使用raise抛出异常

raise "OH NO!"
raise Exception.new("Some error")

# 继承Exception类可以自定义异常类
class MyException < Exception
end

处理异常使用rescue

begin
  raise "OH NO!"
rescue
  puts "Rescued!"
end

# 获取异常信息可以在rescue后加一个参数
begin
  raise "OH NO!"
rescue ex
  puts ex.message
end

# Output: OH NO!

# 捕获自定义异常
begin
  raise MyException.new("OH NO!")
rescue MyException
  puts "Rescued MyException"
end

begin
  raise MyException.new("OH NO!")
rescue ex : MyException
  puts "Rescued MyException: #{ex.message}"
end

如果没有捕获到任何异常,可以使用else

begin
  something_dangerous
rescue
  # execute this if an exception is raised
else
  # execute this if an exception isn't raised
end

ensure, 不管有无异常 都会执行

begin
  something_dangerous
rescue
  # ...
else
  # ...
ensure
  # this will always be executed
end

语法规范

...略

类型推断

...略

宏是一些预编译的方法(在编译时被加入到抽像语法树中),这些方法必须是完整有效的Crystal代码。

macro define_method(name, content)
  def {{name}}
    {{content}}
  end
end

# This generates:
#
#     def foo
#       1
#     end
define_method foo, 1

foo #=> 1

宏作用域

宏被定义在顶层作用域,所有代码中都可用。如果某个宏被标记为private 那么它只在当前文件中可见。 宏也可以定义在类和模块中, 并且在定义它的作用域中可见。程序也会在先祖域中搜索宏(父类 及被包含的模块中)。 For example, a block which is given an object to use as the default receiver by being invoked with with … yield can access macros defined within that object’s ancestors chain:

class Foo
  macro emphasize(value)
    "***#{ {{value}} }***"
  end

  def yield_with_self
    with self yield
  end
end

Foo.new.yield_with_self { emphasize(10) } #=> "***10***"

定义在类中或模块中的宏 也可以在外部被调用。

class Foo
  macro emphasize(value)
    "***#{ {{value}} }***"
  end
end

Foo.emphasize(10) # => "***10***"

插值

使用 {{...}} 在语法树中进行插入值。

宏调用

你可以在编译时调用一些方法, 这些方法被定义在Crystal::Macros 模块中。

条件语句

# 你可以使用{% if condition %} ... {% end %} 来按条件生成代码。宏的条件语句可以在宏定义之外的地方使用。
macro define_method(name, content)
  def {{name}}
    {% if content == 1 %}
      "one"
    {% else %}
      {{content}}
    {% end %}
  end
end

define_method foo, 1
define_method bar, 2

foo #=> one
bar #=> 2

迭代

迭代语句也可以在宏定义之外的地方使用。

macro define_dummy_methods(names)
  {% for name, index in names %}
    def {{name.id}}
      {{index}}
    end
  {% end %}
end

define_dummy_methods [foo, bar, baz]

foo #=> 0
bar #=> 1
baz #=> 2

#
macro define_dummy_methods(hash)
  {% for key, value in hash %}
    def {{key.id}}
      {{value}}
    end
  {% end %}
end
define_dummy_methods({foo: 10, bar: 20})
foo #=> 10
bar #=> 20

#
{% for name, index in ["foo", "bar", "baz"] %}
  def {{name.id}}
    {{index}}
  end
{% end %}

foo #=> 0
bar #=> 1
baz #=> 2

宏方法定义

todo

钩子

在一些场景下,一些特殊的宏被当成钩子来使用。这些场景是 inherited, included, extended 和 method_missing

1.inherited在编译时被触发,如果定义了子类。@type is the inheriting type.
2.included在编译时被触发,当一个模块被include时。@type is the including type.
3.extended在编译时被触发,当一个模块被extend时。@type is the extending type.
4.method_missing 在编译时被触发,当方法不存在时.
例子: inherited

class Parent
  macro inherited
    def lineage
      "{{@type.name.id}} < Parent"
    end
  end
end

class Child < Parent
end

Child.new.lineage #=> "Child < Parent"

例子: method_missing

macro method_missing(call)
  print "Got ", {{call.name.id.stringify}}, " with ", {{call.args.size}}, " arguments", '\n'
end

foo          # Prints: Got foo with 0 arguments
bar 'a', 'b' # Prints: Got bar with 2 arguments

变量刷新

//todo

属性

...略

底层操作

crystal提供了一些底层的操作,用于C绑定。

pointerof

返回一个指向变量或对象变量的指针。

a = 1

ptr = pointerof(a)
ptr.value = 2

a #=> 2

# 操作对象的变量
class Point
  def initialize(@x, @y)
  end

  def x
    @x
  end

  def x_ptr
    pointerof(@x)
  end
end

point = Point.new 1, 2

ptr = point.x_ptr
ptr.value = 10

point.x #=> 10

因为pointerof使用了指针 ,所以它是不安全的。

sizeof

返回一个Int32类型的指定对象的字节大小。

sizeof(Int32)  #=> 4
sizeof(Int64)  #=> 8

对于引用类型 ,大小等于指针的大小。

# On a 64 bits machine
sizeof(Pointer(Int32)) #=> 8
sizeof(String)         #=> 8

instance_sizeof

返回一个类的实例的大小。

class Point
  def initialize(@x, @y)
  end
end

Point.new 1, 2

# 2 x Int32 = 2 x 4 = 8
instance_sizeof(Point) #=> 12

结果是12不是8的原因为,编译器总是会包含一个额外的Int32用于记录对象的type ID

声明一个未初始化的变量

crystal允许声明这种变量

x = uninitialized Int32
x #=> some random value, garbage, unreliable

This is unsafe code and is almost always used in low-level code for declaring uninitialized StaticArray buffers without a performance penalty:

buffer = uninitialized UInt8[256]

The buffer is allocated on the stack, avoiding a heap allocation.

The type after the uninitialized keyword follows the type grammar.

编译时标志位

...略

C语言绑定

crystal允许你直接调用现成的C库。此外,crystal还提供了像out, to_unsafe之类的操作,使用绑定是非常便利的。

Lib

lib声明了一组C函数和类型。

@[Link("pcre")]
lib LibPCRE
end

一个lib的名称通常以Lib开头, 但不是强制的。 属性用来传递标志给连接器, 用于查找外部库。@[Link("pcre")]将会传递 -lpcre给连接器,但是连接器会优先尝试pkg-config 。 @[Link(ldflags: "...")]将会不作修改直接传递这些标志给连接器,如@[Link(ldflags: "-lpcre")]。这里还有一个使用反引号的技巧, 如

@[Link(ldflags: "pkg-config libpcre --libs")]

@[Link(framework: "Cocoa")]将传递 -framework Cocoa 到连接器 (only useful in Mac OS X).

fun

fun用于在lib内部定义一个C绑定函数。

lib C
  # In C: double cos(double x)
  fun cos(value : Float64) : Float64
end

一旦绑定成功,函数就在C类型内部生效 ,使用就像是调用一个类的方法一样。

C.cos(1.5) #=> 0.0707372

如果函数没有参数 ,可以省略括号。

lib C
  fun getch : Int32
end

C.getch

如果函数没有返回 ,则返回类型也可以省略。

lib C
  fun srand(seed : UInt32)
end

C.srand(1_u32)

绑定可变参数的函数

lib X
  fun variadic(value : Int32, ...) : Int32
end

X.variadic(1, 2, 3, 4)

注意:调用C绑定函数必须要正确声明参数类型。

因为crystal的函数必须用小写开头,所以在使用fun时也必须采用小写 。如果C库中存在大写开头的函数,可以像下面这样写:

lib LibSDL
  fun init = SDL_Init(flags : UInt32) : Int32
end

如果名称不是有效的函数或类型,可以使用字符串的形式

lib LLVMIntrinsics
  fun ceil_f32 = "llvm.ceil.f32"(value : Float32) : Float32
end

可以用于C绑定的数据类型有: 1. Primitive types (Int8, ..., Int64, UInt8, ..., UInt64, Float32, Float64)

  1. Pointer types (Pointer(Int32), which can also be written as Int32*)

  2. Static arrays (StaticArray(Int32, 8), which can also be written as Int32[8])

  3. Function types (Function(Int32, Int32), which can also be written as Int32 -> Int32)

  4. Other struct, union, enum, type or alias declared previously.

  5. Void: the absence of a return value.

  6. NoReturn: similar to Void, but the compiler understands that no code can be executed after that invocation.

  7. Crystal structs marked with the @[Extern] attribute

标准库定义了LibC作为C类型的别名,比如有像int, short, size_t之类的类型。

lib MyLib
  fun my_fun(some_size : LibC::SizeT)
end

注意: C的char在crystal中是UInt8, 所以const char 或char是 UInt8 。The Char type in Crystal is a unicode codepoint so it is represented by four bytes, making it similar to an Int32, not to an UInt8. There's also the alias LibC::Char if in doubt.

//todo