简介

Rust是一门赋予每个人构建可靠且高效软件能力的编程语言。可靠主要体现在安全性上。其高效不仅限于开发效率,它的执行效率也是令人称赞的,是一种少有的兼顾开发效率和执行效率的语言。Rust 语言由 Mozilla 开发,最早发布于 2014 年 9 月。Rust 的编译器是在 MIT License 和 Apache License 2.0 双重协议声明下的免费开源软件。

Rust极简教程-LMLPHP

特性

  • 高性能:Rust 速度惊人且内存利用率极高。由于没有运行时和垃圾回收,它能够胜任对性能要求特别高的服务,可以在嵌入式设备上运行,还能轻松和其他语言集成。

  • 可靠性:Rust 丰富的类型系统和所有权模型保证了内存安全和线程安全,让您在编译期就能够消除各种各样的错误。

  • 生产力:Rust 拥有出色的文档、友好的编译器和清晰的错误提示信息, 还集成了一流的工具——包管理器和构建工具, 智能地自动补全和类型检验的多编辑器支持, 以及自动格式化代码等等。

  • Rustacean:使用 rust 的攻城狮不叫 ruster 而是叫 Rustacean ,咱也不知道为什么,书上就是这么说的。

特征

  • 作为一门编程语言,rust既可以分类为面向过程编程语言,也可以分类为面向对象编程语言
  • rust拥有精细化的基础数据结构
  • rust中支持泛型,并且拥有泛型枚举
  • rust中支持接口,甚至支持接口默认方法,并且接口既可以作为方法入参也可以作为方法出参

用途

  • 传统命令行程序
  • 嵌入式
  • 网络服务
  • WebAssembly
  • Web服务
  • ......

安装

下载 rustup-init.exe ,双击此可执行程序会打开一个命令行程序,此程序引导安装,具体安装过程:

Rust Visual C++ prerequisites

Rust requires the Microsoft C++ build tools for Visual Studio 2013 or
later, but they don't seem to be installed.

The easiest way to acquire the build tools is by installing Microsoft
Visual C++ Build Tools 2019 which provides just the Visual C++ build
tools:

  https://visualstudio.microsoft.com/visual-cpp-build-tools/

Please ensure the Windows 10 SDK and the English language pack components
are included when installing the Visual C++ Build Tools.

Alternately, you can install Visual Studio 2019, Visual Studio 2017,
Visual Studio 2015, or Visual Studio 2013 and during install select
the "C++ tools":

  https://visualstudio.microsoft.com/downloads/

Install the C++ build tools before proceeding.

If you will be targeting the GNU ABI or otherwise know what you are
doing then it is fine to continue installation without the build
tools, but otherwise, install the C++ build tools before proceeding.

Continue? (y/N) y


Welcome to Rust!

This will download and install the official compiler for the Rust
programming language, and its package manager, Cargo.

Rustup metadata and toolchains will be installed into the Rustup
home directory, located at:

  C:\Users\cml\.rustup

This can be modified with the RUSTUP_HOME environment variable.

The Cargo home directory located at:

  C:\Users\cml\.cargo

This can be modified with the CARGO_HOME environment variable.

The cargo, rustc, rustup and other commands will be added to
Cargo's bin directory, located at:

  C:\Users\cml\.cargo\bin

This path will then be added to your PATH environment variable by
modifying the HKEY_CURRENT_USER/Environment/PATH registry key.

You can uninstall at any time with rustup self uninstall and
these changes will be reverted.

Current installation options:


   default host triple: x86_64-pc-windows-msvc
     default toolchain: stable (default)
               profile: default
  modify PATH variable: yes

1) Proceed with installation (default)
2) Customize installation
3) Cancel installation
>

info: profile set to 'default'
info: default host triple is x86_64-pc-windows-msvc
info: syncing channel updates for 'stable-x86_64-pc-windows-msvc'
info: latest update on 2022-01-20, rust version 1.58.1 (db9d1b20b 2022-01-20)
info: downloading component 'cargo'
  3.8 MiB /   3.8 MiB (100 %)   1.7 MiB/s in  2s ETA:  0s
info: downloading component 'clippy'
  1.6 MiB /   1.6 MiB (100 %)   1.5 MiB/s in  1s ETA:  0s
info: downloading component 'rust-docs'
 18.8 MiB /  18.8 MiB (100 %)   3.3 MiB/s in  5s ETA:  0s
info: downloading component 'rust-std'
 22.9 MiB /  22.9 MiB (100 %)   3.2 MiB/s in  7s ETA:  0s
info: downloading component 'rustc'
 65.2 MiB /  65.2 MiB (100 %) 493.2 KiB/s in  1m 14s ETA:  0s
info: downloading component 'rustfmt'
  2.2 MiB /   2.2 MiB (100 %) 631.2 KiB/s in  3s ETA:  0s
info: installing component 'cargo'
info: installing component 'clippy'
info: installing component 'rust-docs'
 18.8 MiB /  18.8 MiB (100 %)   1.9 MiB/s in  6s ETA:  0s
info: installing component 'rust-std'
 22.9 MiB /  22.9 MiB (100 %)  10.5 MiB/s in  2s ETA:  0s
info: installing component 'rustc'
 65.2 MiB /  65.2 MiB (100 %)  12.2 MiB/s in  5s ETA:  0s
info: installing component 'rustfmt'
info: default toolchain set to 'stable-x86_64-pc-windows-msvc'

  stable-x86_64-pc-windows-msvc installed - rustc 1.58.1 (db9d1b20b 2022-01-20)


Rust is installed now. Great!

To get started you may need to restart your current shell.
This would reload its PATH environment variable to include
Cargo's bin directory (%USERPROFILE%\.cargo\bin).

Press the Enter key to continue.

核心组件

  • rustup:安装、更新rust用的,rustup doc 可查看安装包中的官方指导文档
  • cargo:包管理器和编译源代码工具
  • rustc:将rust源文件编译成可执行程序
  • rustdoc:为rust项目生成说明文档

常用命令

  • 同理,作为调用方,我们可以定义一个Option
  • Option 源码:

    enum Option<T> {
        Some(T),
        None,}
    

    使用举例:

    
    // 整数除法。
    fn checked_division(dividend: i32, divisor: i32) -> Option<i32> {
        if divisor == 0 {
            // 失败表示成 `None` 取值
            None
        } else {
            // 结果 Result 被包装到 `Some` 取值中
            Some(dividend / divisor)
        }
    }
    // 此函数处理可能失败的除法
    fn try_division(dividend: i32, divisor: i32) {
        // `Option` 值可以进行模式匹配,就和其他枚举类型一样
        let result = checked_division(dividend, divisor);
        if result!=Option::None
        {
            //获取返回值
            println!("Nice, result is :{:?}", result.unwrap());
        }
     
        match checked_division(dividend, divisor) {
            None => println!("{} / {} failed!", dividend, divisor),
            Some(quotient) => {
                println!("{} / {} = {}", dividend, divisor, quotient)
            }
        }
    }
    fn main() {
        try_division(4, 2);
        try_division(1, 0);
    }
    
    
    

    以上代码输出:

    Nice, result is :2
    4 / 2 = 2
    1 / 0 failed!
    

    集合

    Rust 标准库中包含一系列被称为 集合(collections)的非常有用的数据结构。大部分其他数据类型都代表一个特定的值,不过集合可以包含多个值。不同于内建的数组和元组类型,集合指向的数据是储存在堆上的,这意味着数据的数量不必在编译时就已知,并且还可以随着程序的运行增长或缩小。

    vector容器

    vector 允许我们在一个单独的数据结构中储存多个值,所有值在内存中彼此相邻排列。vector 只能储存相同类型的值。

    如果借助枚举,有时候 vector 也可以变相存储不同类型的值。

    示例代码:

    
    fn main() {
        println!("Hello, world!");
        //新建一个 vector
        let mut v: Vec<i32> = Vec::new();
        let v2 = vec![1, 2, 3];
        println!("v is : {:?} , v2 is : {:?}", v, v2);
        //更新
        v.push(888);
        v.push(111);
        v.push(222);
        v.push(333);
        //删除
        v.remove(0);
        //查询
        let two = v[1];
        let two2 = &v[1];
        //get方法返回的是Option
        let three = v.get(2);
        println!("two is : {} , three is  : {:?}", two, three);
        //遍历
        let arr = vec![100, 32, 57];
        for i in &arr {
            println!("arr item for --> {}", i);
        }
        //借助枚举,vector中可以存储不同的数据类型
        let row = vec![
            SpreadsheetCell::Int(3),
            SpreadsheetCell::Text(String::from("blue")),
            SpreadsheetCell::Float(10.12),
        ];
        let  s = &row[1];
        println!("row is : {:?} , s is : {:?} ", row, s);
    }
    #[derive(Debug)]
    enum SpreadsheetCell {
        Int(i32),
        Float(f64),
        Text(String),
    }
    
    
    

    以上代码输出:

    Hello, world!
    v is : [] , v2 is : [1, 2, 3]
    two is : 222 , three is  : Some(333)
    arr item for --> 100
    arr item for --> 32
    arr item for --> 57
    row is : [Int(3), Text("blue"), Float(10.12)] , s is : Text("blue")
    

    String

    你没有看错,rust 中,字符串也是集合,是什么集合呢? 是“字符”的集合。注意:上面提到过字符是 rust 的基础标量类型,但是字符串不是标量类型,而是集合类型。

    • 字符串是UTF-8编码,中文不会乱码,一个英文字符占1字节,一个汉字占2字节。
    • 字符串底层实际是一个结构体,数据存储在结构体中的 Vec (vector容器)里面 。
    • rust中不建议用下标访问字符串元素:rust中的字符串大部分时候和 java 中的不太一样,相比较而言更低级(对部分使用者来说),但是rust中的字符串更能真实的反应文字本来的样子。

    更新字符串的方式:

    • push_str
    • push
    • 加号
    • format!

    示例代码:

    
    fn main() {
        println!("Hello, world!");
        //创建
        let mut s = String::new();
        let data = "initial contents";
        let ss = data.to_string();
        let sss = String::from("你好!");
        println!("s is : {} , ss is  : {} , sss is  : {}", s, ss, sss);
        //更新
        let mut word = String::from("Aa");
        word.push_str("Bb");
        word.push('C');
        word += "cDd";
        word = format!("{}{}-{}", word, "Ee", "Ff");
        println!("final  word is : {} ", word);
        //不要使用下标访问
        let p = String::from("hello");
        let p1 = &p[0..3];
        let k = String::from("你好我是陈明亮");
        let k1 = &k[0..3];
        //输出结果可能不是大多数人预期:p1 is : hel , k1 is : 你
        println!("p1 is : {} , k1 is : {}", p1, k1);
        //遍历
        word+="北京";
        for i in word.chars() {
            println!("word ---> item is :{}", i);
        }
    }
    
    
    

    以上代码输出:

    
    Hello, world!
    s is :  , ss is  : initial contents , sss is  : 你好!
    final  word is : AaBbCcDdEe-Ff
    p1 is : hel , k1 is : 你
    word ---> item is :A
    word ---> item is :a
    word ---> item is :B
    word ---> item is :b
    word ---> item is :C
    word ---> item is :c
    word ---> item is :D
    word ---> item is :d
    word ---> item is :E
    word ---> item is :e
    word ---> item is :-
    word ---> item is :F
    word ---> item is :f
    word ---> item is :b
    word ---> item is :C
    word ---> item is :c
    word ---> item is :D
    word ---> item is :d
    word ---> item is :E
    word ---> item is :e
    word ---> item is :-
    word ---> item is :F
    word ---> item is :f
    word ---> item is :北
    word ---> item is :京
    
    

    代码组织

    讲道理,rust 中的代码组织相比 java 、CSharp 这些所谓高级语言,要复杂的多。大概包含两个部分:命名空间和访问权限。

    命名空间

    rust 中针对包管理主要有三个概念:包(package),箱(crate),模块(module)。三个概念形成一个树状结构,包中包含箱,箱中包含模块。

    • 包:cargo new 出来的工程就是一个包,包中包含箱。一个包会包含有一个 Cargo.toml 文件,阐述如何去构建里面的 crate
    • 箱:箱是二进制程序文件或者库文件,箱中包含模块。在 https://crates.io 这个网站中有很多别人开发好的箱,我们可以通过引入这些箱提升我们的开发效率。
    • 模块:基于业务功能的实现和代码组织的考量,我们将功能相似或共同完成一个功能的代码分到一个组里面,这个组就是模块了,一个箱中往往包含多个模块。当然,模块中可以包含子模块。

    访问其他mod和crate

    访问权限和关键字

    Rust 中默认所有项(函数、方法、结构体、枚举、模块和常量)都是私有的。父模块中的项不能使用子模块中的私有项,但是子模块中的项可以使用他们父模块中的项。

    关键字:

    • pub:添加 pub 访问修饰符的项将被公开,所有人可以访问
    • use:将其他命名空间引入当前作用域
    • as :使用 as 可将use进来的命名空间取一个别名,一般用来解决当两个需 use 对象的名称相同时候区分他们
    • mod :定义一个 module 或者导入一个 module。

    访问其他文件中的对象

    • 一个文件默认就是 module ,所以 other_rs_file.rs 就是一个名为 other_rs_file 的 module

    • main.rs 中如果要使用 other_rs_file 中的对象需要先导入这个 module: mod other_rs_file ,使用 mod::对象名 访问其中对象

    • 如果 other_rs_file.rs 中定义了子 module , main.rs 仅需导入顶层 module (即 mod other_rs_file) ,使用 mod::子mod::对象名 访问其中对象

    • rust 默认将rs文件识别为一个 module 处理, 但是无法将文件夹识别为一个  module ,所以如果我们要访问entity文件夹中dept.rs文件中的对象,需要在entiry文件夹中新增一个mod.rs 并在其中定义 dept module ,访问其中对象的时候同样仅需导入顶层 mod ,然后通过 entity::dept::Dept 方式访问 dept 对象。

    工程目录文件结构:

    │  main.rs
    │  other_rs_file.rs
    └─entity
            dept.rs
            mod.rs
    

    示例代码:
    other_rs_file.rs:

    
    pub mod other {
        #[derive(Debug)]
        pub struct User {
            //结构体属性默认是私有的,若外部需要访问需添加pub关键字
            pub name: String,
            pub age: i32,
        }
        fn init_user(n: String) -> User {
            let u = User {
                name: String::from(n),
                age: 100,
            };
            return u;
        }
        pub fn add_user(n: String, a: i32) -> bool {
            return false;
        }
        //枚举不用给元素添加 pub ,只要枚举是公开的,里面元素就是公开的
        #[derive(Debug)]
        pub enum IpAddrKind {
            IPV4,
            IPV6,
        }
    }
    
    
    

    dept.rs:

    #[derive(Debug)]
    pub struct Dept {
        pub name: String,
        pub no: i32,
    }
    

    mod.rs:

    pub mod dept;
    

    main.rs:

    
    mod other_rs_file;
    mod  entity;
    fn main() {
        println!("Hello, world!");
        /*
         * main.rs 同级 rs文件
         * 引入其他rs文件,需先导入module ,通过mod名称(即文件名称)::对象名称 访问其内部对象
         * 如果在其他文件内部定义了子 module , 导入方式不变,访问方式:mod名称::子mod名称::对象名称
         * 导入时候只导入顶层mod
         */
        let u = other_rs_file::other::User {
            name: String::from("cml"),
            age: 9,
        };
        println!("实例化另外一个rs文件中定义的结构体,u is: {:?}", u);
        /*
        * main.rs 上级文件夹 rs 文件
        * rust 默认将rs文件识别为一个 module 处理, 但是无法将文件夹识别为一个  module ,所以如果我们要访问entity文件夹中dept.rs文件中的对象,需要在entiry文件夹中新增一个mod.rs 并在其中定义 dept module。
        */
        let d=entity::dept::Dept{
            name:String::from("技术部门"),
            no:5,
        };
       
        println!("实例化另外一个rs文件中定义的结构体,d is: {:?}", d);
    }
    

    main输出:

    Hello, world!
    实例化另外一个rs文件中定义的结构体,u is: User { name: "cml", age: 9 }
    实例化另外一个rs文件中定义的结构体,d is: Dept { name: "技术部门", no: 5 }
    

    使用第三方库

    • 使用 use 导入其他的 crate
    • 默认的 crate 源是 crate.io 这个网站
    • 导入其他 crate 需要在 Cargo.toml 中定义相关 dependencies。标准库除外,标准库中的 crate 可以直接导入使用
    • 使用花括号导入一个 crate 下多个 module :use std::{self,io,cmp::Ordering}
    • 使用 * 号导入一个 crate 下所有 module:use std:😗;

    示例代码:
    Cargo.toml:

    [package]
    name = "Crate"
    version = "0.1.0"
    edition = "2021"
    
    
    [dependencies]
    rand = "0.5.5"
    

    main.rs:

    
    /*
    * 导入标准库
    */
    use std::fmt::Result as FmtResult;
    use std::io::Result as IoResult;
    // use std::cmp::Ordering
    use std::*;
    use std::{self, cmp::Ordering, io};
    /*
    * 导入外部第三方库
    */
    use rand::{thread_rng, Rng};
    fn main() {
        println!("Hello, world!");
        let fmtr = FmtResult::Ok;
        let ior = IoResult::Ok("成功");
        println!("fmtr 无法打印 , ior is : {:?}", ior);
        //生成随机数
        let mut rng = thread_rng();
        let x: u32 = rng.gen();
        println!("x is :{}", x);
    }
    
    
    

    异常处理

    • rust中的异常不叫 exception , 而是叫做 panic (恐慌),意思是编译器执行到这里的代码害怕了,不敢继续执行了,就恐慌了,相当于程序就终止了。
    • rust官方对于错误处理有两个类别:可恢复错误(如打开文件失败算是一个错误,但是这种错误可以重试);不可恢复错误(如索引越界,这就是实打实的bug了,程序将崩溃)
    • 对于不可恢复的错误,官方的做法是提供一个Result<T, E> 枚举类来处理,如 std::fs::File--->Result 。若程序运行成功返回 Ok(T) ,即响应结果,若程序运行出现错误返回 Err(E) ,即错误信息。

    工程结构:

    │  Cargo.lock
    │  Cargo.toml
    │  hello.txt
    ├─src
    │      main.rs
    

    示例代码:

    use std::fs;
    use std::fs::File;
    use std::io;
    use std::io::Read;
    
    fn main() {
        println!("Hello, world!");
    
        let mut f = File::open("hello.txt");
        match f {
            Ok(file) => {
                println!("File opened successfully.");
            }
            Err(err) => {
                //打开失败
                println!("Failed to open the file.");
                match err.kind() {
                    //打开失败原因,不存在情况创建
                    io::ErrorKind::NotFound => {
                        println!("File Not Found. soon create . ");
                        File::create("hello.txt");
                        fs::write("hello.txt", "I am  hello.txt");
                    }
                    _ => {
                        //其他原因直接抛 panic
                        panic!("Failed to open the file.");
                    }
                }
            }
        }
    
        //打印 txt内容
        let mut file = std::fs::File::open("hello.txt").unwrap();
        let mut contents = String::new();
        file.read_to_string(&mut contents).unwrap();
        print!("txt 的内容是:{}", contents);
    }
    
    

    以上代码输出:

    Hello, world!
    Failed to open the file.
    File Not Found. soon create .
    txt 的内容是:I am  hello.txt
    

    泛型

    泛型概念

    泛型这种编程语言得设计绝非 rust 独有的,实际上在C# , Java 这些语言中早就引入了泛型的设计思想。泛型即类型的泛化,在编写代码的时候并不知道具体的数据类型或者数据结构是什么样子的,而是定义一个标识,在运行时此标识可动态替换为实际的数据类型。很显然这种设计能够让开发人员避免编写很多相似的重复的代码。

    在 rust 中,可以在如下位置编写泛型代码:

    • 泛型函数
    • 泛型结构体
    • 泛型枚举

    示例代码:

    
    fn main() {
        println!("Hello, world!-->泛型");
        /*
         * 泛型函数
         */
        let a = [2, 4, 6, 3, 1];
        println!("数字数组中最大元素是 = {}", max(&a));
        let b = ["A", "B", "C"];
        println!("字符数组中最大元素是 = {}", max(&b));
        /*
         * 泛型结构体
         */
        // i32
        let p1 = Point { x: 1, y: 2 };
        //f64
        let p2 = Point { x: 1.0, y: 2.0 };
        println!("p1  ={:?} , p2 = {:?}", p1, p2);
    }
    //求最大元素
    fn max<T: std::cmp::PartialOrd>(array: &[T]) -> &T {
        let mut max_index = 0;
        let mut i = 1;
        while i < array.len() {
            if array[i] > array[max_index] {
                max_index = i;
            }
            i += 1;
        }
        return &array[max_index];
    }
    //泛型结构体
    #[derive(Debug)]
    struct Point<T> {
        x: T,
        y: T,
    }
    
    
    

    以上代码输出:

    Hello, world!-->泛型
    数字数组中最大元素是 = 6
    字符数组中最大元素是 = C
    p1  =Point { x: 1, y: 2 } , p2 = Point { x: 1.0, y: 2.0 }
    

    特性(接口)

    特性(trait)概念接近于 Java 中的接口(Interface),但两者不完全相同。特性与接口相同的地方在于它们都是一种行为规范,可以用于标识哪些类有哪些方法

    一个对象的行为由其可供调用的方法构成。如果可以对不同类型调用相同的方法的话,这些类型就可以共享相同的行为了。trait 定义是一种将方法签名组合起来的方法,目的是定义一个实现某些目的所必需的行为的集合。

    • 一个结构体可以同时实现多个接口,但是一个结构体的继承者类(即 impl , rust 中实现类无自己的类名)仅可实现一个接口
    • trait 接口可以有默认实现方法,这个就和java8一样,接口可以有默认的已实现方法

    示例代码:

    
    use std::time::{SystemTime, UNIX_EPOCH};
    fn main() {
        println!("Hello, world!--->特性(接口)");
        //实例化Person
        let p = Person {
            name: String::from("cml"),
            age: 100,
        };
        let d = p.describe();
        println!("d = {}", d);
        let s = p.tostring();
        println!("s={}", s);
        let t = p.nowTime();
        println!("t ={:?}", t);
    }
    //定义一个描述接口
    trait Descriptive {
        fn describe(&self) -> String;
        //接口默认方法
        fn nowTime(&self) -> SystemTime {
            let start = SystemTime::now();
            return start;
        }
    }
    //定义一个 tostring 接口
    trait ToString {
        fn tostring(&self) -> String;
    }
    //定义一个结构体
    #[derive(Debug)]
    struct Person {
        name: String,
        age: u8,
    }
    //结构体的一个继承类实现了 Descriptive 接口
    impl Descriptive for Person {
        fn describe(&self) -> String {
            format!(
                "I am   Person , name is : {} , age is : {}",
                self.name, self.age
            )
        }
    }
    impl ToString for Person {
        fn tostring(&self) -> String {
            format!("Person:{{name:{},age:{}}}", self.name, self.age)
        }
    }
    
    
    

    以上代码输出:

    Hello, world!--->特性(接口)
    d = I am   Person , name is : cml , age is : 100
    s=Person:{name:cml,age:100}
    t =SystemTime { intervals: 132914629768805244 }
    

    文件和IO

    rust 标准库中的 std::fs 可以用来操作文件

    示例代码:

    
    use std::fs;
    use std::fs::OpenOptions;
    use std::io::prelude::*;
    fn main() {
        println!("Hello, world!--->文件io");
        //读取文件内容,一次性读取
        //文件内容:This is a text file.
        let text = fs::read_to_string("text.txt").unwrap();
        println!("txt文件的内容是:{}", text);
        //写入文件,追加
        //追加完成文件内容:
        append();
        println!("追加后,txt文件的内容是:{}", text);
        //写入文件,会覆盖
        //覆盖之后文件内容:
        fs::write("text.txt", "FROM RUST PROGRAM").unwrap();
        println!("覆盖写入后,txt文件的内容是:{}", text);
    }
    //追加
    fn append() -> std::io::Result<()> {
        let mut file = OpenOptions::new().append(true).open("text.txt")?;
        file.write(b" APPEND WORD")?;
        Ok(())
    }
    

    以上代码输出:

    Hello, world!--->文件io
    txt文件的内容是:This is a text file.
    追加后,txt文件的内容是:This is a text file.
    覆盖写入后,txt文件的内容是:This is a text file.
    

    面向对象

    面向对象编程(OOP)思想是一个概念,是一种思想指导,围绕“对象”展开,从万物皆是对象的角度出发,一个 crate , 一个 module ,一个 struct ,一个枚举等等都是一个个独立的对象,能自主表达一个事物、某种特征。而运用封装、继承、多态等手段可以让对象与对象之间产生某种联系,进而表达更多的事物,解决更多的问题。

    面向对象思想是构建大型应用软件系统的基石。

    rust 语言中 可以通过 结构体,枚举,特性(trait)等来实现oop思想。

    并发编程

    rust 中的并发编程主要得益与 线程 、 消息传递和互斥锁。
    Rust 语言是满足多线程特性的,所以 rust 可以满足 主-->子 多任务应用场景。

    消息传递有点类似与消息队列,但消息队列一般跨进程或线程,而 rust 中的消息传递主要是主子线程中数据的传递。

    线程

    • rust 标准库 std::thread 中的 spawn 函数用来创建一个新的线程
    • 通过在主线程中 join 子线程,让主线程阻塞,确保所有子线程执行完毕再继续执行子线程。这种情况在实际开发中非常常见,比如在获取用户个人中心信息接口中,我们将获取基本信息、订单信息、积分信息、用户消息信息分别放在4个子线程中并发执行,在主线程中等待所有子线程都响应了再将结果汇总返回给调用方。

    示例代码:

    
    use std::thread;
    use std::time::Duration;
    fn main() {
        println!("[主线程] Hello, world!--->线程");
        //创建子线程
        for item in 0..5 {
            println!("[主线程] 即将创建一个子线程,当前循环变量:{}", item);
            let child_thread = thread::spawn(|| {
                for i in 0..2 {
                    println!("[子线程] hi number {} from the spawned thread!", i);
                    thread::sleep(Duration::from_millis(1));
                }
            });
        }
        for i in 0..3 {
            println!("[主线程] hi number {} from the main thread!", i);
            thread::sleep(Duration::from_millis(1));
        }
        //线程join
        let child1 = thread::spawn(|| {
            println!("[child-1]hi I am spawned thread!");
            thread::sleep(Duration::from_millis(1));
        });
        let child2 = thread::spawn(|| {
            println!("[child-2]hi I am spawned thread!");
            thread::sleep(Duration::from_millis(1));
        });
        child1.join().unwrap();
        child2.join().unwrap();
        println!("[main]hi I am main thread!");
    }
    

    以上代码输出:

    [主线程] Hello, world!--->线程
    [主线程] 即将创建一个子线程,当前循环变量:0
    [主线程] 即将创建一个子线程,当前循环变量:1
    [子线程] hi number 0 from the spawned thread!
    [主线程] 即将创建一个子线程,当前循环变量:2
    [子线程] hi number 0 from the spawned thread!
    [主线程] 即将创建一个子线程,当前循环变量:3
    [子线程] hi number 0 from the spawned thread!
    [主线程] 即将创建一个子线程,当前循环变量:4
    [子线程] hi number 0 from the spawned thread!
    [主线程] hi number 0 from the main thread!
    [子线程] hi number 0 from the spawned thread!
    [子线程] hi number 1 from the spawned thread!
    [子线程] hi number 1 from the spawned thread!
    [子线程] hi number 1 from the spawned thread!
    [子线程] hi number 1 from the spawned thread!
    [主线程] hi number 1 from the main thread!
    [子线程] hi number 1 from the spawned thread!
    [主线程] hi number 2 from the main thread!
    [child-1]hi I am spawned thread!
    [child-2]hi I am spawned thread!
    [main]hi I am main thread!
    

    消息传递

    以下示例演示了子线程获得了主线程的发送者 tx,并调用了它的 send 方法发送数据,然后主线程就通过对应的接收者 rx 接收到了发送的数据。

    无法在子线程中发送而在另外一个子线程中接收

    示例代码:

    use std::sync::mpsc;
    use std::thread;
    fn main() {
        println!("Hello, world!--->消息传递");
        let p1 = Person {
            active: true,
            name: String::from("cml"),
            email: String::from("cnaylor@163.com"),
            sign_in_count: 99,
            tuple: (8, 8),
        };
        let (tx, rx) = mpsc::channel();
        thread::spawn(move || {
            tx.send(p1).unwrap();
        });
        let received = rx.recv().unwrap();
        println!("Got: {:?}", received);
    }
    #[derive(Debug)]
    struct Person {
        active: bool,
        name: String,
        email: String,
        sign_in_count: u64,
        tuple: (u32, u32),
    }
    

    以上代码输出:

    Hello, world!--->消息传递
    Got: Person { active: true, name: "cml", email: "cnaylor@163.com", sign_in_count: 99, tuple: (8, 8) }
    

    互斥锁

    互斥锁(mutex)是 mutual exclusion 的缩写,也就是说,任意时刻,其只允许一个线程访问某些数据。为了访问互斥器中的数据,线程首先需要通过获取互斥器的 锁(lock)来表明其希望访问数据。锁是一个作为互斥器一部分的数据结构,它记录谁有数据的排他访问权。因此,我们描述互斥器为通过锁系统 保护(guarding)其数据。

    以下示例演示了在主线程中创建一个值,并在多个子线程中修改此值,最终等所有子线程处理完毕,主线程打印最终值。为了让变量能够跨线程之间共享,引入了std::sync::Arc 这个结构体。

    示例代码:

    
    use std::sync::{Arc, Mutex};
    use std::thread;
    fn main() {
        println!("Hello, world!--->互斥锁");
        //Arc 原子引用计数器,确保在多个线程中共享数据;counter初始值为0    
        let counter = Arc::new(Mutex::new(0));
        println!("counter 初始值为:0");
        //定义一个子线程集合
        let mut handles = vec![];
        for _ in 0..10 {
            //将counter值从主线程中克隆,并赋值给私有变量 counter
            let counter = Arc::clone(&counter);
            let handle = thread::spawn(move || {
                //获取互斥锁,并将counter值加一
                let mut num = counter.lock().unwrap();
                *num += 1;
                let thread_id = thread::current().id();
                println!("[子线程:{:?}]中将 counter 的值修改为:{}", thread_id, num);
            });
            //将创建的子线程存储到子线程集合中
            handles.push(handle);
        }
        //将所有的子线程join到主线程,确保主线程等待所有子线程执行成功
        for handle in handles {
            handle.join().unwrap();
        }
        let result = *counter.lock().unwrap();
        println!("Result: {}", result);
    }
    
    

    以上代码输出:

    Hello, world!--->互斥锁
    counter 初始值为:0
    [子线程:ThreadId(2)]中将 counter 的值修改为:1
    [子线程:ThreadId(4)]中将 counter 的值修改为:2
    [子线程:ThreadId(3)]中将 counter 的值修改为:3
    [子线程:ThreadId(5)]中将 counter 的值修改为:4
    [子线程:ThreadId(8)]中将 counter 的值修改为:5
    [子线程:ThreadId(6)]中将 counter 的值修改为:6
    [子线程:ThreadId(7)]中将 counter 的值修改为:7
    [子线程:ThreadId(9)]中将 counter 的值修改为:8
    [子线程:ThreadId(10)]中将 counter 的值修改为:9
    [子线程:ThreadId(11)]中将 counter 的值修改为:10
    Result: 10
    

    代码

    本文示例代码维护在gitee上面:https://gitee.com/naylor_personal/rust-hello-world

    打开代码仓库中的 Sport 文件夹,有惊喜!!!

    说明

    • 勘误:本文篇幅较长、涉及内容较多,若阅读过程种发现错误,欢迎批评指正
    • 目的:本文绝非传统意义上面的技术博文,仅作为学习rust的笔记,或者说是学习rust的一个教程。文中涵盖rust大部分基础知识,在实际开发过程中可以作为手册翻阅
    • 心得:本文主要使用下班和周末时间断断续续编写,历时超过一个月。纵观整个过程,开头和结尾是喜悦的,中间的过程是相当的枯燥。开头为即将掌握一门新语言的 hello,world 编写方式而感到兴奋。结束后回顾了整个过程,发现除了学会了rust 的hello,world 之外,其实也同步回顾了一些已经使用过的其他语言的基础知识,尤其是java。中间过程是对未知事物的探索和对自我理解的求证,简单来说就是先得想办法索取rust中有哪些内容,然后形成自己的理解,最后在整理文章和编写示例代码的时候不断推导和验证自己的理解是否正确。

    引用

    邮箱:cnaylor@163.com
    技术交流QQ群:1158377441
    03-29 12:15