Forging the Future of ICP Open-Source: A Deep Dive into Motoko, Rust, and TypeScript

An in-depth comparison of Motoko, Rust, and TypeScript’s strengths and weaknesses for innovating in the ICP ecosystem.

Agustinus Theodorus
DFINITY Hero ∞
Published in
11 min readApr 15, 2024

--

Image generated by ChatGPT

This article will compare Motoko, Rust, and Azle programming languages to analyze their effectiveness and potential for open-source development within the Internet Computer Protocol (ICP) Ecosystem.

By examining these programming languages in the context of open-source development within the ICP Ecosystem, we aim to gain insights into their respective strengths, weaknesses, and suitability for facilitating innovation and collaboration among developers in this unique environment.

Furthermore, we will analyze the programming languages based on criteria such as ease of use, community support, and popularity to evaluate their potential for open-source development within the ICP Ecosystem.

The Selection of Programming Languages

ICP currently supports four languages within its environment: Motoko, Rust, TypeScript, and Python. In this article, we will specifically compare Motoko, Rust, and TypeScript (Azle), three prominent languages within the ICP dfx CLI.

Python (Kybra) wasn’t considered because it is still in its early development stages. Therefore, we will compare and analyze Motoko, Rust, and Azle’s effectiveness for open-source development within the ICP Ecosystem.

Motoko

Motoko is a high-level, statically typed programming language designed to build software on an ICP. Its design goals include making writing secure and scalable code easy, with features like the actor model for concurrent programming and canister communication APIs for building decentralized applications.

Rust

Rust is a systems programming language emphasizing safety, performance, and concurrency. It aims to provide a reliable and efficient way to write low-level code, making it popular for building critical systems where safety and performance are crucial. Rust is a trendy language in the open-source community, especially in the Web3 community. Projects like NEAR, Polkadot, and Solana use Rust for their blockchains and smart contracts.

TypeScript

TypeScript is a programming language that is a superset of JavaScript. When compiling code, TypeScript transpiles it into JavaScript, making it compatible with existing JavaScript projects. TypeScript provides static typing, which helps catch errors earlier in the development process and improves code maintainability. Undoubtedly the most popular of the three languages, TypeScript is the most used among developers worldwide. This is one of the main reasons for considering it and the Azle framework.

Comparing the Programming Languages

Let’s compare Motoko, Rust, and TypeScript to evaluate their effectiveness and potential for open-source development within the ICP Ecosystem. There will be four criteria that will be considered for each language:

  • State Management
  • Schema Generation
  • Developer Experience
  • Cycle Costs
  • Documentation

Creating a New Project

Creating a new project is fairly simple when using the dfx CLI. It already contains code generator templates for multiple CDKs: Motoko, Rust, Azle (TypeScript), and Kybra (Python).

To create a new project, you can run the command:

dfx new newproject

There will be a couple of options, such as creating a frontend or backend project. You can choose the backend project options based on which CDK you prefer.

State Management

First of all, let’s discuss state management in Motoko. There are two ways you can define a state: volatile and stable. Volatile states do not persist across canister upgrades, meaning the state will be lost when upgraded or restarted. These are good for recording user actions temporarily, for example, but not suitable for long-term data storage:

var list : List.List<Text> = List.nil<Text>();

However, to save a persistent state in Motoko, you should use the built-in type called stable.

stable var list : List.List<Text> = List.nil<Text>();

This stable variable ensures that the state will persist even after upgrades or restarts of the canister, making it suitable for long-term data storage.

State management in Rust is very different from that of Motoko. Using volatile storage is very easy.

type UserStore = BTreeMap<usize, String>;
thread_local! {
static USERS: RefCell<UserStore> = RefCell::default();
}

If you want to use stable storage, you would use the stable data structures:

const NONCE_MEMORY: MemoryId = MemoryId::new(0);
thread_local! {
static MEMORY_MANAGER: RefCell<MemoryManager<DefaultMemoryImpl>> =
RefCell::new(MemoryManager::init(DefaultMemoryImpl::default()));
pub static NONCE: RefCell<StableCell<u32, Memory>> = RefCell::new(
StableCell::init(
MEMORY_MANAGER.with(|m| m.borrow().get(NONCE_MEMORY)),
0
).expect("Initializing NONCE StableCell failed")
);
}

Stable data structures can be imported from the ic_stable_structures Rust package. Rust provides volatile and stable ways to manage state, with volatile storage relatively easy to implement by only using types like RefCell. In contrast, stable storage requires MemoryManager and StableCell from the ic_stable_structures package. A stable structure must be wrapped within a RefCell to allow concurrency and mutability.

Azle uses a similar structure to the Rust CDK for state management. Initializing a volatile state in Azle is as simple as initializing a variable.

let state: {
name: string;
} = {
name: "",
};

For a stable state, you can use stable data structures similar to the Rust CDK:

import { nat8, StableBTreeMap, text } from 'azle';
let map = StableBTreeMap<nat8, text>(0);

Because Azle uses TypeScript, it is one of the most readable code bases — readability, making developing and maintaining projects within the ICP ecosystem easier.

Schema Generation

In Motoko, there is no need to define a schema for the backend canister explicitly. Every time the canister is deployed using the dfx deploy command will autogenerate the type declarations for the exposed functions and data structures based on the Motoko code.

In Rust canisters, you can avoid explicitly defining the backend schema. However, to do so, you’d have to use the candid library in Rust to export the schema of your canister. Add the following to the bottom of your lib.rs file:

ic_cdk::export_candid!();

This line will export the candid schema and autogenerate the .did file for your Rust canister every time you run dfx generate canister_backend. It will also autogenerate the type declarations for the exposed functions and data structures based on the code every time the canister is deployed using the dfx deploy command.

The dfx.json of a Motoko and Rust project will look quite similar. The only difference would be the type of the canister defined within the JSON file.

A Motoko project’s JSON file will look like this:

{
"canisters": {
"motoko_new_backend": {
"main": "src/motoko_new_backend/main.mo",
"type": "motoko"
},
"motoko_new_frontend": {
"dependencies": [
"motoko_new_backend"
],
"source": [
"src/motoko_new_frontend/dist"
],
"type": "assets",
"workspace": "motoko_new_frontend"
}
},
"defaults": {
"build": {
"args": "",
"packtool": ""
}
},
"output_env_file": ".env",
"version": 1
}

A Rust project’s JSON file will look like this:

{
"canisters": {
"backend": {
"candid": "src/backend/backend.did",
"package": "backend",
"type": "rust"
},
"frontend": {
"dependencies": ["backend"],
"frontend": {
"entrypoint": "src/frontend/src/index.html"
},
"source": ["dist/frontend/"],
"type": "assets"
}
},
"defaults": {
"build": {
"args": "",
"packtool": ""
}
},
"output_env_file": ".env",
"version": 1
}

The Azle build system autogenerates the .did file. Unlike Motoko or Rust, you need to define the build commands within the dfx.json file, and you can synchronize the backend build with the frontend build by defining it.

{
"canisters": {
"backend": {
"type": "custom",
"main": "src/backend/index.ts",
"candid": "src/backend/index.did",
"candid_gen": "http",
"build": "npx azle backend",
"wasm": ".azle/backend/backend.wasm",
"gzip": true,
"assets": [["src/frontend/dist", "dist"]],
"build_assets": "npm run build",
"metadata": [
{
"name": "candid:service",
"path": "src/backend/index.did"
},
{
"name": "cdk:name",
"content": "azle"
}
]
}
}
}

Every time the canister is deployed using the dfx deploy command, it will autogenerate the types by running npx azle backend and building the front end using Vite. The backend canister will generate a WASM build.

Developer Experience

Motoko is a programming language designed to develop ICP applications. While it is easier to learn and use than Rust, it is still a relatively new language with a smaller community than Rust and TypeScript. However, the ICP team pushes Motoko to be the native language. As a new language, Motoko needs more packages in the ecosystem. Rust and TypeScript have an inevitably more extensive package ecosystem than Motoko. For example, you can quickly implement SQLite within your canister using TypeScript and Azle.

As mentioned previously, compared to Motoko, Rust has more optionality regarding packages. Since Rust is popular among developers, it already has a robust ecosystem and vast open-source libraries and packages. This makes Rust a versatile language for open-source development within the ICP Ecosystem. But Rust developers need to do manual memory management. It is the nature of the language since Rust doesn’t use a garbage collector, unlike Motoko or TypeScript.

TypeScript is one of the most popular programming languages for web development due to its static typing and robust tooling ecosystem. Because Azle is written in TypeScript, it provides an easy-to-use and familiar development experience for TypeScript developers. It is so easy to create Azle canisters that you can even use Express to develop a backend canister.

Cycle Costs

The cost of using Motoko as a programming language in the ICP ecosystem is relatively low. Motoko is very cost-efficient compared to other languages in ICP. According to the calculations, Motoko, on average, has the best WASM instruction use of all three CDKs. It is 8x cheaper than Rust and 460x cheaper than Azle.

The cost of using Rust as a programming language in the ICP ecosystem is also relatively low. As previously mentioned, it is lower than Motoko, but Rust is still 75 times cheaper than Azle.

Documentation

Although the ICP team offers comprehensive documentation for Motoko, practical project examples are needed. It gets better when you use the ICP AI in their documentation and after browsing some of their forums, but otherwise, it’s still easier to learn other CDKs, such as cdk-rs (Rust) or Azle (TypeScript).

The Rust CDK is well-documented and is the second most popular CDK after Motoko. It is maintained by the ICP team directly and has a comprehensive documentation website. The website provides detailed explanations, examples, and guides for developers to understand and use the Rust CDK effectively for building canisters within the ICP. If your team already has experience building in Rust, you don’t need to worry about learning how to create canisters in ICP. Adopting the Rust CDK is straightforward with its well-documented resources, making the transition smooth for Rust developers.

Azle is still in Beta development; thus, the documentation on certain deep canister subjects can be severely lacking. However, the team behind Azle compensated for this by creating one of the most extensive repositories of examples in the entire ICP ecosystem. Since Azle is written in TypeScript and can use Express within its canisters, while documentation on how to use canister-specific actions in Azle might be difficult, documentation on how to use some of the more interoperable packages is already available.

Summary

The choice between Motoko, Rust, and TypeScript largely depends on the specific requirements of the dApp being developed and the developer’s background.

Motoko offers a more straightforward path for those new to blockchain development or specifically targeting the ICP ecosystem. On the other hand, Rust may be preferable for developers looking for high performance and who are comfortable with its complexity, especially for more complex or computation-intensive applications.

Web and backend developers in the Node.js ecosystem will be familiar with Azle. Building with Azle will result in the fastest progress and a very efficient project timeline out of the three. However, developers must also approach Azle with a healthy dose of skepticism since the project is still new and maintained by a third party, not ICP.

Pros of Using Motoko:

  1. Designed for ICP: Motoko is specifically designed for the Internet Computer, offering seamless integration with its architecture and features.
  2. Built-in Features for ICP: Motoko has built-in features to handle ICP-specific concepts like canisters (smart contracts) directly, making development more straightforward.
  3. Garbage Collection: Automatic memory management can simplify development, especially for those unfamiliar with manual memory management.
  4. Efficient Cycle Use: Motoko uses the most cycles of all three CDKs. It’s 8x cheaper than Rust and 460x cheaper than Azle.

Cons of Using Motoko:

  1. Ecosystem and Community: Being a newer and more niche language, it has a smaller community and ecosystem than Rust. This can impact the availability of libraries and resources.
  2. Less Versatility: Its primary focus is on the ICP ecosystem, which might limit its use for broader applications.

Pros of Using Rust:

  1. Performance: Rust is known for its high performance and efficiency, making it ideal for compute-intensive dApps.
  2. Memory Safety: Rust’s ownership model ensures memory safety without needing a garbage collector, which can be crucial for secure blockchain applications.
  3. Robust Ecosystem: Rust is growing in popularity. It has a large and active community and offers a wealth of libraries and tools.
  4. Versatility: Rust is used in various domains, from web development to systems programming, which could benefit the development of more complex dApps.

Cons of Using Rust:

  1. Steep Learning Curve: Rust’s strict compile-time checks and concepts like ownership and borrowing can be challenging for new developers.
  2. Integration with ICP: While Rust is supported, developers might need extra effort to work with ICP-specific concepts compared to Motoko.
  3. Manual Memory Management: Although it provides performance benefits, manual memory management can be more complex and error-prone during development for those unfamiliar.

Pros of Using TypeScript (Azle):

  1. Designed for TypeScript Developers: This CDK will be the most familiar if you have little experience developing an Express.js API using JavaScript/TypeScript.
  2. Ease of Learning and Use: It is straightforward to use. Out of the three choices, this gives the most flexibility, and creating something from the ground up is very easy.
  3. Access to NPM packages: You have access to NPM. What else is there to say? Do you want to use an SQLite database? Sure. Do you want to make a canister interface using Express.js HTTP APIs? It’s also possible. Currently, only those two examples are available, but with the development of the CDK, there’s no telling how far it could go.
  4. Larger Community Behind TypeScript: While the ICP community is quite small, by using TypeScript as the language of choice for the open-source module, we can attract way more open-source contributors already familiar with the language to contribute. In the grand scheme of things, having more OSS developers to maintain this project will be very beneficial.

Cons of Using TypeScript (Azle):

  1. Very Early Beta: Taking on the project using Azle would be a significant risk because it is in early beta, and security checks and documentation still need to be completed.
  2. Maintained by Third Party: The Azle project is maintained by a third party, not the ICP team. The Azle project development will be delayed or shelved if anything happens to the third party.
  3. Performance: It’s TypeScript. Compared to Rust, it will never match its performance levels, especially for computation-intensive tasks. Also, when it comes to efficient cycle use, it is 460 times more expensive than Motoko.

Conclusion

After evaluating Motoko, Rust, and TypeScript via Azle for the Internet Computer platform, we decided on Rust. This choice was primarily influenced by Rust’s widespread popularity among developers and its robust ecosystem, which provides a rich selection of libraries and tools. Additionally, Rust’s Canister Development Kit (CDK) is notably stable and mature, offering a significant advantage over the TypeScript CDK provided by Azle, which, despite its appeal to JavaScript/TypeScript developers, remains in early beta with uncertainties around its documentation and security features.

Rust’s performance and memory safety, crucial for high-performance and secure decentralized applications, further solidified its standing as our preferred choice. Although Rust introduces a steeper learning curve and demands manual memory management, these challenges are offset by the long-term benefits of the reliability and scalability of our project.

In essence, Rust’s blend of developer support, ecosystem maturity, and technical capabilities aligns best with our project’s objectives, leading us to choose it over the alternatives.

--

--

Agustinus Theodorus
DFINITY Hero ∞

Loves to share his thoughts and opinions on the internet.