Powdr-pil allows the same syntax to declare various kinds of symbols. This includes constants, fixed columns, witness columns and even higher-order functions. It deduces the symbol kind from the type of the symbol and the way the symbol is used.

Symbols can be declared using let <name>; and they can be declared and defined using let <name> = <value>;, where <value> is an expression. The type of the symbol can be explicitly specified using let <name>: <type>; and let <name>: <type> = <value>;.

This syntax can be used for constants, fixed columns, witness columns and even (higher-order) functions that can transform expressions. The kind of symbol is deduced by its type and the way the symbol is used:

  • Symbols without a value are witness columns or arrays of witness columns. Their type can be omitted. If it is given, it must be col or col[].
  • Symbols evaluating to a number or with type fe are constants.
  • Symbols without type but with a value that is a function with a single parameter are fixed columns.
  • Symbols defined with a value and type col (or col[]) are fixed columns (or arrays of fixed columns).
  • Everything else is a "generic symbol" that is not a column or constant.

Note that the type col is the same as int -> fe, so let w: int -> fe also declares a witness column.


fn main() {
    // This defines a constant
    let rows = 2**16;
    // This defines a fixed column that contains the row number in each row.
    // Only symbols whose type is "col" are considered fixed columns.
    let step: col = |i| i;
    // Here, we have a witness column.
    let x;
    // This functions defines a fixed column where each cell contains the
    // square of its row number.
    let square: col = |x| x*x;
    // The same function as `square` above, but now its type is given as
    // `int -> int` and thus it is *not* classified as a column. Instead,
    // it is stored as a utility function. If utility functions are
    // referenced in constraints, they have to be evaluated, meaning that
    // the constraint `w = square_non_column;` is invalid but both
    // `w = square_non_column(7);` and `w = square;` are valid constraints.
    let square_non_column: int -> int = |x| x*x;
    // A recursive function, taking a function and an integer as parameter
    let sum = |f, i| match i {
        0 => f(0),
        _ => f(i) + sum(f, i - 1)