5: Writing and deploying canisters
Overview
In this guide, you'll learn the steps required to write, compile, deploy, and interact with a Rust backend canister.
This guide will showcase a simple 'Hello, world!' example.
Prerequisites
Before getting started, assure you have set up your developer environment according to the instructions in the developer environment guide.
Writing the canister
Open a terminal window on your local computer, if you don’t already have one open.
First, create a new dfx project with the command:
- dfx v0.17.0 or newer
- dfx v0.16.1 or older
Use `dfx new [project_name]` to create a new project:
```
dfx new hello_world
```
You will be prompted to select the language that your backend canister will use:
```
? Select a backend language: ›
❯ Motoko
Rust
TypeScript (Azle)
Python (Kybra)
```
Then, select a frontend framework for your frontend canister. In this example, select:
```
? Select a frontend framework: ›
SvelteKit
React
Vue
❯ Vanilla JS
No JS template
No frontend canister
```
Lastly, you can include extra features to be added to your project:
```
? Add extra features (space to select, enter to confirm) ›
⬚ Internet Identity
⬚ Bitcoin (Regtest)
⬚ Frontend tests
```
```bash
dfx new hello_world
cd hello_world
npm install
```
For projects created with `dfx new` (Motoko and Rust) the command automatically generates the project's default
configuration and two default smart contracts.
By default, the project structure will resemble the following:
Cargo.lock
Cargo.toml
dfx.json
package.json
src
├── hello_world_backend
│ ├── Cargo.toml
│ ├── hello_world_backend.did
│ └── src
│ └── lib.rs
└── hello_world_frontend
├── assets
│ ├── favicon.ico
│ ├── logo2.svg
│ ├── main.css
│ └── sample-asset.txt
└── src
├── index.html
└── index.js
webpack.config.js
For more information on project structure and code organization, review the project organization guide.
Writing the lib.rs
file
You'll be focused on the src/hello_world_backend/src/lib.rs
file in this step.
Open the src/hello_world_backend/src/lib.rs
file in a text editor. Replace the existing content with the following:
#[ic_cdk::query]
fn greet(name: String) -> String {
format!("Hello there, {}! This is an example greeting returned from a Rust backend canister!", name)
}
Save the file.
Writing the Cargo.toml
file
Open the src/hello_world_backend/Cargo.toml
file in a text editor. Replace the existing content with the following:
[package]
name = "hello_world_backend"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
crate-type = ["cdylib"]
[dependencies]
candid = "0.8.2"
ic-cdk = "0.7.0"
Save the file.
Deploying the canister to your local canister execution environment
Creating the canister
First, create an empty canister for the canister code to be installed into. To create the canister, run the command:
dfx canister create hello_world_backend
The output will resemble the following:
Creating canister hello_world_backend...
hello_world_backend canister created with canister id: br5f7-7uaaa-aaaaa-qaaca-cai
Building the canister
Next, you need to compile your program into a WebAssembly module that can be deployed on ICP by building the canister. To build the canister, run the command:
dfx build hello_world_backend
Installing the canister
Then, install the compiled code into your canister with the command:
dfx canister install hello_world_backend
Deploying to the execution environment
To deploy the canister, start the dfx local replica with the command:
dfx start --clean --background
Then, you can deploy the canister with the command:
dfx deploy hello_world_backend
This command deploys just the hello_world_backend
canister. To deploy all canisters in the dfx.json
file, use the command:
dfx deploy
Deploying the canister to the mainnet
To deploy to the mainnet, you will need a balance of cycles.
To learn more about how to get cycles, please review the documentation.
Once you have gotten cycles, check the status of the mainnet to confirm that your local environment can connect to it. You can ping the mainnet with the command:
dfx ping ic
The output should resemble the following:
{
"ic_api_version": "0.18.0" "impl_hash": "d639545e0f38e075ad240fd4ec45d4eeeb11e1f67a52cdd449cd664d825e7fec" "impl_version": "8dc1a28b4fb9605558c03121811c9af9701a6142" "replica_health_status": "healthy" "root_key": [48, 129, 130, 48, 29, 6, 13, 43, 6, 1, 4, 1, 130, 220, 124, 5, 3, 1, 2, 1, 6, 12, 43, 6, 1, 4, 1, 130, 220, 124, 5, 3, 2, 1, 3, 97, 0, 129, 76, 14, 110, 199, 31, 171, 88, 59, 8, 189, 129, 55, 60, 37, 92, 60, 55, 27, 46, 132, 134, 60, 152, 164, 241, 224, 139, 116, 35, 93, 20, 251, 93, 156, 12, 213, 70, 217, 104, 95, 145, 58, 12, 11, 44, 197, 52, 21, 131, 191, 75, 67, 146, 228, 103, 219, 150, 214, 91, 155, 180, 203, 113, 113, 18, 248, 71, 46, 13, 90, 77, 20, 80, 95, 253, 116, 132, 176, 18, 145, 9, 28, 95, 135, 185, 136, 131, 70, 63, 152, 9, 26, 11, 170, 174]
}
Then, to deploy the canister to the mainnet, use the command:
dfx deploy hello_world_backend --network ic
For all commands that are intended to interact with the mainnet, the --network ic
flag needs to be used.
Testing the canister
To test the canister's functionality, call one of the canister's methods directly from the command line. In this example, this canister only has one method, greet
, so that will be the one tested.
To test this method, make a call to the greet
method with the command:
dfx canister call hello_world_backend greet everyone
The output will resemble:
("Hello there, everyone! This is an example greeting returned from a Rust backend canister!")
You can replace the last input string with any name you'd like, such as:
dfx canister call hello_world_backend greet Bob
This will return the output:
("Hello there, Bob! This is an example greeting returned from a Rust backend canister!")
Testing the canister with PocketIC
PocketIC is a lightweight, deterministic testing solution for programmatic testing of canisters that seamlessly integrates with existing testing infrastructure, such as cargo test.
You can learn more about PocketIC and how to install it here.
For Rust canisters, PocketIC can be imported into your project with the code:
use pocket_ic::PocketIc;
Then, in your code you can create a new PocketIC instance with the code:
let pic = PocketIc::new();
The following is a complete example for testing a counter canister:
use candid::encode_one;
use pocket_ic::PocketIc;
#[test]
fn test_counter_canister() {
let pic = PocketIc::new();
// Create an empty canister as the anonymous principal and add cycles.
let canister_id = pic.create_canister();
pic.add_cycles(canister_id, 2_000_000_000_000);
let wasm_bytes = load_counter_wasm(...);
pic.install_canister(canister_id, wasm_bytes, vec![], None);
// 'inc' is a counter canister method.
call_counter_canister(&pic, canister_id, "inc");
// Check if it had the desired effect.
let reply = call_counter_canister(&pic, canister_id, "read");
assert_eq!(reply, WasmResult::Reply(vec![0, 0, 0, 1]));
}
fn call_counter_canister(pic: &PocketIc, canister_id: CanisterId, method: &str) -> WasmResult {
pic.update_call(canister_id, Principal::anonymous(), method, encode_one(()).unwrap())
.expect("Failed to call counter canister")
}
You can see additional PocketIC testing examples here.
Interacting with the canister
In addition to testing the canister's functionality, the canister can be interacted with using other dfx
commands. For example:
To deposit cycles from the wallet into the canister, use the command:
dfx canister deposit-cycles [cycles amount] [canister-name]
To get the identifier of a canister, use the command:
dfx canister id [canister-name]
To get a canister's Wasm module hash and its current controllers, use the command:
dfx canister info [canister-name]
Next steps
Now that your canister has been written, deployed, and tested, let's take a look at making inter-canister calls.