Language features of Rust introduction series - 1

Posted by COOMERDP on Thu, 03 Feb 2022 15:45:35 +0100

1. General

Rust is a compiled language (AOT - ahead of time). It needs to be compiled before generating executable code, which is fundamentally different from interpreted languages such as JavaScript.

2. Variables and variability

Variables are declared through let in Rust, but the variables declared by let are Immutable by default (_Immutable_) variable

let declares a variable and tries to modify its value. An error will be reported at compile time.

fn main() {
    let a = 5;
    a = 6; // error: cannot assign twice to immutable variable
}

We can solve this problem in the following two ways

  • let is decorated with mut keyword when declaring a variable, indicating that it is a variable. It should be noted that Rust is a strongly typed language, so even if it is declared as a variable, it can only be re assigned to the value of the same data type

    fn main() {
      let mut a = 5;
      a = 6;
      // Change data type, compile and report error
      a = "6"; // error: expected integer, found `&str`
    }
  • Using the shadowing attribute, declare again that this variable overrides the previous value and is not limited by the previous data type. After redeclare, the previous variables are hidden

    fn main() {
      let a = '5';   // '5'
      let a = 5;     // 5
      let a = a + 1; // 6
      let a = a * 2; // 12
    }

3. Constant

In Rust, constants are declared through the const keyword. The difference between constants and variables is

  • mut keyword cannot be used
  • A constant must specify a data type when declared
  • Constants can be declared in any scope, including the global scope
  • Constants can only be bound to constant expressions

Naming convention: all letters are capitalized, and words pass between them_ Connection, e.g

const MAX_AGE: u32 = 10_0000;

4. Data type

4.1 scalar type

4.1.1 integer type

  • Signed integers start with i $$[- (2 ^ n-1), 2 ^ {n-1}-1]$$
  • Unsigned integers start with u $$[0, 2 ^ n - 1]$$
  • The number of bits of isize and usize types is determined by the architecture of the computer on which the program is running. On a 64 bit computer, it is 64 bit
  • The default type for integers is i32


4.1.1.1 face value of whole number

  • Hexadecimal starts with 0x
  • Octal starts with 0o
  • Binary starts with 0b
  • The data type of byte type is limited to u8 and starts with b

Except for byte type, type suffixes are allowed for literal values of all values, such as

// Numeric value representing u8 type 57
let a = 57u8;

4.1.1.2 integer overflow

If a u8 (0-255) type variable is set to 256, the following will happen:

  • In development mode, Rust detects overflow and will panic when the program is running
  • In publishing mode, Rust will not detect overflow. When overflow occurs, it will perform surround operation:

    • 256 -> 0
    • 257 -> 1
    • ...

4.1.2 floating point type

  • f32, single precision
  • F64, double precision (the default type of Rust floating point number, because f64 and f32 have the same speed on modern CPU s)
  • The Rust floating point type uses the IEEE-754 standard

4.1.3 boolean type

  • Occupy one byte
  • The symbol is bool and the value is true | false

4.1.4 character type

  • The symbol is char
  • Literal values use single quotes

4.2 composite type

4.2.1 tuple

  • Immutable length after declaration
  • Values of different data types can be grouped together

    // Declare a tuple
    let tup: (i32, f64, u8) = (500, 5.6, 23);
    
    // Get tuple members
    
    // 1. Deconstruction
    let (x, y, z) = tup;
    println!("{}, {}, {}", x, y, z); // 500, 5.6, 23
    
    // 2. Point marking method
    println!("{}, {}, {}", tup.0, tup.1, tup.2); // 500, 5.6, 23

4.2.2 array

  • Like tuple, the length is immutable after declaration
  • Array members must have the same data type
  • Arrays are stored in stack memory
  • The type of the array is expressed in the form of [type; length]

    let arr: [i32; 5] = [1,2,3,4,5];
    
    // Special array declaration method
    let sameMemberArray = [3; 5]; // [3,3,3,3,3]
    
    // Accessing array members
    let f = arr[0]; // 1
    
    // Access array out of bounds - the t rust compiler performs a simple out of bounds check
    let more = arr[6]; // Compilation error
    
    // Access array out of bounds - scenario not detected by the t rust compiler
    let temp = [6,7,8,9];
    let more_temp = arr[temp[0]]; // If the compilation passes, an error will be reported when running

5. Function

  • Use fn keyword to declare function in Rust
  • For function and variable names, use the snake case specification to name them (lowercase words, use splicing)
  • The data type must be specified when defining parameters
  • If you want to return a value in advance, use the return keyword
  • The default return value of the function is an empty tuple

    fn main() {
      let number = get_number(5);
      
      println!("{}", number); // 10
    }
    
    fn get_number(a: i32) -> i32 {
      // If the last line in the function is an expression, the value of the expression will be used as the return value of the function
      // If a semicolon is added at the end of the line, it will be recognized as a statement and will not be used as the return value of the function
      a + 5
    }

6. Control flow

6.1 conditional branching - if

  • It should be noted that if each branch block has a return value, the data type must be the same

    let a = 3;
    
    // if else
    if a == 3 {
      println!("a is 3");
    } else {
      println!("a is not 3");
    }
    
    // Use the characteristics of expression to achieve the effect of ternary expression in other languages
    let b = if a == 3 { 5 } else { 6 }; // 5 

6.2 circulation

  • loop

    • Loop will execute the code in the loop body indefinitely until it is interrupted by break
    • break can provide a return value for the expression of the loop loop
    // Loop loop
    let mut count = 0;
    
    let value = loop {
      count += 1;
      
      if count == 10 {
          break count * 2;
      }
    }
    
    println!("{}", value); // 20
  • while

    let arr = [1,2,3,4,5];
    let mut index = 0;
    
    // Use the while loop to traverse the array
    while index < 5 {
      println!("{}", arr[index]);
      
      index = index + 1;
    }


  • for

    let arr = [1,2,3,4,5];
    
    for item in arr.iter() {
      println!("for item {}", item);
    }
    
    // Use range to implement a specified number of cycles
    // 1. (1.. 5) - > an iterator containing 1,2,3,4
    // 2. Reverse using rev method
    for item in (1..5).rev() {
      println!("range item is {}", item)
    }

7. Ownership

Ownership is the core feature of Rust to ensure memory security without GC

7.1 memory and allocation

When the variable goes out of the scope, Rust will automatically call the drop function to return the memory space to the operating system

7.2 data replication on stack: copy

  • For simple scalar data types, the following code will eventually push two 5's into the stack

    • This is essentially because scalar types implement Copy trait
    • The Copy trail is used for data types stored on the stack, such as integers. Data types that need to allocate memory cannot implement this trail
    • If Copy trait is implemented, the old variable can still be used after assignment
    • If a type implements Drop trait, Rust is not allowed to implement Copy trait
    • Data type with Copy trait

      • integer
      • Floating point number
      • Boolean
      • character
      • Yuanzu (members must be data types with Copy trait)
    let x = 5;
    let y = x;
    
    println!("{}, {}", x, y); // 5, 5

7.3 interaction mode between variables and data: move

  • Corresponding to the composite data type with unknown length, after one variable is assigned to another variable, the former will become invalid (in Rust, it is called move, that is, when the memory space originally pointed by s1 is moved to s2, s1 will become invalid after the move is completed, so as to avoid two release operations on the same memory space when s1 and s2 go out of the scope)

    • double free is a serious bug in other languages that need to manually control memory. It may release the memory being used by other programs, resulting in unknown problems
    let s1 = String::from("haha");
    let s2 = s1;
    
    println!("{}", s1); // error: value borrowed here after move

7.4 ownership and functions

  • In fact, the case of passing values to functions and variables is similar, and move or copy will occur

    fn main() {
      let string = String::from("hello");
    
      // Move, the ownership is moved and passed into the function scope
      move_case(string);
    
      /*
       * Call move_case, the ownership of the memory space pointed to by the string is moved,
       * When move_ When the case call is completed, the memory space pointed to by the string has been released,
       * Therefore, if you access the string later, an error will be reported during compilation
       */
      // println!("{}", string); // value borrowed here after move
    
      // ---------------------------------------------------------------------
    
      let number = 12;
    
      // Copy, pass in a copy of the number value
      copy_case(number);
    
      /*
       * number Is a simple scalar type that implements Copy trait
       * Calling copy_case, only a copy is passed in,
       * So it can still be used in the future
       */
      println!("{}", number);
    
      // ---------------------------------------------------------------------
    
      let bar = String::from("bar");
    
      /*
       * During the call of the following function, the ownership of the memory space pointed to by bar is moved to the function scope,
       * take_ownership_and_return This function takes ownership of a memory space and returns it,
       * Finally, foo gets the ownership of the memory space. In fact, the effect of this code is the same as let foo = bar; identical
       */
      let foo = take_ownership_and_return(bar);
    
      println!("{}", foo)
    }
    
    fn move_case(string: String) {
      println!("{}", string);
    }
    
    fn copy_case(number: u32) {
      println!("{}", number);
    }
    
    fn take_ownership_and_return(s: String) -> String {
      s
    }

8. Stack & Heap

  • stack is a continuous memory space
  • Heap is the hash memory space, and the pointer to heap memory is stored in stack
  • All data stored on the stack must have a known size. Data whose size is unknown at compile time or data whose size may change at run time must be stored on heap

Topics: Front-end Rust