Generics
Lithe's generics are specialized at compile time. Each instantiation compiles to code for its concrete types, with no runtime dispatch and no casts.
Generic functions
Type parameters come in parentheses before the value parameters:
storage is record {
a : address;
}
pure function pick(T)(const first : T; const second : T; const useFirst : bool) : T is {
var chosen : T := second;
if useFirst then { chosen := first; }
} with chosen
external view function ownerOr(const other : address; const flag : bool) : address is
pick(storage.a, other, flag)Type arguments are inferred from the call. Every argument for T must have the same type:
type parameter `T` is both `address` and `uint`Generic modules
A module can take type parameters. Its types and functions can use them, and a use such as Set(address) names one specialization:
import "./lib/set";
type OrderId is bytes32;
storage is record {
admins : Set(address);
filled : Set(OrderId);
}
external function addAdmin(const who : address) is {
Set.add(storage.admins, who);
}
external view function isAdmin(const who : address) : bool is
Set.contains(storage.admins, who)
external function fill(const id : bytes32) is {
Set.add(storage.filled, OrderId(id));
}(* Membership set. One mapping, no enumeration. *)
module Set(T : MapKey) is {
type Self is map(T, bool);
view function contains(storage set : Self; const value : T) : bool is
set[value]
function add(storage set : Self; const value : T) is {
set[value] := true;
}
function remove(storage set : Self; const value : T) is {
set[value] := false;
}
}Set(address)is a type. Here it ismap(address, bool), so it can be stored.Set.add(storage.admins, who)calls the specialization picked by the argument's type.Set(OrderId)andSet(bytes32)are different types, because nominal types stay distinct in generics too.- A module's other types are reached through a specialization:
SetEnumerable(address).iterator. - A generic module's types can use other generic modules at its own parameters. The standard library's
MapEnumerable(K, V)keeps its keys in aSetEnumerable(K).
Constraints
A type parameter can carry a constraint that the compiler checks at each use. The only constraint today is MapKey, which accepts types that can key a map: integers, bool, address, bytesN, wei, timestamp, and nominal types over them.
module Set(T : MapKey) is {
type Self is map(T, bool);
function add(storage set : Self; const value : T) is {
set[value] := true;
}
}
storage is record {
names : Set(string);
}
external function go() is { skip }type `string` does not satisfy `MapKey`Storage parameters
A module function that declares a storage parameter, such as storage set : Self, receives a reference to that storage location, not a copy, and may write it. A const parameter is always a value. Set.add(storage.admins, who) writes to storage.admins. That is how collection modules work without owning a named domain of their own.
A module passes its own storage the same way. Inside a module that declares storage roles, Set.add(roles.admins, who) writes to roles.admins.
The storage can be a map's entry, or anything past one: Checkpoints.push(votes.history[account], key, value) works on account's history. Each key is computed once, at the call.

