Code practice of the rust programming language (Part 1 basic part)

Time:2021-5-5
My relationship with rust started when I found such a modern functional system programming language for system security when I was wandering in the Programming Forum. However, at that time, I only had a general understanding and did not have in-depth study, so I studied it carefully this time.
The learning content includes all the contents of the book "the rust programming language" (completed), all the contents of "the way of rust programming" (unfinished) and some contents of "the rustomicon" (unfinished).

1、 Content overview

I will “rust programming language” learning content is divided into basic learning (1 to 9 chapters) and advanced learning (10 to 19 chapters), these two parts are a rough abbreviation of my learning content. Then it is a simple web server program construction based on the last chapter (Chapter 20) of the book. Finally, it is a simple example comparing the existing Actix web framework of rust community.
This article is an outline of the first half of the “the rust programming language”. The learning code for this part has been distributed on the open source platformGiteeandGitHubOn the platform

2、 Basic learning

2.1 rust variable

Variability and non variability:

By default, the rust variable is immutable, while the MUT key is used to create a variable variable. For example, the following program:

fn main() {
    let x = 5;
    println!("The value of x is: {}", x);//5
    let mut y = 6;
    y = 7
    println!("The value of y is: {}", y);//7
}

Declare constants as follows:

const MAX_POINTS: u32 = 100_000;

Variable masking: use the let keyword to mask variables, that is, the following three X are not actually the same variable

fn main() {
    let x = 5;

    let x = x + 1;

    let x = x * 2;

    println!("The value of x is: {}", x);
}

2.2 data types

Rust is an expression oriented functional programming language. Compared with LISP and Haskell, rust is more like neutralizing LISP’s abstraction and Haskell’s type system. Every statement of rust is an expression, and every expression has its return value, and every return value has its type, so rust can be said that everything has its type. Since rust is a statically typed language, the compiler must know the types of all variables.

Here is a brief introduction to some basic native data types of rust

2.2.1 scalar type

Scalar(scalar)Type represents a single value. Rust has four basic scalar types: integer, floating point, Boolean, and character

Integer:

The following table shows the native integer types of rust:

Length (bit) Signed No sign
8 i8 u8
16 i16 u16
32 i32 u32
64 i64 u64
128 i128 u128
arch isize usize

The signed integer type length (bit) and the unsigned integer type length (bit) of arch depend on the architecture of the computer running the program.

Floating point number:

The following table shows the native float types of rust:

Length (bit) type
32 f32
64 f64

Rust native floating-point number is represented by IEEE-754 standard, F32 is single precision floating-point number, f64 is double precision floating-point number.

Example:

fn main() {
    let x = 2.0; // f64

    let y: f32 = 3.0; // f32
}

Numerical operation:

All number types in rust support basic mathematical operations: addition, subtraction, multiplication, division, and remainder

Boolean type:

The rust boolean type has two possible values:trueandfalse

fn main() {
    let t = true;

    let f: bool = false; //  Explicitly specifying type annotations
}

Boolean types can be converted to integer types 0 and 1, but 0 and 1 cannot be converted to Boolean types.

Character type:

The native character type of rust ‘is a four byte Unicode scalar value, which means that it can represent characters such as Chinese and small expressions.

fn main() {
    let c = 'z';
    let z = 'ℤ';
    let heart_eyed_cat = '😻';
}

2.2.2 composite types

Tuple type:

Tuples are a major way to combine multiple values of other types into a composite type. Tuple length is fixed: once declared, its length will not increase or decrease. A tuple is created using a comma separated list of values contained in parentheses

fn main() {
    let x: (i32, f64, u8) = (500, 6.4, 1);

    let five_hundred = x.0;

    let six_point_four = x.1;

    let one = x.2;
}

Array type:

Arrays in rust are different from those in some other languages because they are fixed length: once declared, their length cannot grow or shrink. An array is a block of memory allocated on the stack, and the elements of the array can be accessed by using the index.

fn main() {
    let a = [1, 2, 3, 4, 5];

    let first = a[0];
    let second = a[1];
    let out = a[5];// Array out of bounds error
}

2.3 function

Rust provides two kinds of functions: named function and anonymous function. Anonymous function is also called closure (advanced feature)

The FN keyword is used to specify the named function, followed by the function name, parameter list and return value (if the return value is omitted, the compiler will automatically add the unit return value ()), and finally the function body (actually a block expression). The function and variable names in rust code use the style of snake case specification. In snake case, all letters are lowercase and words are separated by underscores.

fn main() {
    let x = plus_one(5);

    println!("The value of x is: {}", x);
}

fn plus_one(x: i32) -> i32 {
    let y = x;
    x + y
}

You can see that the value of the block expression behind the function is the value of the last expression, that is, x + y; Instead of the value of the statement (statement returns ())

{
    let y = x;
    x + y
}

2.4 notes

In rust, comments must start with two slashes and continue to the end of the line

// this is a comment

Of course, for programs, rust has a standard set of documentation comments, such as / / and/***/

///this is a doc comment

/**
    this is a
    multiline 
    comment
*/

2.5 control flow

If expression:

fn main() {
    let number = 6;

    if number % 4 == 0 {
        println!("number is divisible by 4");
    } else if number % 3 == 0 {
        println!("number is divisible by 3");
    } else if number % 2 == 0 {
        println!("number is divisible by 2");
    } else {
        println!("number is not divisible by 4, 3, or 2");
    }
}

Use let to get the value of if expression:

fn main() {
    let condition = true;
    let number = if condition {
        5
    } else {
        6
    };

    println!("The value of number is: {}", number);
}

Loop loop expression:

fn main() {
    let mut counter = 0;

    let result = loop {
        counter += 1;

        if counter == 10 {
            break counter * 2;
        }
    };

    println!("The result is {}", result);
}

Here, when the counter value is 10, jump out of the loop loop and multiply the counter by 20

While conditional loop expression:

fn main() {
    let mut number = 3;

    while number != 0 {
        println!("{}!", number);

        number = number - 1;
    }

    println!("LIFTOFF!!!");
}

When the condition is true, the while loop is executed, so here the while loop is executed only three times.

For loop:

fn main() {
    let a = [10, 20, 30, 40, 50];

    for element in a.iter() {
        println!("the value is: {}", element);
    }
}

Here is the iterator generated by a calling iter() function after returning in, so every content in a will be output.

2.5 ownership mechanism

2.5.1 ownership introduction

One of the core functions of rust is ownership. Ownership (system) is the most distinctive feature of rust. It enables rust to ensure memory security without garbage collector.

In fact, in heap data management, some languages have garbage collection mechanism (Java, python), constantly looking for memory that is no longer used when the program is running; In other languages, programmers have to allocate and release memory themselves (C, C + +, d).

The ownership rules of rust are as follows:

  1. Each value in rust has a value called itsownerowner)Variable of.
  2. Value has and has only one owner at any one time.
  3. When the owner (variable) leaves the scope, the value is discarded.

Rust calls the behavior of ownership transfer move, such as the following code:

let s1 = String::from("hello");
let s2 = s1;

println!("{}, world!", s1);

The string:: from() function returns a pointer to a variable in heap memory. If it is used in C + +, it will form a shallow copy, leading to data competition or multiple releases to form a dangling pointer, which will cause potential security vulnerabilities. If it is used in C + +, it will cause performance degradation. In rust, ownership movement is realized, as follows:
Code practice of the rust programming language (Part 1 basic part)
That is, the variable S1 is no longer valid and can no longer be used.

Clone: (deep copy)

let s1 = String::from("hello");
let s2 = s1.clone();

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

At this time, the heap memory variables will be deeply copied to another S2, so that both are effective, but this results in a waste of performance

2.5.2 borrowing

Borrowing refers to taking a reference as a function parameter, that is, not taking ownership, but taking operation right

Immutable borrowing:

fn main() {
    let s1 = String::from("hello");

    let len = calculate_length(&s1);

    println!("The length of '{}' is {}.", s1, len);
}

fn calculate_length(s: &String) -> usize {
    s.len()
}

Variable borrowing (variable reference)

fn main() {
    let mut s = String::from("hello");

    change(&mut s);
}

fn change(some_string: &mut String) {
    some_string.push_str(", world");
}

Rust limits that specific data in a specific scope can have only one variable reference. The advantage of this limitation is that rust can avoid data competition at compile time

String slice:

String slice (string slice) is the reference of some values in string. In fact, all slice types are references

let s = String::from("hello world");

let hello = &s[0..5];
let world = &s[6..11];

Memory references are like this:
Code practice of the rust programming language (Part 1 basic part)

2.6 structure

2.6.1 infrastructure

Rust offers three structures:

  • Named structure
  • Tuple structure
  • Unit structure

Named structure and its method:

struct Rectangle {
    width: u32,
    height: u32,
}

Impl rectangle {// returns an instance of a structure, similar to a constructor
    fn square(size: u32) -> Rectangle {
        Rectangle { width: size, height: size }
    }
}

impl Rectangle {
    fn area(&self) -> u32 {
        self.width * self.height
    }

    fn can_hold(&self, other: &Rectangle) -> bool {
        self.width > other.width && self.height > other.height
    }
}

Impl is the implementation block. From the above, we can see that the implementation block of an instance (called object in object-oriented language) can be multiple and split

Tuple structure:

Tuple structure has the meaning provided by structure name, but there is no specific field name, only field type

struct Color(i32, i32, i32);
struct Point(i32, i32, i32);

let black = Color(0, 0, 0);
let origin = Point(0, 0, 0);

Unit structure:

struct Empty;
fn main() {
    let x = Empty;
    println!("{:?}",& x);// Unit structure address
}

2.6.2 enumerator

Enumerator is defined by enum keyword, followed by enumerator name and enumerator member. Enumerator allows different types of members

enum Message {
    Quit, // enumerating members without parameters
    Move {X: I32, Y: I32}, // anonymous struct
    Write (string), // single parameter enumeration member
    Changecolor (I32, I32, I32), // parameter enumeration member
}

enum IpAddr {
    V4(u8, u8, u8, u8),
    V6(String),
}

let home = IpAddr::V4(127, 0, 0, 1);

let loopback = IpAddr::V6(String::from("::1"));

In fact, enumerators are special structures, both of which can be used to create new types

2.7 pattern matching

Rust has two basic control flow operators for pattern matching: match control flow operator pattern matching and if let concise matching

The match control flow is as follows:

let some_u8_value = 0u8;
match some_u8_value {
    1 => println!("one"),
    3 => println!("three"),
    5 => println!("five"),
    7 => println!("seven"),
    _ => (),// Wildcard matching and remaining matching
}

If let control flow is as follows:

enum UsState {
   Alabama,
   Alaska,
}

enum Coin {
   Penny,
   Nickel,
   Dime,
   Quarter(UsState),
}
let coin = Coin::Penny;

let mut count = 0;
if let Coin::Quarter(state) = coin {
    println!("State quarter from {:?}!", state);
} else {
    count += 1;
}

That is to say, if let is used to deal with matching and mismatching

2.8 rust project management

Rust has a complete module system to manage the organization of code

  • Packages: a feature of cargo that allows you to build, test, and share Crites.
  • Crates: the tree structure of a module that forms a library or binary project.
  • Modules and use: allow you to control the privacy of scopes and paths.
  • Path: a way to name items such as structs, functions, or modules

Crite is a binary item or library.crate rootIs a source file, and the rust compiler takes it as the starting point and forms the root module of crite

Package provides a series of functionsOne or moreCrago is a package management system of rust, similar to Maven of Java and NPM of node.js. A package will contain a cargo.toml file, which describes how to build these crates and the external packages they depend on

At most in one bagcan onlyContains aLibrary crite; The package can contain as many as you wantBinary rate (binary rate); The package contains at least one crite, either library or binary

Module allows us to group the code in a crite to improve readability and reusability. Module can also control the privacy of the item, that is, whether the item can be used by external code (public), or as an internal implementation content, cannot be used by external code (private).

Many modules in a package constitute a module tree. Modules are not only useful for organizing code, but also define the private boundary of rust: this boundary does not allow external code to understand, call and rely on encapsulated implementation details.

By default, all items (functions, methods, structs, enumerations, modules, and constants) in rust are private. Items in the parent module cannot use private items in the child module, but items in the child module can use items in their parent module. This is because submodules encapsulate and hide their implementation details, but submodules can see the context they define.

Can be used before the rust itempubKeyword to make it public

mod front_of_house {
    pub mod hosting {
        pub fn add_to_waitlist() {}
    }
}

Can usesuperStart to build a relative path from the parent module

fn serve_order() {}

mod back_of_house {
    fn fix_incorrect_order() {
        cook_order();
        super::serve_order();
    }

    fn cook_order() {}
}

Can useuseKeyword call path (must be public). Use supports nesting

mod front_of_house {
    pub mod hosting {
        pub fn add_to_waitlist() {}
    }
    pub mod fleeting {
        pub fn add_to_waitlist() {}
    }
}

use crate::front_of_house::{hosting,fleeting};
//use crate::front_ of_ house::*;// Full import

pub fn eat_at_restaurant() {
    hosting::add_to_waitlist();
    hosting::add_to_waitlist();
    fleeting::add_to_waitlist();
    fleeting::add_to_waitlist();
}

Can usepub useKeyword re export item

2.9 assembly

A set is a series of implemented data structures in the rust standard library. Only three are recorded here

  • Vector: allows you to store a variable number of values one by one
  • String: a collection of characters, so it is a common type of string
  • map:hash map The specific key value pairs are associated by hash function

vector:

Vector allows storage of multiple adjacent values of the same data type

let v = vec![100, 32, 57];
for i in &v {
    println!("{}", i);
}
let mut v = vec![100, 32, 57];
for i in &mut v {
    *i += 50;
}

String:

A set of characters (strings) that can be increased in size and changed in content

let s1 = String::from("tic");
let s2 = String::from("tac");
let s3 = String::from("toe");

let s = format!("{}-{}-{}", s1, s2, s3);

Although string is a set of strings and an encapsulation of VEC, it can’t support index. Because of the particularity of U8 characters, operating index may change or separate U8 scalar values and decompose them into multiple single byte characters

map:

The HashMap < K, V > type stores the mapping of a key type K to a value type V. It uses a hashing function to map and decide how to put keys and values into memory

use std::collections::HashMap;

let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);

scores.entry(String::from("Yellow")).or_insert(50);
scores.entry(String::from("Blue")).or_insert(50);

println!("{:?}", scores);

This code usesentryMethod is only inserted when the key does not correspond to a value, so the key value pair “blue” and “50” are inserted into the hash map scores to output {yellow: 50, “blue: 10}

2.10 error handling

Rust combines errors into two main categories:Recoverable errorrecoverable)AndUnrecoverable errorunrecoverable)。 Recoverable errors usually represent situations where it is reasonable to report the error to the user and retry the operation, such as no file found. Unrecoverable errors are often synonymous with bugs, such as trying to access a position beyond the end of an array.

Most languages do not distinguish between these two types of errors and handle them in a way similar to exception. Rust has no exception, but there are recoverable errorsResult<T, E>, and unrecoverable (stop program execution when encountering errors) errorspanic!

panic!:

​ panic! It will cause the program stack to expand (clean stack data) or terminate (exit the program without cleaning stack data)

Here is an example of an unrecoverable error:

fn main() {
    let v = vec![1, 2, 3];

    v[99];
}

Call panic!:

fn main() {
    panic!("crash and burn");
}

Console will output the corresponding error content

Result<T, E>:

TandEIs a generic type parameter,TRepresents the value returned on successOkThe type of data in the member, andERepresents the value returned on failureErrThe type of error in the member

As follows:

use std::fs::File;

fn main() {
    let f = File::open("test.txt");

    let f = match f {
        Ok(file) => file,
        Err(error) => {
            panic!("Problem opening the file: {:?}", error)
        },
    };
}

You can use unwrap for shorthand, ifResultValue is a memberOkunwrapWill returnOkThe value in. IfResultI’m a memberErrunwrapWill call for uspanic!

use std::fs::File;

fn main() {
    let f = File::open("test.txt").unwrap();
}

expectAndunwrapYou can use it the same way: return a file handle or callpanic!Macro.expectUsed to callpanic!Will be passed as a parameter to theexpectInstead ofunwrapUse the defaultpanic!Information

use std::fs::File;

fn main() {
    let f = File::open("hello.txt").expect("Failed to open hello.txt");
}

Can I use it? Implementation propagation error:

use std::error::Error;
use std::fs::File;

fn main() -> Result<(), Box<dyn Error>> {
    let f = File::open("hello.txt")?;

    Ok(())
}

That is? Can be used to return result

The basic part of learning conclusion:

The content of this part is relatively simple. Although it’s all about basic grammar, it’s also very interesting to study the details, such as ownership rules, pattern matching and borrowing checker, which can reflect the design idea of rust.

This work adoptsCC agreementReprint must indicate the author and the link of this article

Author: Chen 0adapter

Recommended Today

Analysis of super comprehensive MySQL statement locking (Part 1)

A series of articles: Analysis of super comprehensive MySQL statement locking (Part 1) Analysis of super comprehensive MySQL statement locking (Part 2) Analysis of super comprehensive MySQL statement locking (Part 2) Preparation in advance Build a system to store heroes of the Three KingdomsheroTable: CREATE TABLE hero ( number INT, name VARCHAR(100), country varchar(100), PRIMARY […]