Candid

All shared types and values in Motoko have a corresponding description in the 'outside world'. This description defines the types and values independently of Motoko or any other language. These alternative descriptions are written in a special Interface Description Language called Candid.

Shared Types

Candid has a slightly different notation (syntax) and keywords to represent shared types.

Primitive types

Primitive types in Candid are written without capital letters:

MotokoCandid
Boolbool
Natnat
Intint
Floatfloat64
Principalprincipal
Texttext
Blobblob

Option types

Option types in Candid are written with the opt keyword. An option type in Motoko like ?Principal would be represented in Candid as:

opt principal

Tuple types

Tuple types in Candid have the same parenthesis notation (). A Motoko tuple (Nat, Text, Principal) would be represented in Candid as:

(nat, text, principal)

Immutable array types

The immutable array type [] is represented in Candid with the vec keyword.

A Motoko array type [Nat] in candid looks like this:

vec nat

Variant types

Variant types in Candid are written with the variant keyword and curly braces { }. A Motoko variant like {#A : Nat; #B : Text} would be represented in Candid like this:

variant {
    A : nat;
    B : text
};

The # character is not used

Object types

Object types in Candid are written with the record keyword and curly braces { }. A Motoko object type like {name : Text; age : Nat} in Candid looks like this:

record {
    name : text;
    age : nat
};

Public shared function types

Public shared function types in Candid have a slightly different notation. A shared public function type in Motoko like shared () -> async Nat would be represented in Candid like this:

() -> (nat)

Parentheses () are used around the arguments and return types. The shared keyword is not used because all representable functions in Candid are by default only public shared functions.

Another example would be the Motoko public shared function type shared query Bool -> async Text which in Candid would be represented as:

(bool) -> (text) query

Note that the query keyword appears after the return types.

A Motoko oneway public shared function type shared Nat -> () in Candid would be represented as:

(nat) → () oneway

The type keyword

Type aliases (custom names) in Candid are written with the type keyword. A Motoko type alias like type MyType = Nat would be represented in Candid like this:

type MyType = nat

Actor Interfaces

An actor running in a canister has a Candid description of its interface. An actor interface consists of the functions in the actor type and any possible types used within the actor type. Consider the following actor in a Motoko source file main.mo:

// main.mo
import Principal "mo:base/Principal";

actor {
    public type User = (Principal, Text);

    public shared query ({ caller = id }) func getUser() : async User {
        (id, Principal.toText(id));
    };

    public shared func doSomething() { () };
};

Only public types and public shared functions are included in the candid interface. This actor has a public type and two public shared functions. Both of these are part of its public interface.

The actor could have other fields, but they won't be included in the Candid Interface.

We describe this actor's interface in a Candid .did file. A Candid Interface contains all the information an external user needs to interact with the actor from the "outside world". The Candid file for the actor above would be:

// candid.did
type User = record {
   principal;
   text;
};

service : {
  getUser: () -> (User) query;
  doSomething: () -> () oneway;
}

Our Candid Interface consists of two parts: a type and a service.

The service : { } lists the names and types of the public shared functions of the actor. This is the information needed to interact with the actor from "the outside" by calling its functions. In fact, actors are sometimes referred to as services.

The type reflects the public Motoko type User from our actor. Since this is a public Motoko type that is used as a return type in a public shared function, it is included in the Candid Interface.

NOTE
The type alias User is a Motoko tuple (Principal, Text). In Candid a custom type alias for a tuple is translated into record { principal; text }. Don't confuse it with the Candid tuple type (principal, text)!

Candid Serialization

Another important use of Candid is data serialization of shared types. Data structures in Motoko, like in any other language, are not always stored as serial (contiguous) bytes in main memory. When we want to send shared data in and out of a canisters or store data in stable memory, we have to serialize the data before sending.

Motoko has built in support for serializing shared types into Candid format. A higher order data type like an object can be converted into a binary blob that would still have a shared type.

Consider the following relatively complex data structure:

type A = { #a : Nat; #b : Int };

type B = { #a : Int; #b : Nat };

type MyData = {
    title : Text;
    a : A;
    b : B;
};

Our object type MyData contains a Text field and fields of variant types A and B. We could turn a value of type MyData into a value of type Blob by using the to_candid() and from_candid() functions in Motoko.

let data : MyData = {
    title = "Motoko";
    a = #a(1);
    b = #a(-1);
};

let blob : Blob = to_candid (data);

We declared a variable of type MyData and assigned it a value. Then we serialized that data into a Blob by using to_candid().

This blob can now be sent or received in arguments or return types of public shared functions or stored in stable memory.

We could recover the original type by doing the opposite, namely deserializing the data back into a Motoko shared type by using from_candid().

let deserialized_data : ?MyData = from_candid (blob);

switch (deserialized_data) {
    case null {};
    case (?data) {};
};

We declare a variable with option type ?MyData, because the from_candid() function always returns an option of the original type. Type annotation is required for this function to work. We use a switch statement after deserializing to handle both cases of the option type.