BLOGPOST
rust header image
3327 Logo

Made with love️ for blockchain community by humans from MVP Workshop

Contact us at

[email protected]

All Rights reserved 2024 © 3327

Rust: Under the Hood

For decades, the most chosen languages for low-level programming are C and C++; they offer great power but bring responsibility to the programmer.

Writing code in C++ became hard, so new languages were born - Go and Rust. Both target low-level operations tasks, operating systems, and cryptographic services, but they were born to produce efficient, performant, safe, and scalable code, also easy to use and learn.

Rust and Go remove the concern about the deallocation of memory and save the back of mistakes that lead to memory leaks or segmentation faults. That helps programmers to focus on business coding problems.

They have different memory management solutions. Go uses a garbage collector, while the Rust compiler has a borrow checker that saves the back and makes code memory safe.  

This is the story about Rust, so we will get to look at how the borrow checker moves and manage values in memory. In effect, we will cover unique concepts which give Rust's speed, security, efficiency, and reliability, which makes it a good choice for developing new protocol solutions in the blockchain.  

Knowing Rust allows us to keep up to date with cutting-edge technology and to understand better protocol solutions in development. More and more tools in the web3 world are being developed in Rust, which facilitates the development of a decentralized application. So knowing it allows us to write faster and safer code.

Why fall in love with Rust?

To fall in love with Rust, you need to understand two unique concepts - ownership and borrowing.

The developers must have a certain level of awareness about what happens in memory because, that way, we will have safer and more secure code. 

Let’s compare: 

  • In C, the programmer is responsible for allocating and deallocating memory. 
  • In Go, we have a garbage collector that takes time to look for the variables that are no longer used and clean up memory.  
  • Finally, Rust is a language that achieves memory safety through an ownership-borrowing model. 

Ownership

To make ownership possible, one must follow three rules:

  • Each value has an owner
  • The owner is unique at the time
  • The lifetime of value is equal to the lifetime of the owner

There are three approaches to what happens in memory when we assign one variable to another, the ways how variables and data interact:

  • Move
  • Copy
  • Clone

Move

For the type whose size we don’t know at the compile time, and it can dynamically grow(i.e., Strings, Vectors), the memory is allocated on the heap, where on the stack, we have:

  •  A pointer to the buffer on the heap
  • The capacity of the buffer
  • The length of how much capacity is filled

Example:

let s = String::from("3327");
let s1 = s;

println!("{}", s);  //not ok

When we assign s to s1, the s moves to s1, the data stored on the stack are copied, the allocated buffer on the heap remains intact, and now the s1 is responsible for freeing the heap buffer. In Rust, the memory is automatically free when the variable that owns its value goes out of scope.

The above code will not compile as the change of ownership happens, and s is not valid anymore.

Copy

The memory is allocated on the stack for the type whose size we know at the compile time(i.e., integer, characters, Booleans).

Example:

let x = 3327;
let y = x;

println!("{}", x);

   

When we assign x to y, it copies the value to the y and creates a new value on the stack. 

All primitive types implement the Copy trait, and if struct and enum need to implement the Copy trait explicitly, all the types inside them need to implement the Copy trait.

Clone

Example:

let s = String::from("3327");
let s1 = s.clone();

println!("{}", s); //ok

Above, on string s, we call the clone() method, and now both s and s1 are independent. With clone(), we copied the pointer and the heap data.

Each has its heap buffer and is responsible for freeing them. 

The ownership model prevents double memory freeing of the same heap buffer. The move can also happen when passing a value as a function argument or returning the value from a function. 

In a nutshell, when we assign values, Rust either moves values or copies them. 

The ownership model is the same for the passing value to the functions; a variable will move or copy. The return value from the function can also transfer ownership.

The ownership model is the same pattern every time.

Borrowing

Borrowing is a model we use when we want to use a value without transferring ownership. It is used with the borrow operator &.

Example:

let s1 = String::from("3327");
let s2 = &s1;
let s3 = &s1;

println!("{}", s1);
println!("{}", s2);
println!("{}", s3);

The above code will compile as we borrow s1 to s2 and s3.

Rust compiler

The Rust compiler is very friendly and noteworthy because when you make a mistake, the compiler will give you exact information where you are wrong and information where you can find more about the error, with examples.

The Rust compiler has two unique steps -> HIR - High-Level Intermediate Representation and MIR - Mid-Level Intermediate Representation.

Inside the HIR step, all macros are desugared, and the code is simplified.

The above code that will print to the standard output is HIR representation. Desugared happens on vec!, println! and loop! Optional are these None and Some. Not human readable.

As part of MIR is a borrow checker, we can see how values move in the memory in the following example.

MIR representation is more complex than HIR, as we have done additional desugaring here. MIR is a control flow graph with some basic blocks with some statements, and the last one is a terminator with a link to where to go next. For each value, we need to have some space in memory. These _1, _2 … _27 are all spaces in memory. When we look at the code from HIR, the line is what we see as a compile error. Now the borrow checker checks how the values are moved and what is valid at a time.

To check how some other examples look behind the scenes check-out -> https://play.rust-lang.org/

Conclusion 

We deeply examine how memory management works and how Rust compilers desugar macros, moves values, and optimizes code.

In this post, we covered why Rust is memory safe language through: 

  • Explaining the ownership model
  • Showing how values are moved in memory and how `borrow checker` works

This is our first post about Rust, and we hope it makes you fall in love with Rust. 

If you have some topics that you want to read about Rust, feel free to leave a comment! 

Make sure to follow us on Twitter and LinkedIn as there are more exciting Rust subjects to come!

SHARE

COMMENTS (0)

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.

You may also find this interesting:

Curvy - protocol for fast anonymous transactions on Ethereum

Have you heard of Stealth Addresses (SA)? Do you know what cryptography is used in their background? Have you heard of BaseSAP, Umbra, or Monero? If so, then find out here what the Curvy protocol is and how it differs from the above protocols. There is a great need to introduce private transactions on public […]

By Marija Mikic
September 24, 2024
Cosmos Blog Header
Ticking All the Boxes: How Madara Modifications Enable On-Chain Game Logic

One of the main lessons from the previous two years is that the blockchain gaming space is expanding rapidly, with many new projects and features being built. However, it still remains in its infancy stage, as no title has managed to grab the mainstream market. I’m not talking about metaverse and virtual reality on the blockchain, though it is a massive part of the gaming space. I’m talking about a core gameplay experience that entirely runs on-chain.

By Filip Moldvai
August 10, 2023
How Lens helps us build user-centric Web3 products?

In our previous blog post, we covered one of the biggest challenges Web3 faces: building user (de)centric products. The main points were: One of the ways to solve this challenge is to start with the validation and exploration of the problems users have rather than a tech solution that can be developed. We want to […]

By Milos Novitovic
December 15, 2022
Deep Dive DeFi: Derivatives

Derivatives DeFi has been an emerging market for the past three years in the Web3 world. Protocols have tried to bridge the gap and bring traditional financial instruments to the Web3world, offering users decentralization, full custody, and favorable conditions to make their own choices with minimum intermediation.  So far, we (Web3 users) have been successful […]

By Andrija Raicevic
December 8, 2022
Let’s geek out together!
Would you love to work with us on Web3-related experiments and studies?
Drop us a message