Skip to main content

2.4 Introduction to Candid

Intermediate
Tutorial

Candid is an interface description language that is used to describe the public interface of a service. A service is an application deployed as a canister on ICP. A canister exposes public interfaces used to interact with the canister's code.

Candid can be used to interact with canisters through the IC SDK via the terminal, through a web browser, or through the use of agents. It also provides a way to specify a method's input argument values and display return values regardless of the manner used to interact with the canister.

Candid is language-agnostic, which is a key benefit since it allows for the interoperation between canisters written in different languages, such as Motoko or Rust, and client applications.

Candid is unique since it provides functionality and features not available in other technologies such as JSON or XML, such as the evolution of service interfaces, direct value mapping of Candid values to the types and values of the canister's language, and the ability to pass complex data to services and methods. Candid also has built-in support for ICP-specific features like query annotation.

Up until now on this tutorial series, you've utilized the Candid UI in your the web browser to interact the public interfaces of backend canisters.

Now it's time to take a closer look at the fundamentals of Candid to understand how it works and how it can be used for more complex dapp development.

Candid types and values

Candid supports the following set of types in order to provide a natural mapping of data types based on reasonable choices suited for each canister language:

  • Unbounded integral number types (Nat, Int).

  • Bounded integral numbers (Nat8, Nat16, Nat32, Nat64, Int8, Int16, Int32, Int64).

  • Floating point types (Float32, Float64).

  • Boolean type (Bool).

  • Textual data (Text).

  • Binary data (Blob).

  • Container types and variants (Opt, Vec, Record, Variant).

  • Reference types (Service, Func, Principal).

  • Special types Null, Reserved, and Empty.

Each of these types is described in detail in the Candid reference documentation.

Candid textual values

Typically, as a developer you may not work with your program data as Candid values; instead, you may work with a host language such as Javascript and utilize Candid to transport data to a canister written in Motoko or Rust. The receiving canister treats this data as native Motoko or Rust values.

In some scenarios, like debugging, logging, or interacting with a service via the terminal command line, it may be useful to see the Candid values in a human-readable format. In these situations, the textual presentation of Candid values can be used.

The syntax for the textual presentation of Candid values is similar to that of Candid types. An example of a textual presentation for a Candid value is:

(record {
  first_name = "John";
  last_name = "Doe";
  age = 24;
  membership_status = variant { active };
  email_addresses =
    vec { "john@doe.com"; "john.doe@example.com" };
})

In contrast, the Candid binary format uses numeric hashes in place of field names. As a result, printing a value without the knowledge of what type that value is will not include the field name of records or variants. The above example would be printed as follows:

(record {
   4846783 = 14;
   456245371 = variant {373703110};
   1443915007 = vec {"john@doe.com"; "john.doe@example.com"};
   2797692922 = "John"; 3046132756 = "Doe"
})

Candid service descriptions

To define Candid types and describe a service, a Candid service description file (.did) can be used. This file can be generated from a canister's code or written manually.

The Candid files you have used thus far in this tutorial series have been generated automatically. This is because a Motoko canister's Candid files are automatically generated by the Motoko compiler when the canister is compiled, then stored in the project's /declarations directory. When Candid files are auto-generated, it is not recommended to edit them manually, and any changes made to the file will be overwritten by dfx the next time the project is built and deployed. For canisters written in other languages, the Candid file must be written manually or generated using an external tool.

Example Candid service descriptions

The simplest service description defines a service with no public methods:

service : {}

Since this service description does not have any public methods, it is not very useful. To add a public method, you can add a simple ping method:

service : {
  ping : () -> ();
}

Now our service description supports a single public method named ping. Note that method names can be arbitrary strings that can be quoted, such as "this is a method."

In this ping method, there are no arguments passed to the method, and there are no results returned, so the empty sequence of () is used for both.

Generating service descriptions

Generating Candid files is natively supported by dfx for canisters written in Motoko. Canisters written in other languages will need to have their Candid files written manually or generated by an external tool. To generate Candid files from Rust code, the Rust CDK v0.11.0 and higher support auto-generation using the candid-extractor tool.

Check out the Candid interface description for more information on manually writing the service description.

The following is an example Motoko canister that defines simple add, subtract, and get functions:

actor {
  var v : Int = 0;
  public func add(d : Nat) : async () { v += d; };
  public func subtract(d : Nat) : async () { v -= d; };
  public query func get() : async Int { v };
}

When this code is compiled, the Motoko compiler will automatically generate a Candid service description file:

service counter : {
  add : (nat) -> ();
  subtract : (nat) -> ();
  get : () -> (int) query;
}

This example describes a service called counter that consists of the following public methods:

  • The add and subtract methods, which change the value of the counter.
  • The get method, which reads the current value of the counter.

This example Candid interface description helps to illustrate that every method has a sequence of argument and result types. Methods can also include annotations, like the query notation shown in this example (() -> (int) query;), which is a feature specific to ICP.

Service upgrades

As your application changes and evolves over time, its methods will most likely change. New methods may be introduced, existing methods may expect additional arguments, or new data may be returned. Ideally, developers want to make these changes without breaking any existing workflows or clients.

Candid supports service upgrades using a set of specific rules that indicate when a new service type will be able to communicate with any clients using the previous interface description. These rules are:

  • New methods can be added.

  • Existing methods can return additional values; clients using the previous interface description will ignore these additional values.

  • Existing methods can shorten their parameter list; clients using the previous interface description may still send extra arguments, but they will be ignored.

  • Existing methods can extend their parameter list with optional arguments; clients using the previous interface description who do not pass that argument will use a null value.

  • Existing parameter types may be changed, but only as a supertype of the previously used type.

  • Existing result types may be changed, but only as a subtype of the previously used type.

For additional information about the supertypes and subtypes of any given type, check out the Candid interface reference documentation for each type.

To demonstrate an example of how a service might be upgraded, let's use the counter service example that you looked at in generating service descriptions.

Let's start with the following Candid service description:

service counter : {
  add : (nat) -> ();
  subtract : (nat) -> ();
  get : () -> (int) query;
}

This service might be upgraded in the following manner:

type timestamp = nat; // Define a new type of 'timestamp'
service counter : {
  set : (nat) -> (); // Define a new method
  add : (int) -> (new_val : nat); // Change the parameter and result types
  subtract : (nat, trap_on_underflow : opt bool) -> (new_val : nat); // Change the parameter and result types
  get : () -> (nat, last_change : timestamp) query; // Change the result types
}

Using Candid

Now that you've taken a deep dive into Candid, let's explore the different ways to use it. There are three main ways to interact with Candid: using dfx on the command line, using the web browser, and using an agent. For this tutorial, you'll look at using dfx and using the web browser; you'll dive into agents in a future section of the Developer Liftoff.

#import TabItem from "@theme/TabItem"; import { AdornedTabs } from "/src/components/Tabs/AdornedTabs";

Before you start, verify that you have set up your developer environment according to the instructions in 1.2 Developer environment setup.
If you're using ICP Ninja, you can [open this project](https://icp.ninja/s/XPWaG) and skip to [Interacting with a service using a web browser](#interacting-with-a-service-using-a-web-browser).

Creating a new project

To get started, either open this project in ICP Ninja and skip to Interacting with a service using a web browser or follow the steps below to create a new project with dfx in your working directory.

Open a terminal window, navigate into your working directory (developer_liftoff), then use the following commands to start dfx and create a new project:

dfx start --clean --background
dfx new candid_example --type=motoko --no-frontend
cd candid_example

Open the src/candid_example_backend/main.mo file in your code editor and replace the existing code with the following:

src/candid_example_backend/main.mo
actor {
  var v : Int = 0;
  public func add(d : Nat) : async () { v += d; };
  public func subtract(d : Nat) : async () { v -= d; };
  public query func get() : async Int { v };
}

Save this file. Then, compile and deploy the project with the command:

dfx deploy

Recall that the Motoko compiler will automatically generate the Candid service description file based on this code. This compilation is done as part of the dfx deploy command. This Candid file can be found at src/declarations/candid_example_backend/candid_example_backend.did and will contain the following:

src/declarations/candid_example_backend/candid_example_backend.did
service : {
  add: (nat) -> ();
  get: () -> (int) query;
  subtract: (nat) -> ();
}

Interacting with a service using the command-line terminal

Now that your canister is deployed, let's use dfx to interact with the methods defined by your Candid file. This section of the tutorial will look familiar since in previous tutorials you've interacted with canisters in the same manner—you just hadn't known you were directly interacting with the Candid-defined services since the Candid files had been automatically generated for you in the background.

For example, let's call the add method to add a number to our counter with the command:

dfx canister call candid_example_backend add 12

Then, you can call the get method to return the value you just added:

dfx canister call candid_example_backend get
Output
(12 : int)

You can then subtract from this value with the command:

dfx canister call candid_example_backend subtract 4

Then call the get method again:

dfx canister call candid_example_backend get
Output
(8 : int)

Reset the counter's value by subtracting the remaining value:

dfx canister call candid_example_backend subtract 8

Interacting with a service using a web browser

Candid can provides a web interface, known as the Candid UI, that allows the developer to call canister functions for debugging and testing from within a web browser. This functionality does not require the development of any frontend code and does not require a frontend canister to be created or deployed.

You have already used this UI in previous modules. To recap, when a backend canister is deployed (either with dfx deploy or by selecting the Deploy button on ICP Ninja), the following Candid UI returned:

## ICP Ninja
→ Building backend
Backend Internet Computer URL:
https://a4gq6-oaaaa-aaaab-qaa4q-cai.icp1.io/?id=6euhw-laaaa-aaaab-qbmha-cai

## dfx
  Backend canister via Candid interface:
    candid_example_backend: http://127.0.0.1:4943/?canisterId=avqkn-guaaa-aaaaa-qaaea-cai&id=b77ix-eeaaa-aaaaa-qaada-cai

Open this URL, either from the ICP Ninja output logs or the dfx terminal output. It will display the following UI:

Candid UI 1

Let's use the same workflow you used on the command line. First, let's add 12 to the counter by entering '12' into the 'add' method, then selecting 'call.' The output log on the right will reflect this call.

Candid UI 2

Then, use the 'get' method's `query' button to query the counter's value. This will be returned in the output log as well:

Candid UI 3

Subtract 4 from the counter by entering '4' into the 'subtract' method, then selecting 'call.'

Candid UI 4

Lastly, call the 'get' method again to return our final counter value:

Candid UI 5

ICP AstronautNeed help?

Did you get stuck somewhere in this tutorial, or do you feel like you need additional help understanding some of the concepts? The ICP community has several resources available for developers, like working groups and bootcamps, along with our Discord community, forum, and events such as hackathons. Here are a few to check out: