Variables & Types
Coco is an indentation-based, statically typed language. This page shows how to declare variables and constants, inspect types, and work with the core built-in types—primitives, arrays/varrays, maps, and classes. Examples are minimal and runnable so you can copy/paste them into Cocolab.
Declaring variables
Using the memory
keyword
coco Variable
const VAL U64 = 10
endpoint DeclareVar() -> (output U64):
memory n U64
n = VAL
yield output n
endpoint DeclareValue() -> (output U64):
memory n = 10
memory m U64 = 11
yield output n + m
Variables in Coco are declared using memory
keyword, followed by the variable name and type (example in DeclareVar
). In the same statement we can also assign a value to the variable, in this case we can omit the type (though not necessary, as shown in DeclareValue
). Without setting the value in memory
statement, the variable is initialized with the zero value for the type.
Using the storage
Keyword
coco GuardianRegistry
state logic:
Operators Map[Identifier]Operator
class Operator:
field Name String
field Guardians []Identifier
endpoint deploy Init():
pass
endpoint dynamic RegisterGuardian(new_guardian Identifier):
storage operator Operator
observe operators <- GuardianRegistry.Logic.Operators:
operator = operators[Sender]
mutate operators <- GuardianRegistry.Logic.Operators:
append(operator.Guardians, new_guardian)
operators[Sender] = operator
A special case of a variable is a pointer to a stored value that allows for very efficient "atomic" handling of storage without transferring large amounts of data between blockhain storage and memory. Such variable is declared using storage
keyword instead of memory
. While for primitive values using storage
is possible, though not necessary, for complex values like arrays, maps or classes, using storage
can be more efficient that dispersing large data structures from the storage into memory.
In the above example, we first observe an operator
but it's only a pointer to the stored value, we don't transfer the complete data structure into memory. Then we append a new_guardian
to the element Guardian
of the operator
, what is a simple operation that doesn't require transferring the complete array into memory. At the end the pointer is written back into map, what again only transfers the new elements, not the entire structure.
Defining Constants Using const
Keyword
Constants can be defined for the whole module using const
keyword. Constant’s definition needs to include both type and value. Contants can only be defined at the top-level, constants can't be local to an endpoint or code blocks.
Get a variable’s type with typeof(expr)
A string description of variable type can be retrieved using typeof(variable)
function. Any variable or expression can be the argument, except functions. Calling functions in typeof()
produces a compiler error.
Primitive types
- Bool
- String
- Bytes
- Identifier (32-byte)
- U64
- U256
- I64
Arrays and Varrays
Coco uses fixed-lenght arrays and variable-length varrays. One can append
to a varray and popend
from its end. Varrays can also be merged with merge
function returning a merged varray.
coco Arrays
endpoint TestArray() -> (res1 [3]U64, res2 [3]U64, length U64):
memory arr = [3]U64{1, 2, 3}
arr2 = make([3]U64) // initialize array
arr2 = [3]U64{2, 3, 4}
return (res1: arr, res2:arr2, length: len(arr))
endpoint TestVarray() ->(res1 []U64, res2 []U64, end U64, length U64, joined []U64):
memory vrr1 []U64
append(vrr1, 1)
append(vrr1,2)
memory vrr2 = make([]U64, 2) // create varray []U64{0, 0}
append(vrr2, 2)
append(vrr2, 3)
append(vrr2, 4)
memory last = popend(vrr2)
memory s = len(vrr2)
memory merged = merge(vrr1, vrr2)
return (res1:vrr1 , res2:vrr2, end: last, length: s, joined: merged)
Maps
Maps are a collection of key-value pairs available in Coco. Each key in the map must be unique and can be mapped to a single value only. Map keys can only be primitive values, but values can be of any type, including another map
mp = make(Map[String]U64)
mp["Maybe"] = 2
memory:
mp2 = Map[String]U64{"No":0, "Yes": 1}
l = len(mp2)
joined = merge(mp, mp2)
remove(joined, "No")
memory exists = joined["No"]? # should be false
The make
command can be used to initialize an empty map.
mp2
variable is initialized with a set of key-value pairs in declaration.
The len
operator can be used to get total number of keys present in a map.
Maps can be merged using the merge
operator. If the keys overlap, the second map pairs overwrite the first. With this operation joined will contain union of all keys present in ‘mp’ and ‘mp2’.
To remove a key/value from the map, one can use remove
function. It doesn’t return the removed value, if we try to remove a non-existent key, remove
doesn’t do anything.
When a collection (map or array) is empty, sweep
can be used to completely remove the empty map from the storage.
Membership check with ?
operator
To check if a map has an element at some key, one can use the “has” operator ?
that returns a boolean “true” if the element exists.
memory exists = m["hi"]?
generate
micro keyword
generate
keyword in front an expression, reading from maps, assures the default value is returned if the key is missing. The case to use generate keyword is e.g.:
counter[newUserId]++
// the above is actually
// counter[newUserId] = counter[newUserId] + 1
// so it returns a runtime error when newUserId
// key doesn't exist
// so we'd need to write
if !counter[newUserId]?:
counter[newUserId] = 0
counter[newUserId]++
With generate, we can resolve the above in a single line:
generate counter[newUserId]++
Classes
coco Class
class Person:
field name String
field age U64
field check U64
method mutate Add():
self.check += 1
endpoint Details(name String, age U64, check U64) -> (nameres String, ageres U64, checkres U64):
memory person Person
person.name = name
person.age = age
person.check = 0
person.Add()
return (nameres: person.name, ageres: person.age, checkres: person.check)
endpoint Literal() -> (length U64, person Person):
memory personobj = Person{name: "Sam", age: 20, check: 0}
length = len(personobj)
yield person personobj
Classes in Coco allows you to simplify the handling of complex structures. Each class is made up of fields and methods.
Classes can be declared using the class
keyword followed by the name of the class. Methods can be declared within the class block using the method
keyword followed by the name of the method, input parameters and output parameters. In methods, self
is available without explicitly listing it as an argument of a method.
If the method changes its own values, mutate
has to follow the method
keyword in the method signature.
To access any field or method of the class the value can be called simply using a dot followed by the name of the method or field.
A maximum of 240 methods can be declared on any class excluding the 16 slots reserved for predefined special methods
.
memory person = Person{name: "Sam", age: 20, check: 0}
Here person is initialized with a class of type Person with the values as provided
In length = len(personobj)
the total number of fields in the class is stored in the variable length.
Special methods on classes
Among the 256 method codes, the first 16 (0x0 to 0xF) are reserved for special methods that can be invoked. The special methods are build, throw, event, join, lt, gt, eq, bool, str, id, and len.
Special methods can be defined or overridden for classes. E.g., one can define a __len__
of a class or __bool__
and use a class in boolean expressions.
class Person:
field name String
method __eq__(other Person) -> (is_equal Bool):
is_equal = self.name == other.name
Reserved Method Outputs As these methods are reserved, their output types are predefined. Ensuring your output type matches the method call is vital, as any mismatch will result in failure.
Special Method Signatures
Special Method | Description | Signature |
---|---|---|
build | Build | (<any> other) → (<any> value) |
throw | Throw | () → (<any> String) |
event | Event | () → (<any> <event>) |
join | Join | (<any> X) → (<any> X) |
lt | Less Than | (<any> X) → (<any> Bool) |
gt | Greater Than | (<any> X) → (<any> Bool) |
eq | Equal To | (<any> X) → (<any> Bool) |
bool | Bool | () → (<any> Bool) |
str | String | () → (<any> String) |
id | Identifier | () → (<any> Identifier) |
len | Length | () → (<any> U64) |
These methods must be called in coco using the __ at start and end of the method name.