Before learning Rust, I have seen some answers on Zhihu and other platforms, saying that Rust has a steep learning curve and is difficult to learn. Personally, I think it is ok to learn Rust if there are some basic C/C++ concepts, but Rust has many unique concepts, which are different from many mainstream languages, so you need to take some time to read them.

This article is recorded by the author in the Rust learning process. I saw the Rust technology essay in Nuggets, and compiled this basic and incomplete introductory guide, hoping to help some friends who are learning Rust.

Mind maps

A brain map summarizes all the points in this article.

The development history

Rust is a side project created in 2006 by Mozilla employee Craydon Hoare, In 2012, Mozilla announced the release of Servo, a new browser engine based on the Rust language with memory security and concurrency as its first major project.

The first major milestone was the release of the first Rust V1.0 release in 2015. Mozilla recently announced layoffs in the past 2020 due to the outbreak, involving a number of Rust projects and active members of the Rust community, This adds yet more uncertainty to Rust speculation.

On February 9, the Rust Foundation, foundation.rust-lang.org/ was established to separate from Mozilla. The current members of the Foundation’s board include: Amazon, Google, Microsoft, Mozilla and domestic huawei, backed by five big tech companies, are good for Rust, which promotes better development of the language and has better prospects.

The characteristics of

  • Type inferenceRust provides powerful type inference capabilities that we can uselet a = 1;Declaring a variable that looks like JavaScript, the result of type inference in Rust might look something like thislet a: i32 = 1;
  • Memory safety: You’ve probably heard that Rust doesn’t require GC, which is what sets it apart from other existing languages: it doesn’t require manual memory allocation and free memory like C/C++, and it doesn’t require garbage collection languages like Java and Go to wait for the system to collect memory, but it also requires conceptual ownership.
  • Thread safety: Before talking about multithreading we often think a problem is usually data competition, namely multiple threads access the same variable do some writing, usually cause some thread safety problem, there is a concept in Rust ownership, ownership system is the owner of the different objects will be transmitted to a different thread, there is also some of the concept of scope, Multiple threads cannot hold write permissions on the same variable at the same time.
  • Template support: In Rust, when we define a function, if there are more than one type, we can define it by a stereotype. In addition to being used in functions, you can define stereotypes in methods, structs, and enumerations.
  • Pattern matching: provides powerful pattern matching function and match expression collocation, can better control the control flow of the program, single-value matching, multi-value matching and range matching can be achieved.
  • .

The installation

Try online

If you don’t want to install it on your local computer, try Rust’s online code editor as soon as possible.

play.rust-lang.org

Unix-like systems

If you are using MacOS, Linux, or another Unix-like system, you can download Rustup to install Rust, which is both a Rust installer and a version management tool.

Run the following command as prompted.

$ curl --proto '=https' --tlsv12. -sSf https://sh.rustup.rs | sh
Copy the code
  • Rustup update

Rust is currently upgraded very frequently, and if you have been installing for a long time, you can update the latest version with the Rustup update command.

$ rustup update
Copy the code
  • Verifying the Installation
$ rustc --version
rustc 1.50. 0 (cb75ad5db 2021-02-10)
Copy the code
  • The environment variable

All Rust tools are stored in the ~/.cargo/bin directory. Under normal circumstances, environment variables are configured during installation. However, due to differences between platforms and shells, rustup’s modification of environment variables does not take effect until the terminal is restarted or users log in again. The rustc –version command will fail if there is a problem.

  • Remove Rust
$ rustup self uninstall
Copy the code
  • reference
    • Github.com/rust-lang/r…
    • www.rust-lang.org/zh-CN/tools…

Windows system

On Windows, install the executable application rustup-init.exe by downloading it.

  • reference
    • other-ways-to-install-rustup

The editor

Rust supports multiple editors VS CODE, SUBLIME TEXT 3, ATOM, INTELLIJ IDEA, ECLIPSE, VIM, EMACS, GEANY.

Make an introduction with VS CODE, which I often use.

Two VS CODE plug-ins for Rust are recommended: Rust-Analyzer and Rust.

Hello Rust!

Create a project cargo New Hello-rust

View the directory structure tree-a

ā”œā”€ā”€ Heavy Exercises. ā”œā”€ heavy exercisesCopy the code

Look at the contents of Cargo. Toml. This is similar to package.json in Node.js, which declares the information needed for a project.

TOML A new configuration file format.

[package]
name = "hello-rust"
version = "0.1.0 from"
authors = ["May Jun 
      
       "
      @gmail.com>]
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
Copy the code

The main program main.rs uses the abbreviation fn for function to declare a function. Note that println! Plus a sign! It’s not a function, it’s a macro.

fn main(){ println! ("Hello, rust!");
}
Copy the code

After compiling our Rust project, the compiled file will be generated in the target/debug/ directory as follows:

$ cargo build
$ target/debug/hello-rust
Hello, rust!
Copy the code

The Release compilation mode for Rust looks like this:

$ cargo build --release
$ target/release/hello-rust 
Hello, rust!
Copy the code

Rust allows us to build and run the cargo Run command directly. This command will automatically do the compilation for us, as follows:

$ cargo run
   Compiling hello-rust v01.. 0 (/Users/xxxx/study/hello-rust)
    Finished dev [unoptimized + debuginfo] target(s) in 1.00s
     Running `target/debug/hello-rust`
Hello, rust!
Copy the code

The data type

variable

Rust uses lets to declare a variable. Normally variables will be mutable, but variables set by default in Rust are immutable by default. This is one of the ways Rust encourages you to write programs that take full advantage of the security it provides. Of course, if you know that the variable is mutable, that’s fine.

The variable num we declare below does not specify its type, which is one of Rust’s features: type inference

fn main() {
    let num = 1;
    num = 2; println! ("num {}", num);
}
Copy the code

The error cannot assign twice to immutable variable ‘num’ is not passed at the compiler stage.

Prefixes the variable name with the muT keyword to indicate that the variable is mutable.

fn main() {
    let mut num = 1; println! ("num {}", num);
    num = 2; println! ("num {}", num);
}
Copy the code

constant

Constants are declared const and then immutable. Unlike let, we must specify the type of the variable when we declare it.

fn main() {
    const NUM: i8 = 1; println! ("num {}", NUM);
}
Copy the code

scope

A variable is valid only in its scope. In the following example, the variable y is valid within curly braces (its block-level scope), and cannot find value y in this scope is reported if you want to print outside of curly braces.

fn main() {
    let x = 1;
    {
        let y = 2; // y starts at this point
        println!("y {}", y);
    } // This scope ends, y is no longer valid
    println!("x {} y {}", x, y);
    // println! ("x {}", x);
}
Copy the code

Basic data types

Rust is a statically typed language, which means it knows the type of a variable at compile time.

Rust contains four basic data types: integer, floating point, Boolean, and character.

The integer

Integers in Rust are divided into signed and unsigned integers, the difference between which is whether the number is negative. For the secure storage of the signed integer range – (2 n – 1) – (2 ^ {}, n – 1) – (2 n – 1) and 2 n – 1-12 ^ {}, n – 1-12 n – 1-1, n is the length of the below.

The safe storage range for unsigned integers is 0 to 2nāˆ’12^{n}-12nāˆ’1. Isize and usize are determined by the architecture of the system, such as signed integers of type I64 if the system is 64-bit and i32 if the system is 32-bit.

The length of the Signed integer Unsigned integer
8-bit i8 u8
16-bit i16 u16
32-bit i32 u32
64-bit i64 u64
128-bit i128 u128
System architecture isize usize

floating-point

Rust’s floating point provides two data types, F32 and F64, represented as 32-bit and 64-bit, respectively, and 64-bit by default.

fn main() {
    let x = 2.0; // f64
    let y: f32 = 3.0; // f32
    println!("x: {}, y: {}", x, y); // x: 2, y: 3
}
Copy the code

The Boolean

As with most programming languages, booleans in Rust contain two values: true and false.

fn main() {
    let x = true; // bool
    let y: bool = false; // bool
}
Copy the code

character

The character type in Rust is a Unicode code with a size of 4 bytes. The char type is enclosed in single quotes.

fn main() {
    let x = 'x';
    letY = 'šŸ˜Š';println!("x: {}, y: {}", x, y); // x: x, y: šŸ˜Š
}
Copy the code

The compound type

Composite types can combine multiple values into a single category. There are two types of composite types: tuples and arrays.

tuples

Tuples are a common way to combine multiple different values into a compound type. Tuples have a fixed length that cannot be changed once declared.

We deconstruct the data from the declared tuple, as shown in the following example:

fn main() {
    let tup: (i32.f64.char) = (1.1.01šŸ˜Š ', ');let (x, y, z) = tup;
    println!("x: {}, y: {}, z: {}", x, y, z); // x: 1, y: 1.01, z: šŸ˜Š
}
Copy the code

In addition, we can also access the data in the tuple through numerical indexes.

fn main() {
    let tup: (i32.f64.char) = (1.1.01šŸ˜Š ', ');let x = tup.0;
    let y = tup.1;
    let z = tup.2;
    println!("x: {}, y: {}, z: {}", x, y, z); // x: 1, y: 1.01, z: šŸ˜Š
}
Copy the code

An array of

Unlike tuples, where all elements in an Array must be of the same type, arrays in Rust are different from other languages because their Array lengths are fixed as tuples.

fn main() {
    let a: [i32; 5] = [1.2.3.4.5];
    println!("a[0]: {}, a[4]: {}", a[0], a[1]); // a[0]: 1, a[4]: 2
}
Copy the code

Process control

If expression

If statements in Rust must accept a Boolean value, unlike JavaScript, which converts automatically, and can omit parentheses.

fn main() {
    let number = 1;
    if number < 2 {
        println!("true"); // true
    } else {
        println!("false"); }}Copy the code

If the expected value is not a Boolean, an error is reported at compile time.

fn main() {
    let number = 1;
    if number {
        println!("true"); }}Copy the code

After running, the following error is reported:

cargo run
   Compiling hello-rust v0.1.0 (/Users/xxxxx/study/hello-rust)
error[E0308]: mismatched types
 --> src/main.rs:3:8
  |
3 |     if number {
  |        ^^^^^^ expected `bool`, found integer

error: aborting due to previous error
Copy the code

When using the if expression in let, note that the data type of the if else branch is consistent, because Rust is statically typed and all types need to be determined at compile time.

fn main() {
    let condition = true;
    let num = if condition { 1 } else { 0 };
    println!("num: {}", num); / / 1
}
Copy the code

The loop cycle

A loop expression executes a block of code through an infinite loop, and can be used with a break statement to terminate the loop.

fn main() {
    let mut counter = 0;
    let result = loop {
        counter += 1;
        if counter == 10 {
            break counter * 2; }};println!("result: {}", result); / / 20
}
Copy the code

The while loop

We use the while to determine whether or not we need to loop more times. If the condition is true, we continue the loop, and if the condition is false, we exit the loop.

fn main() {
    let mut counter = 3;
    whilecounter ! =0 {
        println!("counter: {}", counter);
        counter -= 1;
    }
    println!("end");
}
Copy the code

The for loop

Using a for loop to iterate over collection elements, such as an array, increases the security of the program that the array size is not exceeded or the read length is insufficient.

fn main() {
    let arr = ['a'.'b'.'c'];
    for element in arr.iter() {
        println!("element: {}", element);
    }
    println!("end");
}
Copy the code

Another way to use the for loop in Rust.

fn main() {
    for number in (1.4).rev() {
        println!("Number: {}", number);
    }
    println!("end");
}
Copy the code

Struct/function/method/enumeration

function

Functions are ubiquitous in Rust code, such as the main function we use. Several features of functions are summarized as follows:

  • Declaration using the fn keyword.
  • Function parameters must be typed.
  • The arrow->By default, the last expression is returned. Be careful not to have a semicolon;
  • You can also return with a semicolon;.
fn main() {
    let res1 = multiply(2.3);
    let res2 = add(2.3);
    print!("multiply {}, add {} \n", res1, res2);
}
fn multiply(x: i32, y: i32) - >i32 {
    x * y
}
fn add(x: i32, y: i32) - >i32 {
    return x + y;
}
Copy the code

The structure of the body

Structure is a kind of custom data types, it consists of a sequence of attribute, this property has its own properties, and values), the structure is a collection of data, like object-oriented programming language in a lightweight class without method, because the Rust itself is not an object-oriented language, the use of the reasonable structure can make our programs more structured.

Define a structure

Using the struct keyword to define a structure, it is also easy to create an instance of the structure, as shown in the following example:

struct User {
    username: String,
    age: i32
}
fn main() {
    // Create the structure instance user1
    let user1 = User {
        username: String::from("May Man."),
        age: 18
    };
    print!("I am: {}, forever {}\n", user1.username, user1.age); // I am: May Jun, forever 18
}
Copy the code

methods

Methods are similar to functions in that they are declared using the fn keyword and have arguments and return values, except that the method is defined in the context of a structure. The first argument to a method is always self, indicating the instance of the structure that called the method.

Rewrite the above struct example by defining a method info to print information on the User struct using the keyword IMPl, which is short for implementation.

struct User {
    username: String,
    age: i32
}
impl User {
    fn info(self) {
        print!("I am: {}, forever {}\n".self.username, self.age); }}fn main() {
    let user1 = User {
        username: String::from("May Man."),
        age: 18
    };
    user1.info();
}
Copy the code

The enumeration

  • Simple enumeration
enum Language {
    Go,
    Rust,
    JavaScript,
}
Copy the code
  • Enumeration of tuple structures
#[derive(Debug)]
enum OpenJS {
    Nodejs,
    React
}
enum Language {
    JavaScript(OpenJS),
}
Copy the code
  • Structure enumeration
#[derive(Debug)]
enum IpAddrKind {
    V4,
    V6,
}
#[derive(Debug)]
struct IpAddr {
    kind: IpAddrKind,
    address: String,}fn main() {
    let home = IpAddr {
        kind: IpAddrKind::V4,
        address: String::from("127.0.0.1"),};let loopback = IpAddr {
        kind: IpAddrKind::V6,
        address: String::from(: : "1"),};println!("{: #? } \n {:#? } \n", home, loopback);
}
Copy the code

Pattern matching

The matching patterns provided by Rust allow you to compare a value to a series of patterns and execute the corresponding code block based on the matched pattern, represented by the expression match.

Define the match match pattern example

For example, we can define an enumeration of languages, representing programming languages, and then define a function get_URl_by_language to get a corresponding address based on the Language. The result of the match expression is the result of this function. It looks a bit like an if expression, but if can only return true or false, and match expressions can return any type.

This example is broken down into three tips:

  • ifGoMatch, because this branch weYou only need to return a value, and you don’t need to use braces.
  • ifRustMatch, this time weTo execute multiple lines of code in a branch, use curly braces.
  • ifJavaScriptMatch, this time weTo bind a value to a matching pattern, you can modify a member of the enumeration to hold the data. This pattern is called a pattern of bound values.
#[derive(Debug)]
enum OpenJS {
    Nodejs,
    React
}
enum Language {
    Go,
    Rust,
    JavaScript(OpenJS),
}

fn get_url_by_language (language: Language) -> String {
    match language {
        Language::Go => String::from("https://golang.org/"),
        Language::Rust => {
            println!("We are learning Rust.");
            String::from("https://www.rust-lang.org/")
        },
        Language::JavaScript(value) => {
            println!("Openjs value {:? }!", value);
            String::from("https://openjsf.org/")}}}fn main() {
    print!("{}\n", get_url_by_language(Language::JavaScript(OpenJS::Nodejs)));
    print!("{}\n", get_url_by_language(Language::JavaScript(OpenJS::React)));
    print!("{}\n", get_url_by_language(Language::Go));
    print!("{}\n", get_url_by_language(Language::Rust));
}
Copy the code

Match Option with Some(value)

Option is an enumerated type defined by Rust systems that has two variables: None for failure and Some(value) for a tuple structure that encapsulates a value of a generic type.

fn something(num: Option<i32- > >)Option<i32> {
    match num {
        None= >None.Some(value) => Some(value + 1),}}fn main() {
    let five = Some(5);
    let six = something(five);
    let none = something(None);

    println!("{:? } {:? }", six, none);
}
Copy the code

Another concept of Rust matching is that matches are exhaustive. None => None in the above example must be written otherwise the pattern None not covered error will be reported and the compile phase will not pass.

A simple example of how Rust matches multiple patterns

  • If you write a fixed value, a single value matches.
  • use|Symbol implements multi-value matching.
  • use..=The symbol implements range matching, and note that it was used previously.This method is now obsolete.
  • _The symbol is a poor match, and Rust checks for any overrides.
fn main() {
    let week_day = 0;
    match week_day {
        1 ..= 4= >println!("Monday to Thursday is so slow..."),
        5= >println!("Wow! It's Friday!"),
        6 | 0= >println!("These two days are weekend, rest!"), _ = >println!("There are only 7 days per week, please enter the correct value...")}; }Copy the code

If let simple control flow

We want to do this only when Some(value) matches, but we don’t want to consider other cases. In order to satisfy the requirement of the match expression, we need to write _ => () to match other cases.

fn main() {
    let five = Some(5);
    match five {
        Some(value) => println!("{}", value),
        _ => ()
    }
}
Copy the code

For scenarios where only one pattern is matched, you can use the if let syntax and write less code, as shown below:

fn main() {
    let five = Some(5);
    if let Some(value) = five {
        println!("{}", value + 1); }}Copy the code

The ownership of

What is ownership?

Ownership is one of Rust’s core features, enabling Rust to keep memory safe without garbage collection. Remember that in Rust every value has a unique owner, and if an assignment is made to the value, ownership of the value is transferred, and the value is destroyed when the owner leaves the scope.

This is a new concept for many people, and we’ll learn more about it later.

Memory Management mode

I also have to say that the current memory management mode, one is similar to THE C language such as the need to manually allocate and release memory, in C language can use malloc/free these two functions, manual memory management if there is omission release, early release, repeated release and other problems are the most likely to create bugs. ** Advanced languages like JavaScript and Java manage their memory automatically with garbage collection, so you don’t have to worry about forgetting to free anything or prematurely freeing it.

Rust in the third kind of way, through the ownership system manages memory, the compiler compiler will do inspection according to a series of rules, if an error occurs, such as a value on the heap its variable x is assigned to a new variable y, if the program is still in the use of x will be an error, because there is only one owner of a value, This example is described in the section “Complex Data Types – Movement” below.

Rust memory allocation and automatic release

Basic data types, such as i32 and CHAR, are fixed in length, can easily be allocated memory, and are stored on the stack and removed from the stack when they leave their scope. For complex data types, such as String, whose length is unknown at the time of writing and whose length may change during execution, the type is stored on the heap.

For String data, the process looks like this:

  • Step 1: Request memory from the operating system during operation.
  • Step 2: Return memory to the operating system when the program is done with the String type.

Here’s a quick example:

fn main() {
    let s1 = String::from("hello");
    print!("{}", s1); // hello
}
Copy the code

As shown in the figure below, the left side is the data stored on the stack, PTR points to the pointer holding the contents of the string, len is the number of bytes of memory used by s1, and Capacity is the number of bytes of memory obtained from the operating system. On the right is the data stored on the heap.

When we execute String::from(“hello”), this is the first operation that implements the memory required for the request. S1 is automatically released when it leaves scope. This is the second step, but the developer doesn’t need to do this manually. Rust calls a special function drop for us, where the String author can place memory-freeing code. Rust automatically calls drop at the end}.

Basic data type – Assignment

Declare the basic data type x and assign it to variable y. Since it is a known value of fixed size, it is placed on the stack. The assignment process is also a copy process.

fn main() {
    let x: i32 = 5;
    let y: i32 = x;
    print!("x {}, y {}", x, y); // x 5, y 5
}
Copy the code

Complex data types – movement

Next, let’s look at an assignment to a complex data type, similar to the above example, but using String.

fn main() {
    let s1 = String::from("hello");
    let s2 = s1;
    print!("s1 {}, s2 {}", s1, s2);
}
Copy the code

After running, the following error is reported:

$ cargo run
   Compiling hello-rust v0.1.0 (/Users/xxx/study/hello-rust)
error[E0382]: borrow of moved value: `s1`
 --> src/main.rs:4:28
  |
2 |     let s1 = String::from("hello");
  |         -- move occurs because `s1` has type `String`, which does not implement the `Copy` trait
3 |     let s2 = s1;
  |              -- value moved here
4 |     print!("s1 {}, s2 {}", s1, s2);
  |                            ^^ value borrowed here after move
Copy the code

String data is stored on the heap, so assignment does not make a copy on the heap. If it does make a copy on the heap during run time, it also has a significant impact on performance once the heap is large.

To ensure security, Rust has one notable detail in this scenario. ** When attempting to copy allocated memory, Rust invalidates the first variable, a process known in Rust as moving. ** Can be thought of as s1 being moved to S2. Again, Rust has only one owner for each value at a time.

Complex data types – copy

Basic data types are stored on the stack, assignment is a copy process, and data on the heap can be copied using a Clone generic function when needed.

fn main() {
    let s1 = String::from("hello");
    let s2 = s1.clone();
    print!("s1 {}, s2 {}", s1, s2); // s1 hello, s2 hello
}
Copy the code

Ownership and function

Passing a value to a function is similar to assigning a value to a variable. The ownership of the value changes, as shown in the following example.

fn main() {
    let s1 = String::from("hello"); // s1 enters scope
    dosomething(s1); // move s1 to dosomething
    print!("s1: {}", s1); // s1 is no longer valid here
}
fn dosomething(s: String) { // s enters scope
    print!("dosomething->s: {}", s);
}// s out of scope is automatically released
Copy the code

One solution to this problem is to transfer ownership of the return value of the function.

fn main() {
    let s1 = String::from("hello");
    let s = dosomething(s1);
    print!("s1: {} \n", s);
}
fn dosomething(s: String) - >String {
    print!("dosomething->s: {} \n", s);
    s
}
Copy the code

However, this implementation is cumbersome and can be implemented simply by reference.

Ownership and reference, borrowing

reference

The symbol & represents a reference, and &s1 creates a reference to the value s1 for us, but does not own it, and there is no transfer of ownership.

fn main() {
    let s1 = String::from("hello");
    dosomething(&s1);
    print!("s1: {} \n", s1); // s1: hello
}
fn dosomething(s: &String) {
    print!("dosomething->s: {} \n", s);
} // s leaves scope, but it has no ownership of the reference value, and nothing happens here...
Copy the code

To borrow

Quoting may be understandable, but what about borrowing? In Rust, taking a reference as a parameter to a function is called borrowing. It is important to note that default variables are immutable by default, and mutable references are required to change the value of a reference. In a particular scope, data has only one mutable reference, which is beneficial to avoid data contention at compile time.

fn main() {
    let mut s1 = String::from("hello");
    dosomething(&mut s1);
    print!("s1: {} \n", s1); // s1: Hello, May
}
fn dosomething(s: &mut String) {
    s.push_str("May Man.");
    print!("dosomething->s: {} \n", s); // dosomething->s: Hello
}
Copy the code

paradigm

A stereotype is an abstraction of a concrete type, often used in strongly typed programming languages to efficiently handle repeating concepts. For example, if we define a function whose parameters may have multiple types of values, we cannot declare it with a specific type. Instead, we can specify the type using a stereotype when we write code and specify the type as an argument when we instantiate it.

Define a paradigm in a function

An example of a size comparison paradigm

To enable the comparison function, we use STD :: CMP ::PartialOrd defined in the Trial library

fn largest<T: std::cmp::PartialOrd>(a: T, b: T) -> T {
    if a > b {
        a
    } else { 
        b
    }
}
fn main() {
    let res1 = largest(1.2);
    let res2 = largest(1.1.2.1);

    print!("res1: {}, res2: {} \n", res1, res2);
}
Copy the code

Example of a paradigm for adding two numbers

fn add<T: std::ops::Add>(a: T, b: T) -> <T>::Output {
    a + b
}
fn main() {
    let res1 = add(1.2);
    let res2 = add(1.1.2.1);

    print!("res1: {}, res2: {}", res1, res2);
}
Copy the code

Define a paradigm in a structure

We usually use T to identify a generic. For example, if we define a coordinate structure, x and y may be different types at the same time, so we need to define two generic parameters. Also, try to avoid defining too many generic parameters in the parameters, which can make the code look hard to read and understand.

struct Point<T1, T2> {
    x: T1,
    y: T2
}

fn main() {
    let res1 = Point { x:1, y: 2};
    let res2 = Point { x:1.1, y: 2.1};
    let res3 = Point { x:1, y: 2.1};
}
Copy the code

Define a paradigm in a method

The stereotype T must be declared after the IMPL, which is T1, T2 in our example because there are multiple stereotype parameters.

struct Point<T1, T2> {
    x: T1,
    y: T2
}
impl<T1, T2> Point<T1, T2> {
    fn x(&self) -> &T1 { &self.x }
    fn y(&self) -> &T2 { &self.y }
}
fn main() {
    let res = Point { x:1, y: 2.1};
    print!("res.x: {}, res.y: {} \n", res.x(), res.y());
}
Copy the code

Define a paradigm in an enumeration

The two enumerations, Option and Result, both have templates provided by the Rust library.

enum Option<T> {
    Some(T),
    None,}enum Result<T, E> {
    Ok(T),
    Err(E),
}
Copy the code

Taking Resut as an example, if we read an environment variable, Ok is fired on success and Err is fired on failure.

fn main() {
    match std::env::var("HOME") {
        Ok(data) => print!("Data: {}", data),
        Err(err) => print!("Error: {}", err)
    }
}

fn main() {
    match std::env::var("xxxx") {
        Ok(data) => print!("Data: {}", data),
        Err(err) => print!("Error: {}", err) // Error: environment variable not found}}Copy the code

Traits – Define shared behaviors

Traits tell the Rust compiler what features a type has that can be shared with other types. They define shared behaviors abstractly. In a nutshell, they put method signatures together and define a set of behaviors necessary to achieve a certain purpose. Interfaces are similar to other languages, with some differences.

Traits that define

Rs defines the behavior Person using traits. For example, each person has a simple introduction that declares, in curly braces, the signature methods needed to implement the trail behavior.

Note that the trait defines a type that has only method signatures, but no concrete implementations. At the end, pub stands for public and can be called by other external modules.

pub trait Person {
  fn intro(&self) - >String;
}
Copy the code

Trait implementation

Implementing the behavior defined in traits, using impL followed by a custom trait, for followed by a custom construct, and writing a method signature that implements the behavior type declaration Person in curly braces, looks similar to interfaces in other object-oriented programming languages.

format! () is returned formatted as a string.

pub struct Worker {
  pub name: String.pub age: i32
}

impl Person for Worker {
  fn intro(&self) - >String {
    format!("My name is {}, age {}, is a worker".self.name, self.age)
  }
}
Copy the code

Introduce the Person. rs module in main.rs.

The mod keyword is used to load the files that need to be imported for our module, and use means to load the traits and constructs defined by the module Person.

mod person;
use person::{ Person, Worker };
fn main() {
    let may_jun = Worker {
        name: String::from("May Man."),
        age: 20
    };
    println!("{}", may_jun.intro());
}
Copy the code

Default implementation of traits

You can provide default behavior for some of the signature methods in traits, so that when a particular type implements traits, you can choose to retain or override the default behavior provided by the signature methods.

Modify person.rs so that intro methods cannot be defined again in workers after default behavior is provided for methods, but can be called after Worker instances.

pub trait Person {
  fn intro_author(&self) - >String;
  fn profession(&self) - >String;
  fn intro(&self) - >String {
    format!("My profile: {}, occupation {}".self.intro_author(), self.profession())
  }
}

pub struct Worker {
  pub name: String.pub age: i32
}

impl Person for Worker {
  fn intro_author(&self) - >String {
    format!(Name: {} Age: {}.self.name, self.age)
  }
  fn profession(&self) - >String {
      format!("Working man")}}Copy the code

Traits as parameters

When using the trait as a parameter, for example, to define the intro method, the item passed in should be the parameter type that implements the Person trait, so that we can directly call the intro() method defined in the Person trait.

pub fn print_intro (item: impl Person) {
  println!("pub fn print_intro {}", item.intro());
}
Copy the code

Modify main.rs to introduce the constructor type that implements the Person trait when print_intro() is executed in the person.rs file.

mod person;
use person::{ Person, Worker, print_intro };
fn main() {
    let may_jun = Worker {
        name: String::from("May Man."),
        age: 20
    };
    println!("{}", may_jun.intro());
    print_intro(may_jun);
}
Copy the code

Trait as the return value

You can define the type that implements a trait as a return value, using the above example to modify the main.rs file.

fn returns_intro () - >impl Person {
    Worker {
        name: String::from("May Man."),
        age: 20
    }
}
print_intro(returns_intro());
Copy the code

Trait Bounds

Trait Bounds are useful for complex scenarios, such as defining multiple types in method parameters that implement traits.

pub fn print_intro (item1: impl Person, item2: impl Person) {}
Copy the code

The Trait Bounds are declared with the generic parameters, rewriting the above example as follows:

pub fn print_intro<T: Person> (item1: T, item2: T) {}
Copy the code

Summary and Reflection

The above is just an introduction to some basic knowledge and unique concepts of Rust. There is not enough space to write about it. Welcome to “May Jun” and we will continue to share it in the future.

If you are interested in Rust, you should check out The Rust Programming Language, which is an official open source guide to learning Rust. There are several video versions of the explanation is basically a reference to this book, the emphasis is still to think more, more practice.

Some of the topics you’ll see in the community are “Could Rust replace C?” Rust provides the ability of system programming. In terms of capability, Rust is also capable of implementing C, but it is hard to replace C/C++. The ecosystem is already large, and not all projects need to be rewritten.

Programming language is just a tool, programming tools for us to achieve some business or functions, do not blindly blow or black a language, such as some often see “XXXX years, XXX cool?” , learn more about the design ideas, advantages and disadvantages behind different programming languages, hone your skills, break through yourself, and make timely choices.

Reference

  • The Rust Programming Language
  • The Rust Programming Language
  • Blog.rust-lang.org/2020/05/15/…

šŸ† technology project go over the language to be bestowed favor on newly Rust | 10 period