Skip to main content

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

variable.coco
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 MethodDescriptionSignature
buildBuild(<any> other) → (<any> value)
throwThrow() → (<any> String)
eventEvent() → (<any> <event>)
joinJoin(<any> X) → (<any> X)
ltLess Than(<any> X) → (<any> Bool)
gtGreater Than(<any> X) → (<any> Bool)
eqEqual To(<any> X) → (<any> Bool)
boolBool() → (<any> Bool)
strString() → (<any> String)
idIdentifier() → (<any> Identifier)
lenLength() → (<any> U64)

These methods must be called in coco using the __ at start and end of the method name.