Constant Evaluation

const Variables

A const variable is a special variable that defines a variable that is evaluated during compilation and cannot be changed during running.

A const variable differs from a variable declared by let or var in that it must be initialized at the time of definition and must be assigned a const expression. Therefore, the type of a const variable can only be of types supported by const expressions.

Similar to let and var, const variables also support type omission.

The definition of a const expression is as follows:

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 {}

After being defined, a const variable can be used like a variable declared by let or var. Unlike variables declared by let or var, const can significantly reduce the computation required at runtime because its value can be obtained during compilation.

const a = 0

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

A const variable can be a global variable, local variable, or static member variable. It cannot be defined in the extension.

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 variables can access all instance members of the corresponding type or call all non-mut instance member functions of the corresponding type.

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
}

After a const variable is initialized, all members of instances of this type belong to const (including the members of members of the deep const), and therefore cannot be used as lvalues.

struct Foo {
    let a = 0
    var b = 0

    const init() {}
}

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

Const Expression

Certain forms of expressions, known as const expressions, have the ability to be evaluated at compile time. In the const context, these are the only allowed expressions and are always evaluated at compile time. In non-const contexts, const expressions do not guarantee evaluation at compile time.

The following are const expressions.

  1. Literals of the numeric, Bool, Unit, Rune, and String types (excluding interpolated strings)
  2. Array literals (not of Array type) and tuple literals where all elements are const expressions
  3. const variables, const function parameters, and local variables of const functions
  4. const functions, including functions whose names are declared using const, lambda expressions that meet the requirements of const functions, and function expressions returned by these functions
  5. const function calls (including const constructors) whose expressions and arguments are all const expressions
  6. enum constructor calls where all parameters are const expressions, and enum constructors without parameters
  7. Arithmetic expressions, relational expressions, and bitwise operation expressions of the numeric, Bool, Unit, Rune, and String types, whose operands are all const expressions
  8. if, match, try, is, and as expressions and control transfer expressions (including return, break, continue, and throw), whose expressions are all const expressions
  9. Member access expressions (excluding those for attribute access) and index access expressions of the tuple type in const expressions
  10. this and super expressions in the const init and const functions
  11. Function calls of const instance member functions of const expressions, where all arguments are const expressions

const Context

The const context is a specific type of context where all expressions must be const expressions, and these expressions are always evaluated at compile time.

The const context refers to the initialization expressions of const variables.

const Functions

A const function is a special function that can be evaluated during compilation. When being called in the const context, it performs calculation during compilation. In non-const contexts, it is executed at runtime, which is the same as normal functions.

The difference between const functions and regular functions is that the const function restricts some functionalities that affect the evaluation during compilation. const function can contain only declarations, const expressions, and a limited subset of assignment expressions.

  1. The declaration of a const function must be modified by const.
  2. Global const and static const functions can access only the external variables declared by const, including global const variables and static const member variables. const init functions and const instance member functions can access the external variables declared by const as well as the instance member variables of the current type.
  3. All expressions in const functions except const init functions must be const expressions.
  4. In const functions, let and const can be used to declare new local variables. However, var is not supported.
  5. There is no special requirement on parameter types and return types in const functions. If the argument of a function call does not meet the requirements of a const expression, the function call cannot be used as a const expression, but can still be used as a common expression.
  6. const functions are not necessarily executed during compilation. For example, they can be called in non-const functions during running.
  7. The overloading rules of const functions are the same as those of non-const functions.
  8. The numeric, Bool, Unit, Rune, String, and enum types can be used to define const instance member functions.
  9. For struct and class, const instance member functions can be defined only after a const init function is defined. const instance member functions in class cannot be open. const instance member functions in struct cannot be 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 Function in Interfaces

const functions can be defined in interfaces. The rules are as follows:

  1. The implementation type of a const function in an interface must also be a const function.
  2. For a non-const function in an interface, the implementation type can be a const or non-const function.
  3. Similar to a static function, a const function in an interface can be used by constrained generic variables only when the interface is used as a generic constraint.
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

The existence of a const init function determines which custom struct or class can be used in a const expression. Whether a const init function can be defined for a type depends on the following conditions:

  1. If the current type is class, an instance member variable declared by var cannot be used. Otherwise, a const init function cannot be defined. If the current type has a superclass, the current const init function must call the const init function of the superclass. (A const init function without parameters can be called explicitly or implicitly.) If the superclass does not have a const init function, an error is reported.
  2. An init without parameters of the Object type is also a const init function. Therefore, the subtypes of the Object type can use this const init function.
  3. If an instance member variable of the current type has an initial value, the initial value must be a const expression. Otherwise, a const init function cannot be defined.
  4. Only assignment expressions can be used to assign values to instance member variables in a const init function.

The difference between const init functions and const functions is that values can be assigned to instance member variables in const init functions (assignment expressions are required).

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 cannot 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
}