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