常量求值

const 变量

const 变量是一种特殊的变量,它可以定义在编译时完成求值,并且在运行时不可改变的变量。

const 变量与 let/var 声明的变量的区别是必须在定义时就初始化,且必须用 const 表达式初始化。因此 const 变量的类型只能是 const 表达式支持的类型。

与 let/var 一样,const 变量也支持省略类型。

const 表达式见下文定义。

const a: Int64 = 0 // ok
const b: Int64 // error, b is uninitialized
const c = f() // error, f() is not const expression
const d = 0 // ok, the type of d is Int64

func f(): Unit {}

const 变量定义后可以像 let/var 声明的变量一样使用。与 let/var 声明的变量不同,const 由于在编译时就可以得到结果,可以大幅减少程序运行时需要的计算。

const a = 0

main(): Int64 {
    let b: Int64 = a // ok
    print(a) // ok
    let c: VArray<Int64, $0> = [] // ok
    return 0
}

const 变量可以是全局变量,局部变量,静态成员变量。const 变量不能在扩展中定义。

const a = 0 // ok

class C {
    const b = 0 // error, const member field must be modified by static
    static const c = 0 //ok

    var v: Int64

    init() {
        const d = 0 // ok
        v = b + c + d
    }
}

extend C {
    const e = 0 // error, const cannot be defined in extend
}

const 变量可以访问对应类型的所有实例成员,也可以调用对应类型的所有非 mut 实例成员函数。

struct Foo {
    let a = 0
    var b = 0

    const init() {}

    func f1() {}
    const func f2() {}
    mut func f3() {
        b = 123
    }
}

main(): Int64 {
    const v = Foo()
    print(v.a) // ok
    print(v.b) // ok
    v.f1() // ok
    v.f2() // ok
    v.f3() // error, f3 is mut function
    return 0
}

const 变量初始化后该类型实例的所有成员都是 const 的(深度 const,包含成员的成员),因此不能被用于左值。

struct Foo {
    let a = 0
    var b = 0

    const init() {}
}

func f() {
    const v = Foo()
    v.a = 1 // error
    v.b = 1 // error
}

const 表达式

某些特定形式的表达式,被称为 const 表达式,这些表达式具备了可以在编译时求值的能力。在 const 上下文中,这些是唯一允许的表达式,并且始终会在编译时进行求值。而在其它非 const 上下文,const 表达式不保证在编译时求值。

以下表达式都是 const 表达式。

  1. 数值类型、Bool、Unit、Rune、String 类型的字面量(不包含插值字符串)。
  2. 所有元素都是 const 表达式的 array 字面量(不能是 Array 类型),tuple 字面量。
  3. const 变量,const 函数形参,const 函数中的局部变量。
  4. const 函数,包含使用 const 声明的函数名、符合 const 函数要求的 lambda、以及这些函数返回的函数表达式。
  5. const 函数调用(包含 const 构造函数),该函数的表达式必须是 const 表达式,所有实参必须都是 const 表达式。
  6. 所有参数都是 const 表达式的 enum 构造器调用,和无参数的 enum 构造器。
  7. 数值类型、Bool、Unit、Rune、String 类型的算数表达式、关系表达式、位运算表达式,所有操作数都必须是 const 表达式。
  8. if、match、try、控制转移表达式(包含 return、break、continue、throw)、is、as。这些表达式内的表达式必须都是 const 表达式。
  9. const 表达式的成员访问(不包含属性的访问),tuple 的索引访问。
  10. const init 和 const 函数中的 this 和 super 表达式。
  11. const 表达式的 const 实例成员函数调用,且所有实参必须都是 const 表达式。

const 上下文

const 上下文是一类特定上下文,在这些上下文内的表达式都必须是 const 表达式,并且这些表达式始终在编译时求值。

const 上下文是指 const 变量初始化表达式。

const 函数

const 函数是一类特殊的函数,这些函数具备了可以在编译时求值的能力。在 const 上下文中调用这种函数时,这些函数会在编译时执行计算。而在其它非 const 上下文,const 函数会和普通函数一样在运行时执行。

const 函数与普通函数的区别是限制了部分影响编译时求值的功能,const 函数中只能出现声明、const 表达式、受限的部分赋值表达式。

  1. const 函数声明必须使用 const 修饰。
  2. 全局 const 函数和 static const 函数中只能访问 const 声明的外部变量,包含 const 全局变量、const 静态成员变量,其它外部变量都不可访问。const init 函数和 const 实例成员函数除了能访问 const 声明的外部变量,还可以访问当前类型的实例成员变量。
  3. const 函数中的表达式都必须是 const 表达式,const init 函数除外。
  4. const 函数中可以使用 let、const 声明新的局部变量。但不支持 var。
  5. const 函数中的参数类型和返回类型没有特殊规定。如果该函数调用的实参不符合 const 表达式要求,那这个函数调用不能作为 const 表达式使用,但仍然可以作为普通表达式使用。
  6. const 函数不一定都会在编译时执行,例如可以在非 const 函数中运行时调用。
  7. const 函数与非 const 函数重载规则一致。
  8. 数值类型、Bool、Unit、Rune、String 类型 和 enum 支持定义 const 实例成员函数。
  9. 对于 struct 和 class,只有定义了 const init 才能定义 const 实例成员函数。class 中的 const 实例成员函数不能是 open 的。struct 中的 const 实例成员函数不能是 mut 的。
const func f(a: Int64): Int64 { // ok
    let b = 6
    if (a > 5 && (b + a) < 15 ) {
        return a * a
    }
    return a
}

class A {
    var a = 0
}

const func f1(a: A): Unit { // ok
    return
}

const func f2(): A {
    return A() // error, A did not contains const init
}

接口中的 const 函数

接口中也可以定义 const 函数,但会受到以下规则限制。

  1. 接口中的 const 函数,实现类型必须也用 const 函数才算实现接口。
  2. 接口中的非 const 函数,实现类型使用 const 或非 const 函数都算实现接口。
  3. 接口中的 const 函数与接口的 static 函数一样,只有在该接口作为泛型约束的时候,受约束的泛型变元或变量才能使用这些 const 函数。
interface I {
    const func f(): Int64
    const static func f2(): Int64
}

const func g<T>(i: T) where T <: I {
    return i.f() + T.f2()
}

const init

有无 const init 决定了哪些自定义 struct/class 可以用在 const 表达式上。一个类型能否定义 const init 取决于以下条件。

  1. 如果当前类型是 class,则不能具有 var 声明的实例成员变量,否则不允许定义 const init。如果当前类型具有父类,当前的 const init 必须调用父类的 const init(可以显式调用或者隐式调用无参 const init),如果父类没有 const init 则报错。
  2. Object 类型的无参 init 也是 const init,因此 Object 的子类可以使用该 const init。
  3. 当前类型的实例成员变量如果有初始值,初始值必须要是 const 表达式,否则不允许定义 const init。
  4. const init 内可以使用赋值表达式对实例成员变量赋值,除此以外不能有其它赋值表达式。

const init 与 const 函数的区别是 const init 内允许对实例成员变量进行赋值(需要使用赋值表达式)

struct R1 {
    var a: Int64
    let b: Int64

    const init() { // ok
        a = 0
        b = 0
    }
}

struct R2 {
    var a = 0
    let b = 0

    const init() {} // ok
}

func zero(): Int64 {
    return 0
}

struct R3 {
    let a = zero()

    const init() {} // error,Initialization of a is not const expression
}

class C1 {
    var a = 0

    const init() {} // error,a can not be var binding
}

struct R4 {
    var a = C1()

    const init() {} // error,Initialization of a is not const expression
}

open class C2 {
    let a = 0
    let b = R2()

    const init() {} // ok
}

class C3 <: C2 {
    let c = 0

    const init() {} // ok
}