Rust 101-1

My Rust learning note

November 12, 2023

What is Rust and the motivation of learning Rust

Rust is a modern programming language known for its focus on safety and performance. It’s designed to provide memory safety without using a garbage collector, which makes it unique among low-level languages. This is achieved through its innovative ownership and borrowing system, which manages resources and memory at compile time, preventing common bugs like null pointer dereferences and buffer overflows.

In addition, what Rust does better than C++ is that Rust has a better and smarter compiler. In my own experience, Rust’s compiler rustc provides comprehensive compile error message and actionable feedbacks to tell you how to debug. That’s really great for developer experience when it comes to debugging. Big thanks to whoever designed this.

It’s kinda trendy nowadays. People creating cool and better-performance tools by rewriting existing tools by Rust. Like, in web development, we have swc, a compiler that perform better than Babel, and Turbopack, a rust-powered successor to Webpack. Besides, Rust can also be used for writing performant servers and apps.

I think I’m a little bit FOMO but the trend is part of why I want to learn Rust. I also want to develop new backend languages in my skill set. I guess that’s another reason I want to learn Rust.

In this series, I used the

by Google as learning resource. It is used for internal educating at first but recently released to general public. If you want to learn Rust, check out this great resource!

Syntax

Let’s start with a classic hello-world example

fn main() {
    println!("Hello world!");
}

In this example:

  • main function is entry point
  • function is represented by fn
  • println! is a hygienic macro
    • What is Macros?
      • Generate code at compile time
      • Called with !
      • Used for reducing duplicated code
    • What does hygenic mean?

Other basic syntax notes:

  • Mind the type of integer: i8 for 8 bits 2’s complemented representation (-128~127), it will overflow if the number is too large (either compile time error or runtime error could happen).
    • If runtime overflow, will panic at debug mode cargo build or wrap-around at release mode cargo build --release
  • Variables are immutable by default, if you want to make it mutable, add mut.
  • We can use rustup doc in terminal to search for document. Ex: rustup doc std::fmt, it will open the browser and navigate to std::fmt’s documentaion.
  • Print something!: use print!() or println!()
    • print!("{}", x): This expression used positional parameters. Just like C++ and Python.
    • print!("{x}", x=10): This kind of expression used name parameters. You provide the named arguments that declared in the string. If you did not provide, ex: print!("{x}"), it will use the variable x in the scope.
  • For loop: for i in 1..n to iterate from 0 to n-1. It will stop at i == n-1 but for i in 1..=n will include the upper bound.

Types

  • Signed integers: i8, i16, i32
  • Unsigned integers u8, u16, u32
  • Float: f32, f64
  • Strings: &str
  • Unicode scalar values: char
  • Boolean: bool
  • Array: [T; N]
    • Note that the size of array N is part of the type
    • Note that it can only be print! by debug formatter output {:?}
  • Tuple: (). (T), (T1, T2), …
    • The () is like void in other language. It’s special and known as “unit type” since the only value satisfy this type is () itself.

References:

Store a value’s memory address(also a form of borrowing). It’s similar to C++. In the code below, you create a mutable reference to x. And use “dereference” operator * to dereference the reference and mutate the value.

fn main() {
    let mut x: i32 = 10;
    let ref_x: &mut i32 = &mut x;
    *ref_x = 20;
    println!("x: {x}");
}

Some notes about reference

  • Shared reference &: readonly access to data
  • Mutable reference &mut: read and write access to data
  • Need to dereference to mutate the value
  • Only one mutable reference of a particular piece of data in a particular scope. But, you can have multiple shared reference of a piece of data in a particular scope.
  • When print! a reference, rust automatically dereference the reference (Rust does this usually)
  • If you want to print the address, use {:p} formatting.
  • No dangling reference:

    fn main() {
    	let ref_x: &i32;
    	{
    		let x: i32 = 10;
    		ref_x = &x;
    	}
    	println!("ref_x: {ref_x}");
    }

    The code above will result a compiling error. Look this this:

    Compiling playground v0.0.1 (/playground)  
    error[E0597]: `x` does not live long enough  
    --> src/main.rs:5:17  
      |  
    4 | let x: i32 = 10;  
      |     - binding `x` declared here  
    5 | ref_x = &x;  
      |         ^^ borrowed value does not live long enough  
    6 | }  
      | - `x` dropped here while still borrowed  
    7 | println!("ref_x: {ref_x}");  
      |                   ------- borrow later used here  
      
    For more information about this error, try `rustc --explain E0597`.  
    error: could not compile `playground` (bin "playground") due to previous error
    • Reference is actually borrow the value it refers to
    • If the owner doesn’t live long enough, and you use the reference, it will cause this error.
    • The scope of a borrow is limited to where the reference is in scope. Once the reference goes out of scope, the borrow ends.
  • Slice is actually concept of reference and borrow. Slices borrow data from sliced type. In Rust, if you want to use slice of an array, you borrow by reference.

    fn main() {
    	let mut a: [i32; 6] = [10, 20, 30, 40, 50, 60];
    	println!("a: {a:?}");
    
    	let s: &[i32] = &a[2..4];
    
    	println!("s: {s:?}");
    }
    • Slice can be mutable reference or shared reference depends on how you create it. &[T] or &mut [T]

Function

  • The last expression of a function becomes the return value. Simply omit the ; at the end of expression.

    fn foo(){
    	let x: i8 = 1;
    	return x
    }
    // is equivalent to
    fn foo(){
    	let x: i8 = 1;
    	x
    }
  • use /// above function to write document. It’s similar to jsdoc. The document will be available on docs.rs for published crates. (the document is generated by rustdoc tool)

  • Methods are similar to Python’s method. It contains self which is a shared reference or mutable reference to the object.

    struct Rectangle {
        width: u32,
        height: u32,
    }
    
    impl Rectangle {
        fn area(&self) -> u32 {
            self.width * self.height
        }
    
        fn inc_width(&mut self, delta: u32) {
            self.width += delta;
        }
        
    	// static method
    	fn new(width: u32, height: u32) -> Rectangle {
    	    Rectangle { width, height }
    	}
    }
    
    fn main() {
        let mut rect = Rectangle { width: 10, height: 5 };
        println!("old area: {}", rect.area());
        rect.inc_width(5);
        println!("new area: {}", rect.area());
    }
    • If Method doesn’t contain self, it is called static method and can be called directly. Just like Python.
avatar

About author...

Shu-Wei (Frank) Hsu
📍 NY-based Full stack Dev 🗽
Passionate about Web Development and UI/UX.
linkedin github threads email