Computer Engineering and Mathematics student 🤓 developer in the making 👨🏫
Computer Engineering and Mathematics student 🤓 developer in the making 👨🏫

Subscribe to Daniel Espejel

Subscribe to Daniel Espejel
Share Dialog
Share Dialog


<100 subscribers
<100 subscribers
A comparison between blockchain languages based on and built on Rust.
Not long ago I started programming in Rust for the fun of it. I enjoy doing challenges in it in my spare time from daily tasks and I am sure it will continue to grow in popularity over other general purpose languages in the coming years.

When I found out that there were close variants of it for developing smart contracts I decided to learn some of them to create my own applications. Although I am not an expert yet, I have practiced enough to be able to make a small comparison between the blockchain-based languages I have come across and those that are based/written on Rust.
Here I will talk about three of them.

Sway. Created by Fuel Labs for the FuelVM. Initially it was created to be a monolitic L2 of Ethereum; today it is one of the fastest performing layers out there. It is also one of the first Rollups to run its transactions in parallel.

Move. Initially created for Facebook’s Libra project. Today it is Sui from Mysten Labs who keeps it up to date. Sui is an independent high-speed L1 which is advancing rapidly in the Web3 world.

Rust. Rust itself is used to create contracts. In particular, the Substrate and Wasm frameworks are the ones that allow communication with Blockchains. Substrate was created by Gavin Wood and Parity Technologies and is used primarily with Polkadot and its parachains. There are many libraries to create Smart Contracts in this language, but in this case we will use the one created by Gear Technologies.
For Rust enthusiasts entering the Web3 world it is not easy to adapt to such different languages. But how about using one based on Rust? They just need to choose which one they would like to develop on.
Is the most similar to Rust, so it maintains many of its characteristics
Is the perfect combination between Solidity and Rust
Maintains features like Option and Result, important for error handling
Does not make use of borrowed values
Manages programs with different purposes. Contract, library, script and predicate
Makes a more precise use of persistent memory
Has a less complex syntax
Does not use enums or traits at its core
Makes use of mutable and immutable borrowed values
Manages memory usage that depends on ‘abilities’ that are given to structures
Has two types of programs: module and script
Uses a greater variety of data structures, such as vectors, hashmaps and sets
Is the original recipe
Needs to be compiled to Wasm
Makes use of nightly Rust
Makes it possible to use all its resources
Can be used on any Polkadot chain or any generated with the Substrate framework.
Is particularly tricky for smart contracts
Can be confusing for new developers, even with knowledge of Rust
Is commonly used in unsafe mode
A few months ago I started to write the Uno card game on the Sui Blockchain with the Move language. Let’s write the same code in Sway and Rust.

uno.sw
// The program is a library called 'uno'.
library uno;
// Public structure representing a game.
// Manages a list of players and a vector with Rounds.
pub struct Game {
players: Vec<Identity>,
rounds: Vec<Round>,
}
// Public structure that represents the rounds of the game.
// Saves the round number and the list of players in it.
pub struct Round {
round: u8,
player: Vec<Identity>,
}
The code above is a library called uno.sw that contains the structures called Game and Round.Below is the main.sw contract with the implementation of a function and the abi. So for this occasion we will have two programs.
This is because the code makes structures, and structures must be declared in a library outside of the contract.
main.sw
// The program is a contract.
contract;
// Game and Round structures are imported. Also the msg_sender function.
dep uno;
use std::{auth::msg_sender};
use uno::{Game, Round};
// Storage saves and initializes variables to persistent storage.
storage {
// The key is the identifier of the game and the value is the game itself.
games: StorageMap<u64, Game> = StorageMap{},
}
// Abi with general functions.
abi check {
// Declare whether to read or write to persistent memory.
#[storage(read, write)]
fn check_participation(game_id: u64);
}
// Implements the functions of the abi declared above.
impl check for Contract {
#[storage(read, write)]
fn check_participation(game_id: u64) {
// We can see explicit read access to storage.
let new_number_of_rounds: u8 = storage.games.get(game_id).rounds.len() + 1;
// The conditional first checks if no one has played overall (empty rounds vector).
// If so, a vector is initialized with the address of the sender in that vector.
if storage.games.get(game_id).rounds.is_empty() {
let mut player: Vec<Identity> = storage.games.get(game_id).players;
player.push(msg_sender().unwrap());
storage.games.get(game_id).rounds.push(Round{ round: new_number_of_rounds, player });
}
// If all the players had already participated, then the round is advanced and
// a new vector is initialized with the current sender.
else if storage.games.get(game_id).rounds.len() == storage.games.get(game_id).players.len() {
let mut player: Vec<Identity> = Vec::new();
player.push(msg_sender().unwrap());
storage.games.get(game_id).rounds.push(Round{ round: new_number_of_rounds, player });
}
// In case the game is in the middle of a round, we only add the user to the
// list of players who have already played.
else {
let mut player: Vec<Identity> = storage.games.get(game_id).players;
player.push(msg_sender().unwrap());
storage.games.get(game_id).rounds.push(Round{ round: new_number_of_rounds, player});
}
}
}
Let’s first remember that an abi is the way to interact with contracts on Ethereum.
In this case the contract has only one abi and its implementation in the contract. The abi has a syntax similar to that of a trait in Rust; a collection of methods for the contract.
At the start of the contract we bring into scope the Game and Round structures from the uno.sw library. From the standard library we also bring the msg_sender() function that identifies the caller in the transaction.
A very important and differentiating part of Sway is the ability to explicitly define the variables that will be in persistent memory. The ‘structure’ called storage does just that. We can even initialize the values of those elements in memory.
In storage we place a StorageMap with the identifier of each game and the game itself. Very similar to Solidity mappings.
We then declare the abi called Check and define a check_participation function. That method must say if it will do persistent reading and/or writing. An abi should normally be declared in a library; however, here it was written in the contract for reasons of simplicity.
The implementation of the abi goes towards the contract. Inside the function is implemented. Here its parameter is just the game id, Game.
We can see that in the contract there are no borrowed variables. But we can describe a mutable reference in the arguments we give to functions (not done here).
#![no_std]
// The Gear standard library is imported, it already includes the Rust library.
use gstd::{msg, prelude::*, ActorId};
// Structure that organizes a set of games by their id.
struct AllInfo {
games: HashMap<u64, Game>,
}
// A game that includes a list of players and a hashmap with rounds.
struct Game {
players: Vec<ActorId>,
rounds: HashMap<u8, Vec<ActorId>>,
}
// The information that will remain in the contract.
static mut CONTRACT: Option<AllInfo> = None;
// Implementation to the function definition.
impl AllInfo {
fn check_participation(&mut self, game_id: &u64) {
// The game is obtained by its id
let game: &mut Game = match self.games.get_mut(game_id) {
Some(thing) => thing,
None => panic!("No game found."),
};
let game_rounds = &mut game.rounds;
let round_number = game_rounds.len() as u8;
let participations = game_rounds.get_mut(&round_number).unwrap();
// The conditional first checks if no one has played overall (empty rounds vector).
// If so, a vector is initialized with the address of the sender in that vector.
if round_number == 0 {
let mut player_vec: Vec<ActorId> = Vec::new();
let user = msg::source();
player_vec.push(user);
game_rounds.insert(1, player_vec);
// If all the players had already participated, then the round is advanced and
// a new vector is initialized with the current sender.
} else if participations.len() == game.players.len() {
let mut player_vec: Vec<ActorId> = Vec::new();
let user = msg::source();
player_vec.push(user);
game_rounds.insert(round_number + 1, player_vec);
// In case the game is in the middle of a round, we only add the user to the
// list of players who have already played.
} else {
participations.push(msg::source());
}
}
}
// A handle function is like a script.
// It is necessary to access functions and, in this case, receive information.
#[no_mangle]
extern "C" fn handle() {
// Bytes are received and converted to a u64, the game identifier.
let game_addr = msg::load().expect("Unable to receive Game Id");
let game_addr = String::from_utf8(game_addr)
.expect("Unable to get Game ID")
.parse::<u64>()
.unwrap();
// The information is obtained from the storage and its function is accessed.
let contract_info = unsafe {
if let Some(thing) = &mut CONTRACT { thing }
else { panic!("Contract has not been initialized.") }
};
contract_info.check_participation(&game_addr);
}
This code is a bit easier for Rust developers to understand. Let’s remember that this program must be transferred to Wasm when it is finished writing. This last step must be carried out whenever Substrate is used for Smart Contracts. The Gear library is one of the main ones, but there are others like the Ink! which are also very good.
In the program above, the important thing is to highlight the use of the Gear Std that includes the Rust Std. Also the way to store information in static. And finally, to receive information to the contract we must use the special function handle().
To interact with the program we make use of msg messages with various implementations. The first is with msg::source() to get the sender and the second is with msg::load() which does a binary load from the outside.
game_objects.move
// Name of the module and the address to which it belongs.
module 0x1::game_objects {
// The libraries or prefabricated code that we are going to use.
use sui::tx_context::{Self, TxContext};
use sui::object::{Self, UID, ID};
// The structure that will be the object with the option to be persistent in memory.
// To save the object you must have the "key" ability and have an id to identify it.
struct Game has key {
// id to identify the object in memory.
id: UID,
players: vector<address>,
// The Vec_Map stores the round and the amount (vector) of directions or
// players that have participated.
rounds: VecMap<u8, vector<address>>,
}
// The public function takes the "game" and "ctx" parameters explained below.
public fun check_participation(game: &Game, ctx: &mut TxContext) {
let game_rounds = get_rounds(game);
let round_number = (vec_map::size(&game_rounds) as u8);
let participations = vec_map::get(&mut game_rounds, &round_number);
// The address of the player is recorded depending on the round the game is in.
// If the game still doesn't start (empty HashMap), initialize the first round
// and register the player.
if(vec_map::is_empty<u8, vector<address>>(&game_rounds)) {
vec_map::insert(&mut game_rounds, 1, vector::singleton(tx_context::sender(ctx)));
// If all users participated in a round, it advances to the next one and the
// current player is registered.
} else if(vector::length(participations) == vector::length(&get_players(game))) {
vec_map::insert(&mut game_rounds, round_number + 1, vector::empty<address>());
// In the event that a round is halfway over, we only have to register the player.
} else {
let addresses = vec_map::get_mut(&mut game_rounds, &round_number);
vector::push_back(addresses, tx_context::sender(ctx));
};
}
In this version of Move, we must use the Sui standard library to call objects like Vec_Map (a HashMap) or use an ID to identify the tokens on the blockchain.
All codes must begin with the word module, an address, and the name of the module. The module is owned exclusively by the user who writes the contract and is able to decide if the code is private (default), public or public for other specific modules.
The check_participation function is impure because it will make changes to things outside of it; in this case we will make changes to the object that represents the game: Game. To achieve this we must pass the game as an argument, in addition to the Transaction Context that encapsulates data such as the sender or the transaction hash.
In the rest of the code, mutable and immutable references are also highlighted.
Below is a table with the main comparisons between the languages. Please note that there are still many unique features in each of them.

As you can see from the table above, the three languages are similar. Nevertheless there are key differences between them.
While Sway maintains a syntax similar to Vanilla Rust, it also does a great work combining the best solidity features while at the same time prevents reentrancy attacks. It is also one of the most organized languages I have ever used. The compiler’s strict handling enforces segregation between library and contract elements by default. Sway has been easy to learn thanks to its excellent documentation and guides for new users. Speaking personally, this language is one of my favorites.
On the other hand, Move has a very friendly and helpful community. Move is a mature language that has been carefully refined again and again through its stages of development from Facebook to Sui; that has made it simple and practical for Developers. Its documentation is very good and Sui team are very supportive.
Last but not least, writing contracts with Rust is an interesting experience. In it being a general purpose language, one would might think that it is not a very good choice for writing smart contracts. However, libraries written around it have turned out to be very useful. The development specifics can have variations depending on whether it is written with Gear or with ink! or with some other tool based on Substrate. In the end, Blockchain development is unique and Rust turned out to be a very good option for that purpose.
A comparison between blockchain languages based on and built on Rust.
Not long ago I started programming in Rust for the fun of it. I enjoy doing challenges in it in my spare time from daily tasks and I am sure it will continue to grow in popularity over other general purpose languages in the coming years.

When I found out that there were close variants of it for developing smart contracts I decided to learn some of them to create my own applications. Although I am not an expert yet, I have practiced enough to be able to make a small comparison between the blockchain-based languages I have come across and those that are based/written on Rust.
Here I will talk about three of them.

Sway. Created by Fuel Labs for the FuelVM. Initially it was created to be a monolitic L2 of Ethereum; today it is one of the fastest performing layers out there. It is also one of the first Rollups to run its transactions in parallel.

Move. Initially created for Facebook’s Libra project. Today it is Sui from Mysten Labs who keeps it up to date. Sui is an independent high-speed L1 which is advancing rapidly in the Web3 world.

Rust. Rust itself is used to create contracts. In particular, the Substrate and Wasm frameworks are the ones that allow communication with Blockchains. Substrate was created by Gavin Wood and Parity Technologies and is used primarily with Polkadot and its parachains. There are many libraries to create Smart Contracts in this language, but in this case we will use the one created by Gear Technologies.
For Rust enthusiasts entering the Web3 world it is not easy to adapt to such different languages. But how about using one based on Rust? They just need to choose which one they would like to develop on.
Is the most similar to Rust, so it maintains many of its characteristics
Is the perfect combination between Solidity and Rust
Maintains features like Option and Result, important for error handling
Does not make use of borrowed values
Manages programs with different purposes. Contract, library, script and predicate
Makes a more precise use of persistent memory
Has a less complex syntax
Does not use enums or traits at its core
Makes use of mutable and immutable borrowed values
Manages memory usage that depends on ‘abilities’ that are given to structures
Has two types of programs: module and script
Uses a greater variety of data structures, such as vectors, hashmaps and sets
Is the original recipe
Needs to be compiled to Wasm
Makes use of nightly Rust
Makes it possible to use all its resources
Can be used on any Polkadot chain or any generated with the Substrate framework.
Is particularly tricky for smart contracts
Can be confusing for new developers, even with knowledge of Rust
Is commonly used in unsafe mode
A few months ago I started to write the Uno card game on the Sui Blockchain with the Move language. Let’s write the same code in Sway and Rust.

uno.sw
// The program is a library called 'uno'.
library uno;
// Public structure representing a game.
// Manages a list of players and a vector with Rounds.
pub struct Game {
players: Vec<Identity>,
rounds: Vec<Round>,
}
// Public structure that represents the rounds of the game.
// Saves the round number and the list of players in it.
pub struct Round {
round: u8,
player: Vec<Identity>,
}
The code above is a library called uno.sw that contains the structures called Game and Round.Below is the main.sw contract with the implementation of a function and the abi. So for this occasion we will have two programs.
This is because the code makes structures, and structures must be declared in a library outside of the contract.
main.sw
// The program is a contract.
contract;
// Game and Round structures are imported. Also the msg_sender function.
dep uno;
use std::{auth::msg_sender};
use uno::{Game, Round};
// Storage saves and initializes variables to persistent storage.
storage {
// The key is the identifier of the game and the value is the game itself.
games: StorageMap<u64, Game> = StorageMap{},
}
// Abi with general functions.
abi check {
// Declare whether to read or write to persistent memory.
#[storage(read, write)]
fn check_participation(game_id: u64);
}
// Implements the functions of the abi declared above.
impl check for Contract {
#[storage(read, write)]
fn check_participation(game_id: u64) {
// We can see explicit read access to storage.
let new_number_of_rounds: u8 = storage.games.get(game_id).rounds.len() + 1;
// The conditional first checks if no one has played overall (empty rounds vector).
// If so, a vector is initialized with the address of the sender in that vector.
if storage.games.get(game_id).rounds.is_empty() {
let mut player: Vec<Identity> = storage.games.get(game_id).players;
player.push(msg_sender().unwrap());
storage.games.get(game_id).rounds.push(Round{ round: new_number_of_rounds, player });
}
// If all the players had already participated, then the round is advanced and
// a new vector is initialized with the current sender.
else if storage.games.get(game_id).rounds.len() == storage.games.get(game_id).players.len() {
let mut player: Vec<Identity> = Vec::new();
player.push(msg_sender().unwrap());
storage.games.get(game_id).rounds.push(Round{ round: new_number_of_rounds, player });
}
// In case the game is in the middle of a round, we only add the user to the
// list of players who have already played.
else {
let mut player: Vec<Identity> = storage.games.get(game_id).players;
player.push(msg_sender().unwrap());
storage.games.get(game_id).rounds.push(Round{ round: new_number_of_rounds, player});
}
}
}
Let’s first remember that an abi is the way to interact with contracts on Ethereum.
In this case the contract has only one abi and its implementation in the contract. The abi has a syntax similar to that of a trait in Rust; a collection of methods for the contract.
At the start of the contract we bring into scope the Game and Round structures from the uno.sw library. From the standard library we also bring the msg_sender() function that identifies the caller in the transaction.
A very important and differentiating part of Sway is the ability to explicitly define the variables that will be in persistent memory. The ‘structure’ called storage does just that. We can even initialize the values of those elements in memory.
In storage we place a StorageMap with the identifier of each game and the game itself. Very similar to Solidity mappings.
We then declare the abi called Check and define a check_participation function. That method must say if it will do persistent reading and/or writing. An abi should normally be declared in a library; however, here it was written in the contract for reasons of simplicity.
The implementation of the abi goes towards the contract. Inside the function is implemented. Here its parameter is just the game id, Game.
We can see that in the contract there are no borrowed variables. But we can describe a mutable reference in the arguments we give to functions (not done here).
#![no_std]
// The Gear standard library is imported, it already includes the Rust library.
use gstd::{msg, prelude::*, ActorId};
// Structure that organizes a set of games by their id.
struct AllInfo {
games: HashMap<u64, Game>,
}
// A game that includes a list of players and a hashmap with rounds.
struct Game {
players: Vec<ActorId>,
rounds: HashMap<u8, Vec<ActorId>>,
}
// The information that will remain in the contract.
static mut CONTRACT: Option<AllInfo> = None;
// Implementation to the function definition.
impl AllInfo {
fn check_participation(&mut self, game_id: &u64) {
// The game is obtained by its id
let game: &mut Game = match self.games.get_mut(game_id) {
Some(thing) => thing,
None => panic!("No game found."),
};
let game_rounds = &mut game.rounds;
let round_number = game_rounds.len() as u8;
let participations = game_rounds.get_mut(&round_number).unwrap();
// The conditional first checks if no one has played overall (empty rounds vector).
// If so, a vector is initialized with the address of the sender in that vector.
if round_number == 0 {
let mut player_vec: Vec<ActorId> = Vec::new();
let user = msg::source();
player_vec.push(user);
game_rounds.insert(1, player_vec);
// If all the players had already participated, then the round is advanced and
// a new vector is initialized with the current sender.
} else if participations.len() == game.players.len() {
let mut player_vec: Vec<ActorId> = Vec::new();
let user = msg::source();
player_vec.push(user);
game_rounds.insert(round_number + 1, player_vec);
// In case the game is in the middle of a round, we only add the user to the
// list of players who have already played.
} else {
participations.push(msg::source());
}
}
}
// A handle function is like a script.
// It is necessary to access functions and, in this case, receive information.
#[no_mangle]
extern "C" fn handle() {
// Bytes are received and converted to a u64, the game identifier.
let game_addr = msg::load().expect("Unable to receive Game Id");
let game_addr = String::from_utf8(game_addr)
.expect("Unable to get Game ID")
.parse::<u64>()
.unwrap();
// The information is obtained from the storage and its function is accessed.
let contract_info = unsafe {
if let Some(thing) = &mut CONTRACT { thing }
else { panic!("Contract has not been initialized.") }
};
contract_info.check_participation(&game_addr);
}
This code is a bit easier for Rust developers to understand. Let’s remember that this program must be transferred to Wasm when it is finished writing. This last step must be carried out whenever Substrate is used for Smart Contracts. The Gear library is one of the main ones, but there are others like the Ink! which are also very good.
In the program above, the important thing is to highlight the use of the Gear Std that includes the Rust Std. Also the way to store information in static. And finally, to receive information to the contract we must use the special function handle().
To interact with the program we make use of msg messages with various implementations. The first is with msg::source() to get the sender and the second is with msg::load() which does a binary load from the outside.
game_objects.move
// Name of the module and the address to which it belongs.
module 0x1::game_objects {
// The libraries or prefabricated code that we are going to use.
use sui::tx_context::{Self, TxContext};
use sui::object::{Self, UID, ID};
// The structure that will be the object with the option to be persistent in memory.
// To save the object you must have the "key" ability and have an id to identify it.
struct Game has key {
// id to identify the object in memory.
id: UID,
players: vector<address>,
// The Vec_Map stores the round and the amount (vector) of directions or
// players that have participated.
rounds: VecMap<u8, vector<address>>,
}
// The public function takes the "game" and "ctx" parameters explained below.
public fun check_participation(game: &Game, ctx: &mut TxContext) {
let game_rounds = get_rounds(game);
let round_number = (vec_map::size(&game_rounds) as u8);
let participations = vec_map::get(&mut game_rounds, &round_number);
// The address of the player is recorded depending on the round the game is in.
// If the game still doesn't start (empty HashMap), initialize the first round
// and register the player.
if(vec_map::is_empty<u8, vector<address>>(&game_rounds)) {
vec_map::insert(&mut game_rounds, 1, vector::singleton(tx_context::sender(ctx)));
// If all users participated in a round, it advances to the next one and the
// current player is registered.
} else if(vector::length(participations) == vector::length(&get_players(game))) {
vec_map::insert(&mut game_rounds, round_number + 1, vector::empty<address>());
// In the event that a round is halfway over, we only have to register the player.
} else {
let addresses = vec_map::get_mut(&mut game_rounds, &round_number);
vector::push_back(addresses, tx_context::sender(ctx));
};
}
In this version of Move, we must use the Sui standard library to call objects like Vec_Map (a HashMap) or use an ID to identify the tokens on the blockchain.
All codes must begin with the word module, an address, and the name of the module. The module is owned exclusively by the user who writes the contract and is able to decide if the code is private (default), public or public for other specific modules.
The check_participation function is impure because it will make changes to things outside of it; in this case we will make changes to the object that represents the game: Game. To achieve this we must pass the game as an argument, in addition to the Transaction Context that encapsulates data such as the sender or the transaction hash.
In the rest of the code, mutable and immutable references are also highlighted.
Below is a table with the main comparisons between the languages. Please note that there are still many unique features in each of them.

As you can see from the table above, the three languages are similar. Nevertheless there are key differences between them.
While Sway maintains a syntax similar to Vanilla Rust, it also does a great work combining the best solidity features while at the same time prevents reentrancy attacks. It is also one of the most organized languages I have ever used. The compiler’s strict handling enforces segregation between library and contract elements by default. Sway has been easy to learn thanks to its excellent documentation and guides for new users. Speaking personally, this language is one of my favorites.
On the other hand, Move has a very friendly and helpful community. Move is a mature language that has been carefully refined again and again through its stages of development from Facebook to Sui; that has made it simple and practical for Developers. Its documentation is very good and Sui team are very supportive.
Last but not least, writing contracts with Rust is an interesting experience. In it being a general purpose language, one would might think that it is not a very good choice for writing smart contracts. However, libraries written around it have turned out to be very useful. The development specifics can have variations depending on whether it is written with Gear or with ink! or with some other tool based on Substrate. In the end, Blockchain development is unique and Rust turned out to be a very good option for that purpose.
No activity yet