const Functions and Constant Evaluation

Constant evaluation allows some specific forms of expressions to be evaluated during compilation, reducing the calculations required during program execution. This section describes the usage and rules of constant evaluation.

const Variables

A const variable is a special variable that is introduced with the const keyword, which defines a variable that is evaluated during compilation and cannot be changed during execution. In the following example, the gravitational constant G is defined:

const G = 6.674e-11

The type annotation can be omitted for const variables, but the initialization expression cannot be omitted. A const variable can be a global variable, local variable, or static member variable. However, it cannot be defined in an extension. It can access all instance members and call all non-mut instance member functions of the corresponding type.

In the following example, a struct is defined to record the mass and radius of a planet, and a const member function gravity calculates the gravitational force of the planet on an object with a mass of m at a distance of r:

struct Planet {
    const Planet(let mass: Float64, let radius: Float64) {}

    const func gravity(m: Float64, r: Float64) {
        G * mass * m / r**2
    }
}

main() {
    const myMass = 71.0
    const earth = Planet(5.972e24, 6.378e6)
    println(earth.gravity(myMass, earth.radius))
}

The gravitational force of the Earth on an adult with a mass of 71 kg on the ground is calculated during compilation.

695.657257

After a const variable is initialized, all of its instance members are considered const (transitively including all instance members reachable through it), and therefore cannot be assigned to.

main() {
    const myMass = 71.0
    myMass = 70.0 // Error, cannot assign to immutable value
}

const Context and const Expressions

The const context refers to initialization expressions of const variables, which are always evaluated during compilation. Only a restricted set of expressions are allowed in the const context to avoid side effects such as global state and I/O and to ensure that they can be evaluated during compilation.

const expressions are those that can be evaluated during compilation and therefore are allowed to appear in a const context. The following are const expressions:

  1. Literals of the numeric, Bool, Unit, Rune, and String types (excluding interpolated strings)
  2. Array literals whose elements are all const expressions (the elements cannot be of the Array type but can be of the VArray type) and tuple literals whose elements are all const expressions
  3. const variables, parameters of const functions, 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. calls to const functions (including const constructors) whose expressions and arguments are all const expressions
  6. calls to enum constructors whose parameters are all 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 sub-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 const init and const functions
  11. Function calls of const instance member functions of const expressions, where all arguments are const expressions

const Functions

A const function is a special function that can be evaluated during compilation. When called in a const context, it is evaluated during compilation. In non-const contexts, it is executed during program execution time the same as a normal function.

In the following example, the const function distance calculates the distance between two points on a plane, using let to define two local variables dx and dy:

struct Point {
    const Point(let x: Float64, let y: Float64) {}
}

const func distance(a: Point, b: Point) {
    let dx = a.x - b.x
    let dy = a.y - b.y
    (dx**2 + dy**2)**0.5
}

main() {
    const a = Point(3.0, 0.0)
    const b = Point(0.0, 4.0)
    const d = distance(a, b)
    println(d)
}

The result is as follows:

5.000000

Note:

  1. The declaration of a const function must be modified by const.
  2. Global const and static const functions can only access those external variables that are 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 are no special requirements on parameter types and return types in const functions. If the actual parameter 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 const init is defined. const instance member functions in class cannot be open. const instance member functions in struct cannot be mut.

In addition, const functions can be defined in APIs. The rules are as follows:

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

In the following example, two const functions are defined in the I API implemented by the A class, and the upper bound of the parameter type of the generic function g is I:

interface I {
    const func f(): Int64
    const static func f2(): Int64
}

class A <: I {
    public const func f() { 0 }
    public const static func f2() { 1 }
    const init() {}
}

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

main() {
    println(g(A()))
}

The result is as follows:

1

const init Functions

If a struct or class defines a const constructor, the struct or class instance can be used in a const expression.

  1. For class types, a const init function can only be defined if there are no var member variables. If the current type has a parent class, any const init functions in it must call a const init function of the parent class (explicitly or implicitly if a const init function without parameters is available). If the parent class does not have a const init function, an error is reported.
  2. 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.
  3. 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).