Rust 学习

2023-12-16 18:56:06

Rust 官网:https://www.rust-lang.org/zh-CN/

模块?库:https://crates.io/

1、Rust?简介

Rust 语言的主要目标之一是解决传统 系统级编程语言(如 C 和 C++)中常见的安全性问题,例如空指针引用、数据竞争等。为了实现这个目标,Rust 引入了一种称为 "所有权" 的概念,通过静态检查来确保内存安全和线程安全。此外,Rust 还具有其他一些特性,如模式匹配、代数数据类型、函数式编程风格的特性(如闭包和高阶函数)等。它还提供了丰富的标准库和包管理器 Cargo,使得开发者可以轻松构建和管理他们的项目。

Rust 是一门注重安全(safety)、速度(speed)和并发(concurrency)的现代系统编程语言。Rust 通过内存安全来实现以上目标,但不使用垃圾回收机制(garbage collection, GC)。

Rust 是?静态类型statically typed)语言,也就是说在编译时就必须知道所有变量的类型。

Rust?特点

  • 高性能:Rust 速度惊人且内存利用率极高。由于没有运行时和垃圾回收,它能够胜任对性能要求特别高的服务,可以在嵌入式设备上运行,还能轻松和其他语言集成。
  • 可靠性:Rust 丰富的类型系统和所有权模型保证了内存安全和线程安全,让您在编译期就能够消除各种各样的错误。
  • 生产力:Rust 拥有出色的文档、友好的编译器和清晰的错误提示信息, 还集成了一流的工具——包管理器和构建工具, 智能地自动补全和类型检验的多编辑器支持, 以及自动格式化代码等等。

Rust 相关概念

  • channel:Rust?会发布3个不同版本:stable、beta、nightly。
    ? ? ? ? stable:Rust 的稳定版本,每 6 周发布一次。
    ? ? ? ? beta:Rust 的公开测试版本,将是下一个 stable 版本。
    ? ? ? ? nightly:每天更新,包含以一些实验性的新特性。
  • toolchain:一套 Rust 组件,包括编译器及其相关工具,并且包含 channel,版本及支持的平台信息。
  • target:指编译的目标平台,即:编译后的程序在哪种操作系统上运行。
  • component (组件):toolchain 是由 component 组成的。查看所有可用和已经安装的组件命令如下:rustup component list。rustup 默认安装的组件:
    ? ? ? ? rustc:Rust 编译器。
    ? ? ? ? rust-std:Rust 标准库。
    ? ? ? ? cargo:包管理和构建工具。
    ? ? ? ? rust-docs:Rust 文档。
    ? ? ? ? rustfmt:用来格式化 Rust 源代码。
    ? ? ? ? clippy:Rust 的代码检查工具。
  • profile:为了方便对 component 进行管理,使用 profile 定义一组 component。不同的 profile 包含不同的组件,安装 rustup 时有三种 profile 可选,修改 profile 命令如下:rustup set profile minimal
  • Rustup?是什么Rustup?是 Rust安装器和版本管理工具安装 Rust 的主要方式是通过 Rustup 这一工具,它既是一个 Rust 安装器又是一个版本管理工具。Rust 的升级非常频繁。运行?rustup update?获取最新版本的 Rust。文档:https://rust-lang.github.io/rustup/
  • Cargo 是什么Cargo?是?Rust 的 构建工具 和 包管理器。安装?Rustup?时会自动安装。Cargo 可以做很多事情:
    ? ? ? ? cargo build? ? ? 可以构建项目
    ? ? ? ? cargo run? ? ? ? 可以运行项目
    ? ? ? ? cargo test? ? ? ?可以测试项目
    ? ? ? ? cargo doc? ? ? ?可以为项目构建文档
    ? ? ? ? cargo publish 可以将库发布到 crates.io。
    检查是否安装了 Rust 和 Cargo,可以在终端中运行:cargo --version

下载、安装

下载:https://www.rust-lang.org/tools/install
安装:https://www.rust-lang.org/zh-CN/learn/get-started

默认情况,Rust 依赖 C++ build tools,没有安装也关系。安装过程需要保证网络正常。

在 Rust 开发环境中,所有工具都安装在 ~/.cargo/bin 目录中,可以在这里找到包括 rustc、cargo 和 rustup 在内的 Rust 工具链。在安装过程中,rustup 会尝试配置 PATH,如果 rustup 对 PATH 的修改不生效,可以手动添加路径到?PATH

~/.cargo/bin

~/.rustup/bin

以下是一些常用的命令:

rustup 相关

rustup -h ? ? ? ? ? ? ? ?# 查看帮助
rustup show ? ? ? ? ? ? ?# 显示当前安装的工具链信息
rustup update ? ? ? ? ? ?# 检查安装更新
rustup self uninstall ? ?# 卸载
rustup default stable-x86_64-pc-windows-gnu ? ?# 设置当前默认工具链

rustup toolchain list ? ?# 查看工具链
rustup toolchain install stable-x86_64-pc-windows-gnu ? ?# 安装工具链
rustup toolchain uninstall stable-x86_64-pc-windows-gnu ?# 卸载工具链
rustup toolchain link <toolchain-name> "<toolchain-path>" ? ?# 设置自定义工具链

rustup override list ? ?# 查看已设置的默认工具链
rustup override set <toolchain> --path <path> ? ?# 设置该目录以及其子目录的默认工具链
rustup override unset --path <path> ? ?# 取消目录以及其子目录的默认工具链

rustup target list ? ? ? ? ? ? ? # 查看目标列表
rustup target add <target> ? ? ? # 安装目标
rustup target remove <target> ? ?# 卸载目标
rustup target add --toolchain <toolchain> <target> ? ?# 为特定工具链安装目标

rustup component list ? ? ? ? ? ? ? ? # 查看可用组件
rustup component add <component> ? ? ?# 安装组件
rustup component remove <component> ? # 卸载组件

rustc 相关

rustc --version ? ?# 查看rustc版本

cargo 相关

cargo --version ? ?# 查看cargo版本
cargo new <project_name> ? ?# 新建项目
cargo build ? ?# 构建项目
cargo run ? ? ?# 运行项目
cargo check ? ?# 检查项目
cargo -h ? ? ? # 查看帮助

配置工具链安装位置

在系统环境变量中添加如下变量:
CARGO_HOME 指定 cargo 的安装目录
RUSTUP_HOME 指定 rustup 的安装目录
默认分别安装到用户目录下的.cargo 和.rustup 目录

配置国内镜像

配置 rustup 国内镜像。在系统环境变量中添加如下变量(选一个就可以,可以组合):

# 清华大学
RUSTUP_DIST_SERVER:https://mirrors.tuna.tsinghua.edu.cn/rustup
RUSTUP_UPDATE_ROOT:https://mirrors.tuna.tsinghua.edu.cn/rustup/rustup

# 中国科学技术大学
RUSTUP_DIST_SERVER:https://mirrors.ustc.edu.cn/rust-static
RUSTUP_UPDATE_ROOT:https://mirrors.ustc.edu.cn/rust-static/rustup

配置 cargo 国内镜像。在 cargo 安装目录下新建 config 文件(注意 config 没有任何后缀),文件内容如下:

[source.crates-io]
registry = "https://github.com/rust-lang/crates.io-index"
replace-with = 'tuna'

# 清华大学
[source.tuna]
registry = "https://mirrors.tuna.tsinghua.edu.cn/git/crates.io-index.git"

# 中国科学技术大学
[source.ustc]
registry = "git://mirrors.ustc.edu.cn/crates.io-index"
# 设置代理
[http]
proxy = "127.0.0.1:8889"
[https]
proxy = "127.0.0.1:8889"

Windows 交叉编译 Linux 程序。目标服务器是?Linux(CentOS 7) 64bit, 所以我们添加的 target 应该是x86_64-unknown-linux-gnu(动态依赖) 或者x86_64-unknown-linux-musl(静态依赖)

  • 动态依赖:目标服务器需要包含动态依赖的相关库(用户共享库)
  • 静态依赖,目标服务器不需要包含相应的库,但是打包文件会更大些

1). 添加需要的 target
rustup target add ?x86_64-unknown-linux-musl
2). 在 cargo 安装目录下新建 config 文件(注意 config 没有任何后缀),添加的文件内容如下:
[target.x86_64-unknown-linux-musl]
linker = "rust-lld"
3). 构建
cargo build --target x86_64-unknown-linux-musl

示例:

创建新项目

用 Cargo 创建一个新项目。在终端中执行:cargo new hello-rust,会生成一个名为?hello-rust?的新目录,其中包含以下文件:

  • Cargo.toml?为 Rust 的清单文件。其中包含了项目的元数据和依赖库。
  • src/main.rs?为编写应用代码的地方。

进入新创建的目录中,执行命令运行此程序:cargo run

添加 依赖

现在来为程序添加依赖。可以在?crates.io,即 Rust 包的仓库中找到所有类别的库。在 Rust 中通常把 "包" 称作 "crates"。在本项目中,使用了名为?ferris-says?的库。

在?Cargo.toml?文件中添加以下信息(从 crate 页面上获取):

[dependencies]
ferris-says = "0.3.1"

下载 依赖

运行:cargo build? ,?Cargo 就会安装该依赖。运行?build?会创建一个新文件?Cargo.lock,该文件记录了本地所用依赖库的精确版本。

使用?依赖

使用该依赖库:可以打开?main.rs,删除其中所有的内容(它不过是个示例而已),然后在其中添加下面这行代码:use ferris_says::say;

这样就可以使用?ferris-says?crate 中导出的?say?函数了。

完整?Rust?示例

现在用上面的依赖库编写一个小应用。在?main.rs?中添加以下代码:

use ferris_says::say; // from the previous step
use std::io::{stdout, BufWriter};

fn main() {
    let stdout = stdout();
    let message = String::from("Hello fellow Rustaceans!");
    let width = message.chars().count();

    let mut writer = BufWriter::new(stdout.lock());
    say(&message, width, &mut writer).unwrap();
}

保存完毕后,执行命令运行程序:cargo run

成功执行后,会打印一个字符形式的螃蟹图案。

Ferris (?费理斯 )?是 Rust 社区的非官方吉祥物。

2、Rust?相关文档

https://www.rust-lang.org/zh-CN/learn

核心文档

以下所有文档都可以用?rustup doc?命令在本地阅读,它会在浏览器中离线打开这些资源!

标准库

详尽的 Rust 标准库 API 手册。:https://doc.rust-lang.org/std/index.html

版本指南

Rust 版本指南。:https://doc.rust-lang.org/edition-guide/index.html

CARGO 手册

Rust 的包管理器和构建系统。:https://doc.rust-lang.org/cargo/index.html

RUSTDOC 手册

学习如何为?crate 编写完美的文档。:https://doc.rust-lang.org/rustdoc/index.html

RUSTC 手册

熟悉 Rust 编译器中可用的选项。:https://doc.rust-lang.org/rustc/index.html

编译错误索引表

深入解释遇到的编译错误。:https://doc.rust-lang.org/error_codes/error-index.html

Rust?程序

命令行?程序

用 Rust 构建高效的命令行应用。:https://rust-cli.github.io/book/index.html

WEBASSEMBLY 手册

通过 WebAssembly 用 Rust 构建浏览器原生的库。:https://rustwasm.github.io/docs/book/

嵌入式手册

Rust 编写嵌入式程序。:https://doc.rust-lang.org/stable/embedded-book/

Learn X?in Y

// 这是注释,单行注释...
/* ...这是多行注释 */

///
// 1. 基础   //
///

// 函数 (Functions)
// `i32` 是有符号 32 位整数类型(32-bit signed integers)
fn add2(x: i32, y: i32) -> i32 {
    // 隐式返回 (不要分号)
    x + y
}

// 主函数(Main function)
fn main() {
    // 数字 (Numbers) //

    // 不可变绑定
    let x: i32 = 1;

    // 整形/浮点型数 后缀
    let y: i32 = 13i32;
    let f: f64 = 1.3f64;

    // 类型推导
    // 大部分时间,Rust 编译器会推导变量类型,所以不必把类型显式写出来。
    // 这个教程里面很多地方都显式写了类型,但是只是为了示范。
    // 绝大部分时间可以交给类型推导。
    let implicit_x = 1;
    let implicit_f = 1.3;

    // 算术运算
    let sum = x + y + 13;

    // 可变变量
    let mut mutable = 1;
    mutable = 4;
    mutable += 2;

    // 字符串 (Strings) //

    // 字符串字面量
    let x: &str = "hello world!";

    // 输出
    println!("{} {}", f, x); // 1.3 hello world

    // 一个 `String` – 在堆上分配空间的字符串
    let s: String = "hello world".to_string();

    // 字符串分片(slice) - 另一个字符串的不可变视图
    // 基本上就是指向一个字符串的不可变指针,它不包含字符串里任何内容,只是一个指向某个东西的指针
    // 比如这里就是 `s`
    let s_slice: &str = &s;

    println!("{} {}", s, s_slice); // hello world hello world

    // 数组 (Vectors/arrays) //

    // 长度固定的数组 (array)
    let four_ints: [i32; 4] = [1, 2, 3, 4];

    // 变长数组 (vector)
    let mut vector: Vec<i32> = vec![1, 2, 3, 4];
    vector.push(5);

    // 分片 - 某个数组(vector/array)的不可变视图
    // 和字符串分片基本一样,只不过是针对数组的
    let slice: &[i32] = &vector;

    // 使用 `{:?}` 按调试样式输出
    println!("{:?} {:?}", vector, slice); // [1, 2, 3, 4, 5] [1, 2, 3, 4, 5]

    // 元组 (Tuples) //

    // 元组是固定大小的一组值,可以是不同类型
    let x: (i32, &str, f64) = (1, "hello", 3.4);

    // 解构 `let`
    let (a, b, c) = x;
    println!("{} {} {}", a, b, c); // 1 hello 3.4

    // 索引
    println!("{}", x.1); // hello

    //
    // 2. 类型 (Type)  //
    //

    // 结构体(Sturct)
    struct Point {
        x: i32,
        y: i32,
    }

    let origin: Point = Point { x: 0, y: 0 };

    // 匿名成员结构体,又叫“元组结构体”(‘tuple struct’)
    struct Point2(i32, i32);

    let origin2 = Point2(0, 0);

    // 基础的 C 风格枚举类型(enum)
    enum Direction {
        Left,
        Right,
        Up,
        Down,
    }

    let up = Direction::Up;

    // 有成员的枚举类型
    enum OptionalI32 {
        AnI32(i32),
        Nothing,
    }

    let two: OptionalI32 = OptionalI32::AnI32(2);
    let nothing = OptionalI32::Nothing;

    // 泛型 (Generics) //

    struct Foo<T> { bar: T }

    // 这个在标准库里面有实现,叫 `Option`
    enum Optional<T> {
        SomeVal(T),
        NoVal,
    }

    // 方法 (Methods) //

    impl<T> Foo<T> {
        // 方法需要一个显式的 `self` 参数
        fn get_bar(self) -> T {
            self.bar
        }
    }

    let a_foo = Foo { bar: 1 };
    println!("{}", a_foo.get_bar()); // 1

    // 接口(Traits) (其他语言里叫 interfaces 或 typeclasses) //

    trait Frobnicate<T> {
        fn frobnicate(self) -> Option<T>;
    }

    impl<T> Frobnicate<T> for Foo<T> {
        fn frobnicate(self) -> Option<T> {
            Some(self.bar)
        }
    }

    let another_foo = Foo { bar: 1 };
    println!("{:?}", another_foo.frobnicate()); // Some(1)

    ///
    // 3. 模式匹配 (Pattern matching) //
    ///

    let foo = OptionalI32::AnI32(1);
    match foo {
        OptionalI32::AnI32(n) => println!("it’s an i32: {}", n),
        OptionalI32::Nothing  => println!("it’s nothing!"),
    }

    // 高级模式匹配
    struct FooBar { x: i32, y: OptionalI32 }
    let bar = FooBar { x: 15, y: OptionalI32::AnI32(32) };

    match bar {
        FooBar { x: 0, y: OptionalI32::AnI32(0) } =>
            println!("The numbers are zero!"),
        FooBar { x: n, y: OptionalI32::AnI32(m) } if n == m =>
            println!("The numbers are the same"),
        FooBar { x: n, y: OptionalI32::AnI32(m) } =>
            println!("Different numbers: {} {}", n, m),
        FooBar { x: _, y: OptionalI32::Nothing } =>
            println!("The second number is Nothing!"),
    }

    ///
    // 4. 流程控制 (Control flow) //
    ///

    // `for` 循环
    let array = [1, 2, 3];
    for i in array {
        println!("{}", i);
    }

    // 区间 (Ranges)
    for i in 0u32..10 {
        print!("{} ", i);
    }
    println!("");
    // 输出 `0 1 2 3 4 5 6 7 8 9 `

    // `if`
    if 1 == 1 {
        println!("Maths is working!");
    } else {
        println!("Oh no...");
    }

    // `if` 可以当表达式
    let value = if true {
        "good"
    } else {
        "bad"
    };

    // `while` 循环
    while 1 == 1 {
        println!("The universe is operating normally.");
    }

    // 无限循环
    loop {
        println!("Hello!");
    }

    
    // 5. 内存安全和指针 (Memory safety & pointers) //
    

    // 独占指针 (Owned pointer) - 同一时刻只能有一个对象能“拥有”这个指针
    // 意味着 `Box` 离开他的作用域后,会被安全地释放
    let mut mine: Box<i32> = Box::new(3);
    *mine = 5; // 解引用
    // `now_its_mine` 获取了 `mine` 的所有权。换句话说,`mine` 移动 (move) 了
    let mut now_its_mine = mine;
    *now_its_mine += 2;

    println!("{}", now_its_mine); // 7
    // println!("{}", mine); // 编译报错,因为现在 `now_its_mine` 独占那个指针

    // 引用 (Reference) – 引用其他数据的不可变指针
    // 当引用指向某个值,我们称为“借用”这个值,因为是被不可变的借用,所以不能被修改,也不能移动
    // 借用一直持续到生命周期结束,即离开作用域
    let mut var = 4;
    var = 3;
    let ref_var: &i32 = &var;

    println!("{}", var); //不像 `mine`, `var` 还可以继续使用
    println!("{}", *ref_var);
    // var = 5; // 编译报错,因为 `var` 被借用了
    // *ref_var = 6; // 编译报错,因为 `ref_var` 是不可变引用

    // 可变引用 (Mutable reference)
    // 当一个变量被可变地借用时,也不可使用
    let mut var2 = 4;
    let ref_var2: &mut i32 = &mut var2;
    *ref_var2 += 2;

    println!("{}", *ref_var2); // 6
    // var2 = 2; // 编译报错,因为 `var2` 被借用了
}

rust?打印占位符

在 Rust 中,打印的占位符由格式化宏提供,最常用的是 println!format!。下面是一些常见的占位符及其用法:

  • {}:默认占位符,根据值的类型自动选择合适的显示方式。

  • {:?}:调试占位符,用于打印调试信息。通常用于 Debug trait 的实现。

  • {:#?}:类似于 {:?},但打印出更具可读性的格式化调试信息,可以嵌套显示结构体和枚举的字段。

  • {x}:将变量 x 的值插入到占位符的位置。

  • {x:format}:将变量 x 按照指定的格式进行格式化输出。例如,{x:?}", {x:b}, {x:e}` 等。

这只是一小部分常见的占位符用法,你还可以根据需要使用其他格式化选项。Rust 的格式化宏提供了非常灵活和强大的格式化功能,可以满足大多数打印需求。

fn main() {
    let name = "Alice";
    let age = 25;
    let height = 1.65;

    println!("Name: {}", name);
    println!("Age: {}", age);
    println!("Height: {:.2}", height);  // 格式化为小数点后两位

    let point = (3, 5);
    println!("Point: {:?}", point);
}

打印 "枚举、结构体"

#[derive(Debug)]
enum MyEnum {
    Variant1,
    Variant2(u32),
    Variant3 { name: String, age: u32 },
}

#[derive(Debug)]
struct MyStruct{
    field_1: String,
    field_2: usize,
}

impl MyStruct {
    fn init_field(&self){
        let name = &self.field_1;
        let age = self.field_2;
        println!("{name} ---> {age}")
    }
}

fn main() {
    let my_enum = MyEnum::Variant2(42);
    println!("{:?}", my_enum);
    let my_struct = MyStruct{
        field_1: String::from("king"),
        field_2: 100
    };
    my_struct.init_field();
    println!("{:?}", my_struct);
}

3、Rust 程序设计语言

英文文档:https://doc.rust-lang.org/book/

中文文档:https://kaisery.github.io/trpl-zh-cn/

《Rust 程序设计语言》被亲切地称为“圣经”。给出了 Rust 语言的概览。在阅读的过程中构建几个项目,读完后,就能扎实地掌握 Rust 语言。

  1. 1.?入门指南
    1. 1.1.?安装
    2. 1.2.?Hello, World!
    3. 1.3.?Hello, Cargo!
  2. 2.?写个猜数字游戏
  3. 3.?常见编程概念
    1. 3.1.?变量与可变性
    2. 3.2.?数据类型
    3. 3.3.?函数
    4. 3.4.?注释
    5. 3.5.?控制流
  4. 4.?认识所有权
    1. 4.1.?什么是所有权?
    2. 4.2.?引用与借用
    3. 4.3.?Slice 类型
  5. 5.?使用结构体组织相关联的数据
    1. 5.1.?结构体的定义和实例化
    2. 5.2.?结构体示例程序
    3. 5.3.?方法语法
  6. 6.?枚举和模式匹配
    1. 6.1.?枚举的定义
    2. 6.2.?match 控制流结构
    3. 6.3.?if let 简洁控制流
  7. 7.?使用包、Crate 和模块管理不断增长的项目
    1. 7.1.?包和 Crate
    2. 7.2.?定义模块来控制作用域与私有性
    3. 7.3.?引用模块项目的路径
    4. 7.4.?使用 use 关键字将路径引入作用域
    5. 7.5.?将模块拆分成多个文件
  8. 8.?常见集合
    1. 8.1.?使用 Vector 储存列表
    2. 8.2.?使用字符串储存 UTF-8 编码的文本
    3. 8.3.?使用 Hash Map 储存键值对
  9. 9.?错误处理
    1. 9.1.?用 panic! 处理不可恢复的错误
    2. 9.2.?用 Result 处理可恢复的错误
    3. 9.3.?要不要 panic!
  10. 10.?泛型、Trait 和生命周期
    1. 10.1.?泛型数据类型
    2. 10.2.?Trait:定义共同行为
    3. 10.3.?生命周期确保引用有效
  11. 11.?编写自动化测试
    1. 11.1.?如何编写测试
    2. 11.2.?控制测试如何运行
    3. 11.3.?测试的组织结构
  12. 12.?一个 I/O 项目:构建命令行程序
    1. 12.1.?接受命令行参数
    2. 12.2.?读取文件
    3. 12.3.?重构以改进模块化与错误处理
    4. 12.4.?采用测试驱动开发完善库的功能
    5. 12.5.?处理环境变量
    6. 12.6.?将错误信息输出到标准错误而不是标准输出
  13. 13.?Rust 中的函数式语言功能:迭代器与闭包
    1. 13.1.?闭包:可以捕获其环境的匿名函数
    2. 13.2.?使用迭代器处理元素序列
    3. 13.3.?改进之前的 I/O 项目
    4. 13.4.?性能比较:循环对迭代器
  14. 14.?更多关于 Cargo 和 Crates.io 的内容
    1. 14.1.?采用发布配置自定义构建
    2. 14.2.?将 crate 发布到 Crates.io
    3. 14.3.?Cargo 工作空间
    4. 14.4.?使用 cargo install 安装二进制文件
    5. 14.5.?Cargo 自定义扩展命令
  15. 15.?智能指针
    1. 15.1.?使用Box<T> 指向堆上数据
    2. 15.2.?使用Deref Trait 将智能指针当作常规引用处理
    3. 15.3.?使用Drop Trait 运行清理代码
    4. 15.4.?Rc<T> 引用计数智能指针
    5. 15.5.?RefCell<T> 与内部可变性模式
    6. 15.6.?引用循环会导致内存泄漏
  16. 16.?无畏并发
    1. 16.1.?使用线程同时地运行代码
    2. 16.2.?使用消息传递在线程间通信
    3. 16.3.?共享状态并发
    4. 16.4.?使用Sync 与 Send Traits 的可扩展并发
  17. 17.?Rust 的面向对象编程特性
    1. 17.1.?面向对象语言的特点
    2. 17.2.?为使用不同类型的值而设计的 trait 对象
    3. 17.3.?面向对象设计模式的实现
  18. 18.?模式与模式匹配
    1. 18.1.?所有可能会用到模式的位置
    2. 18.2.?Refutability(可反驳性): 模式是否会匹配失效
    3. 18.3.?模式语法
  19. 19.?高级特征
    1. 19.1.?不安全的 Rust
    2. 19.2.?高级 trait
    3. 19.3.?高级类型
    4. 19.4.?高级函数与闭包
    5. 19.5.?宏
  20. 20.?最后的项目:构建多线程 web server
    1. 20.1.?建立单线程 web server
    2. 20.2.?将单线程 server 变为多线程 server
    3. 20.3.?优雅停机与清理
  21. 21.?附录
    1. 21.1.?A - 关键字
    2. 21.2.?B - 运算符与符号
    3. 21.3.?C - 可派生的 trait
    4. 21.4.?D - 实用开发工具
    5. 21.5.?E - 版本
    6. 21.6.?F - 本书译本
    7. 21.7.?G - Rust 是如何开发的与 “Nightly Rust”

Rust?数据类型

Rust 是?静态类型statically typed)语言,也就是说在编译时就必须知道所有变量的类型。

标量、复合

Rust 2大类数据类型:

  • 标量(scalar):代表一个单独的值。4种基本的标量:整型、浮点型、布尔类型、字符字符串(String)类型由 Rust 标准库提供,而不是编入核心语言。在 Rust 中,"字符串字面值" 使用双引号括起来,例如:"Hello, World!"。这是一种字符串类型的常量表示方法。而普通的字符串类型则是指动态可变的字符串,即 String 类型。字符串字面值是静态不可变的,不能修改其中的内容。你可以直接使用字符串字面值进行一些简单的操作,如拼接、切割等,但无法修改它们的值。字符串字面值就是 String 的 slice
  • 复合(compound):Rust 有两个原生的复合类型:元组(tuple)、数组(array)
    元组 是一个将多个其他类型的值组合进一个复合类型的主要方式。元组长度固定:一旦声明,其长度不会增大或缩小。
    另一个包含多个值的方式是 数组(array)。与元组不同,数组中的每个元素的类型必须相同。Rust 中的数组与一些其他语言中的数组不同,Rust 中的数组长度是固定的。

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

三个在 Rust 程序中被广泛使用的集合:

  • vector??一个挨着一个地储存一系列数量可变的值。为了创建一个新的空 vector,可以调用 Vec::new 函数,会用初始值来创建一个 Vec<T> 而 Rust 会推断出储存值的类型,所以很少会需要这些类型注解。为了方便 Rust 提供了 vec! 宏,这个宏会根据我们提供的值来创建一个新的 vector。
  • 字符串string)是字符的集合。我们之前见过?String?类型,不过在本章我们将深入了解。
  • 哈希 maphash map)允许我们将值与一个特定的键(key)相关联。这是一个叫做?map?的更通用的数据结构的特定实现。

对于标准库提供的其他类型的集合,请查看文档

vec?示例:

fn main() {

    let mut v = Vec::new();
    v.push(5);
    v.push(6);
    v.push(7);
    v.push(8);
    println!("{:?}", v);

    let v = vec![1, 2, 3, 4, 5];
    let third: &i32 = &v[2];
    println!("The third element is {third}");

    let third: Option<&i32> = v.get(2);
    match third {
        Some(third) => println!("The third element is {third}"),
        None => println!("There is no third element."),
    }
}
fn main() {
    let mut v = vec![100, 32, 57];
    for i in &mut v {
        // 为了修改可变引用所指向的值,在使用 += 运算符之前
        // 必须使用解引用运算符(*)获取 i 中的值。
        *i += 100;
    }
    for i in &v{
        println!("{i}")
    }
}

示例:

fn main() {

    let mut v = Vec::new();
    v.push(5);
    v.push(6);
    v.push(7);
    v.push(8);
    println!("{:?}", v);

    let v = vec![1, 2, 3, 4, 5];
    let third: &i32 = &v[2];
    println!("The third element is {third}");

    let third: Option<&i32> = v.get(2);
    match third {
        Some(third) => println!("The third element is {third}"),
        None => println!("There is no third element."),
    }

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

    #[derive(Debug)]
    enum SpreadsheetCell {
        Int(i32),
        Float(f64),
        Text(String),
    }

    // 使用枚举来存储多个类型,类比 Python 的 list
    let row = vec![
        SpreadsheetCell::Int(3),
        SpreadsheetCell::Text(String::from("blue")),
        SpreadsheetCell::Float(10.12),
    ];

    for cell in &row {
        match cell {
            SpreadsheetCell::Int(value) => println!("整数值: {}", value),
            SpreadsheetCell::Text(value) => println!("文本值: {}", value),
            SpreadsheetCell::Float(value) => println!("浮点数值: {}", value),
        }
    }
}

Rust 在编译时就必须准确的知道 vector 中类型的原因在于它需要知道储存每个元素到底需要多少内存。第二个好处是可以准确的知道这个 vector 中允许什么类型。如果 Rust 允许 vector 存放任意类型,那么当对 vector 元素执行操作时一个或多个类型的值就有可能会造成错误。使用枚举外加?match?意味着 Rust 能在编译时就保证总是会处理所有可能的情况,

如果在编写程序时不能确切无遗地知道运行时会储存进 vector 的所有类型,枚举技术就行不通了。相反,你可以使用 trait 对象,

字符串

Rust 的核心语言中只有一种字符串类型:字符串 slice?str,它通常以被借用的形式出现,&str。第四章讲到了?字符串 slices:它们是一些对储存在别处的 UTF-8 编码字符串数据的引用。举例来说,由于字符串字面值被储存在程序的二进制输出中,因此字符串字面值也是字符串 slices。

fn main() {
    let s1 = String::from("Hello, ");
    let s2 = String::from("world!");
    let s3 = s1 + &s2; // 注意 s1 被移动了,不能继续使用

    println!("s2 ---> {s2}");
    println!("s3 ---> {s3}");

    let s1 = String::from("tic");
    let s2 = String::from("tac");
    let s3 = String::from("toe");
    // 宏 format! 生成的代码使用引用所以不会获取任何参数的所有权。
    let s = format!("{s1}-{s2}-{s3}"); 

索引字符串
    println!("s ---> {s}");
    println!("s1 ---> {s1}");
    println!("s2 ---> {s2}");
    println!("s3 ---> {s3}");
}

哈希 map:HashMap<K, V>

HashMap<K, V>?类型储存了一个键类型?K?对应一个值类型?V?的映射。它通过一个?哈希函数hashing function)来实现映射,决定如何将键和值放入内存中。很多编程语言支持这种数据结构,不过通常有不同的名字:哈希、map、对象、哈希表、关联数组、Python的字典(Dict)?等。

可以使用?new?创建一个空的?HashMap,并使用?insert?增加元素。

fn main() {
    use std::collections::HashMap;

    let mut scores = HashMap::new();

    scores.insert(String::from("Blue"), 10);
    scores.insert(String::from("Yellow"), 50);
}

必须首先?use?标准库中集合部分的?HashMap。在这三个常用集合中,HashMap?是最不常用的,所以并没有被 prelude 自动引用。标准库中对?HashMap?的支持也相对较少,例如,并没有内建的构建宏。类似于 vector,哈希 map 是同质的:所有的键必须是相同类型,值也必须都是相同类型。

Rust?的?所有权

https://kaisery.github.io/trpl-zh-cn/ch04-01-what-is-ownership.html

所有权(系统)是 Rust 最为与众不同的特性,对语言的其他部分有着深刻含义。它让 Rust 无需垃圾回收(garbage collector)即可保障内存安全,因此理解 Rust 中所有权如何工作是十分重要的。

所有权的规则

  1. Rust 中的每一个值都有一个?所有者owner)。
  2. 值在任一时刻有且只有一个所有者。
  3. 当所有者(变量)离开作用域,这个值将被 丢弃(释放内存空间)。

变量、作用域

变量是否有效与作用域的关系跟其他编程语言是类似的。

fn main() {
? ? { ? ? ? ? ? ? ? ? ? ? ?// s 在这里无效,它尚未声明
? ? ? ? let s = "hello"; ? // 从此处起,s 是有效的
? ? ? ? // 使用 s
? ? } ? ? ? ? ? ? ? ? ? ? ?// 此作用域已结束,s 不再有效,清理并drop(释放掉)内存空间
}

这里有两个重要的时间点:

  • 当?s?进入作用域?时,它就是有效的。
  • 这一直持续到它?离开作用域?为止。

变量与数据交互:移动 (浅拷贝)、克隆 (深拷贝)

  • 浅拷贝shallow copy)和?深拷贝deep copy
    浅拷贝:拷贝指针、长度和容量,而不拷贝指针所指向内存空间的数据。
    深拷贝:拷贝指针、长度和容量,同时也拷贝指针所指向内存空间的数据。

fn main() {
? ? let s1 = String::from("hello");
? ? let s2 = s1;? ? ? ? ? // 在 C++ 中,这里会发生浅拷贝,不过在?Rust?中会使第一个变量无效,这个操作被称为?移动move),而不是叫做浅拷贝。为了确保内存安全,在?let s2 = s1;?之后,Rust 认为?s1?不再有效,因此 Rust 不需要在?s1?离开作用域后清理任何东西。

? ??println!("{}, world!", s1);? ? //?看看在?s2?被创建之后尝试使用?s1?会发生什么,这里会报错,? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?// 因为只有?s2?是有效的,当其离开作用域,它就释放自己的内存
}

这里隐含了 Rust?的一个设计选择:Rust 永远也不会自动创建数据的 “深拷贝”。

变量的 "移动(转移)"

只要进行 "赋值(=)、函数传参"?都会有?移动

  • 移动 ( 转移 ):变量的所有权总是遵循相同的模式:将值赋给另一个变量时移动它。当持有堆中数据值的变量离开作用域时,其值将通过 drop 被清理掉,除非数据被移动为另一个变量所有。

变量的?克隆

确实?需要深度复制?String?中堆上的数据,而不仅仅是栈上的数据,可以使用一个叫做?clone?的通用函数。

fn main() {
? ? let s1 = String::from("hello");
? ? let s2 = s1.clone();

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

只在栈上的数据:拷贝

fn main() {
? ? let x = 5;
? ? let y = x;

? ? println!("x = {}, y = {}", x, y);
}

一个通用的规则,任何一组简单标量值的组合都可以实现 Copy,任何不需要分配内存或某种形式资源的类型都可以实现 Copy 。如下是一些 Copy 的类型:

  • 所有整数类型,比如?u32
  • 布尔类型,bool,它的值是?true?和?false
  • 所有浮点数类型,比如?f64
  • 字符类型,char
  • 元组,当且仅当其包含的类型也都实现?Copy?的时候。比如,(i32, i32)?实现了?Copy,但?(i32, String)?就没有。

所有权与函数

将值传递给函数与给变量赋值的原理相似。向函数传递值可能会移动或者复制,就像赋值语句一样。

fn main() {
? ? let s = String::from("hello"); ?// s 进入作用域

? ? takes_ownership(s); ? ? ? ? ? ? // s 的值移动到函数里 ...
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? // ... 所以到这里不再有效

? ? let x = 5; ? ? ? ? ? ? ? ? ? ? ?// x 进入作用域

? ? makes_copy(x); ? ? ? ? ? ? ? ? ?// x 应该移动函数里,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? // 但 i32 是 Copy 的,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? // 所以在后面可继续使用 x

} // 这里,x 先移出了作用域,然后是 s。但因为 s 的值已被移走,
? // 没有特殊之处

fn takes_ownership(some_string: String) { // some_string 进入作用域
? ? println!("{}", some_string);
} // 这里,some_string 移出作用域并调用 `drop` 方法。
? // 占用的内存被释放

fn makes_copy(some_integer: i32) { // some_integer 进入作用域
? ? println!("{}", some_integer);
} // 这里,some_integer 移出作用域。没有特殊之处
?

返回值与作用域

返回值也可以转移所有权。

fn main() {
? ? let s1 = gives_ownership(); ? ? ? ? // gives_ownership 将返回值
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? // 转移给 s1

? ? let s2 = String::from("hello"); ? ? // s2 进入作用域

? ? let s3 = takes_and_gives_back(s2); ?// s2 被移动到
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? // takes_and_gives_back 中,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? // 它也将返回值移给 s3
} // 这里,s3 移出作用域并被丢弃。s2 也移出作用域,但已被移走,
? // 所以什么也不会发生。s1 离开作用域并被丢弃

fn gives_ownership() -> String { ? ? ? ? ? ? // gives_ownership 会将
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?// 返回值移动给
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?// 调用它的函数

? ? let some_string = String::from("yours"); // some_string 进入作用域。

? ? some_string ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?// 返回 some_string?
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?// 并移出给调用的函数
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?//?
}

// takes_and_gives_back 将传入字符串并返回该值
fn takes_and_gives_back(a_string: String) -> String { // a_string 进入作用域
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //?

? ? a_string ?// 返回 a_string 并移出给调用的函数
}

引用 (?借用?)

  • 在任意给定时间,要么?只能有一个可变引用,要么?只能有多个不可变引用。同时出现时 "可变引用、不可变引用 作用域 不能重叠"
  • 引用不传递所有权。
  • 引用必须总是有效的。
  • 引用 (?借用?):& 符号就是?引用,它们允许你使用值,但不获取其所有权,因为没有所有权,所以在离开作用域时,不会进行清理释放内存。引用(reference)像一个指针,因为它是一个地址,我们可以由此访问储存于该地址的属于其他变量的数据。 与指针不同,引用确保指向某个特定类型的有效值

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 是 String 的引用
? ? s.len()
} // 这里,s 离开了作用域。但因为它并不拥有引用值的所有权,
? // 所以什么也不会发生

变量?s?有效的作用域与函数参数的作用域一样,不过当?s?停止使用时并不丢弃引用指向的数据,因为?s?并没有所有权。当函数使用引用而不是实际值作为参数,无需返回值来交还所有权,因为就不曾拥有所有权。

  • 总结:将创建一个引用的行为称为?借用borrowing)。正如现实生活中,如果一个人拥有某样东西,你可以从他那里借来。当你使用完毕,必须还回去。因为并不拥有它。

正如变量默认是不可变的,引用也一样。(默认)不允许修改引用的值。

如果想要修改引用的值,就需要用到?可变引用mutable reference)。可变引用有一个很大的限制:如果创建了一个变量的可变引用,就不能再创建对该变量的引用。不可变引用的值本身就不希望被改变,一个变量可以有多个不可变引用。

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

? ? let r1 = &mut s;
? ? let r2 = &mut s;

? ? println!("{}, {}", r1, r2);
}
这个报错说这段代码是无效的,因为我们不能在同一时间多次将?s?作为可变变量借用。第一个可变的借入在?r1?中,并且必须持续到在?println!?中使用它,但是在那个可变引用的创建和它的使用之间,我们又尝试在?r2?中创建另一个可变引用,该引用借用与?r1?相同的数据。

这一限制以一种非常小心谨慎的方式允许可变性,防止同一时间对同一数据存在多个可变引用。新 Rustacean 们经常难以适应这一点,因为大部分语言中变量任何时候都是可变的。这个限制的好处是 Rust 可以在编译时就避免数据竞争。数据竞争data race)类似于竞态条件,它可由这三个行为造成:

  • 两个或更多指针同时访问同一数据。
  • 至少有一个指针被用来写入数据。
  • 没有同步数据访问的机制。

数据竞争会导致未定义行为,难以在运行时追踪,并且难以诊断和修复;Rust 避免了这种情况的发生,因为它甚至不会编译存在数据竞争的代码!

可以使用大括号来创建一个新的作用域,以允许拥有多个可变引用,只是不能?同时?拥有:

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

? ? {
? ? ? ? let r1 = &mut s;
? ? } // r1 在这里离开了作用域,所以我们完全可以创建一个新的引用

? ? let r2 = &mut s;
}
Rust 在同时使用可变与不可变引用时也采用的类似的规则。这些代码会导致一个错误:

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

? ? let r1 = &s; // 没问题
? ? let r2 = &s; // 没问题
? ? let r3 = &mut s; // 大问题

? ? println!("{}, {}, and {}", r1, r2, r3);? ?
}? ?// r1、r2、r3?作用域都是到这里结束,但是上面打印时,r1、r2?生效时 r3?也生效,所以报错。因为?rust?会自动判断?变量引用的作用域是否重叠,所以可以调整?println?的顺序即可。

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

? ? let r1 = &s; // 没问题
? ? let r2 = &s; // 没问题

? ? println!("{}, {}", r1, r2);
? ? let r3 = &mut s; // 大问题
? ? println!("{}", r3);
}

不可变引用?r1?和?r2?的作用域在?println!?最后一次使用之后结束,这也是创建可变引用?r3?的地方。它们的作用域没有重叠,所以代码是可以编译的。编译器可以在作用域结束之前判断不再使用的引用。记住这是 Rust 编译器在提前指出一个潜在的 bug?的规定。

悬垂引用(Dangling References)

在具有指针的语言中,很容易通过释放内存时保留指向它的指针而错误地生成一个?悬垂指针dangling pointer),所谓悬垂指针是其指向的内存可能已经被分配给其它持有者。相比之下,在 Rust 中编译器确保引用永远也不会变成悬垂状态:当你拥有一些数据的引用,编译器确保数据不会在其引用之前离开作用域。

fn main() {
? ? let reference_to_nothing = dangle();
}

fn dangle() -> &String { // dangle 返回一个字符串的引用

? ? let s = String::from("hello"); // s 是一个新字符串

? ? &s // 返回字符串 s 的引用,但是引用不转移所有权,所以函数结束时,s?被销毁释放内存
} // 这里 s 离开作用域并被丢弃。其内存被释放。
? // 危险!
正确的做法:不返回引用。

fn main() {
? ? let string = no_dangle();
}

fn no_dangle() -> String {
? ? let s = String::from("hello");
? ? s? ?//?这样就没有任何错误了。所有权被移动出去,所以没有值被释放。
}

Slice 类型:slice?允许你引用集合中一段连续的元素序列,而不用引用整个集合。slice 是一类引用,所以它同样没有所有权。

枚举、结构体

  • 枚举(enumerations),也被称作 enums。枚举允许你通过列举可能的 成员(variants)来定义一个类型。
  • struct,或者 structure,是一个自定义数据类型,允许你包装和命名多个相关的值,从而形成一个有意义的组合。结构体可以定义方法。结构体作用就是将字段和数据聚合在一块,形成新的数据类型
fn main() {
    enum IpAddr {
        V4(String),
        V6(String),
    }

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

    match home {
        IpAddr::V4(ip) => println!("Home IPv4 地址是: {}", ip),
        IpAddr::V6(ip) => println!("Home IPv6 地址是: {}", ip),
    }

    match loopback {
        IpAddr::V4(ip) => println!("Loopback IPv4 地址是: {}", ip),
        IpAddr::V6(ip) => println!("Loopback IPv6 地址是: {}", ip),
    }
}

Rust?的?Result

在 Rust 中,Result 是一个枚举类型,它代表了可能产生错误的操作的结果。Result 枚举有两个变体:OkErr

  • Ok 变体表示操作成功,并包含操作返回的值。
  • Err 变体表示操作失败,并包含一个错误值,用于描述错误的原因。

通常,Result 类型被用于表示可能会发生错误的函数的返回类型。这样,调用者可以通过检查 Result 来处理操作的成功或失败。简单的示例,演示如何使用 Result

fn divide(a: i32, b: i32) -> Result<i32, String> {
    if b == 0 {
        return Err(String::from("除数不能为零"));
    }

    Ok(a / b)
}

fn main() {
    let result = divide(10, 2);

    match result {
        Ok(value) => println!("结果是: {}", value),
        Err(error) => println!("出现错误: {}", error),
    }
}

示例 2:

use std::io;
use std::io::stdin;

fn main() {
    let mut input_str = String::from("");
    stdin().read_line(&mut input_str).expect("获取输入失败");
    let input_int:usize = match input_str.trim().parse() {
        Ok(n) => n,
        Err(_) => {
            println!("无效的输入");
            return;
        }
    };
    let result = input_int * 100;
    println!("{result}")
}

match 控制流结构、匹配 Option<T>

  • Rust?的?match?是极为强大的控制流运算符,它允许将一个值与一系列的模式相比较,并根据相匹配的模式执行相应代码。模式可由字面值、变量、通配符和许多其他内容构成。
  • ?Option<T>?时,是为了从?Some?中取出其内部的?T?值;
    ? ? ? ? 如果其中含有一个值,则执行有值得流程。
    ? ? ? ? 如果其中没有值,则执行没有值得流程。
fn main() {
    fn plus_one(x: Option<i32>) -> Option<i32> {
        match x {
            None => None,
            Some(i) => Some(i + 1),
        }
    }

    let five = Some(5);
    let six = plus_one(five);
    let none = plus_one(None);

    // 使用模式匹配
    match six {
        Some(value) => println!("Some 值是: {}", value),
        None => println!("None"),
    }

    // 使用 unwrap() 方法
    if let Some(value) = five {
        println!("Some 值是: {}", value);
    } else {
        println!("None");
    }

    if let Some(value) = six {
        println!("Some 值是: {}", value);
    } else {
        println!("None");
    }
}

使用包、Crate、模块管理

Rust 有许多功能可以让你管理代码的组织,包括哪些内容可以被公开,哪些内容作为私有部分,以及程序每个作用域中的名字。这些功能,有时被统称为 “模块系统(the module system)”,包括:

  • crate :是 Rust 在编译时最小的代码单位。如果用?rustc?而不是?cargo?来编译一个文件时,编译器会将那个文件认作一个 crate。crate 可以包含模块,模块可以定义在其他文件,然后和 crate 一起编译。crate 有两种形式:二进制的可执行程序、lib库。
  • Crates?:一个模块的树形结构,它形成了库或二进制项目。
  • Packages):Cargo 的一个功能,它允许你构建、测试和分享 crate。

    package)是提供一系列功能的一个或者多个 crate。一个包会包含一个?Cargo.toml?文件,阐述如何去构建这些 crate。Cargo 就是一个包含构建你代码的二进制项的包。Cargo 也包含这些二进制项所依赖的库。其他项目也能用 Cargo 库来实现与 Cargo 命令行程序一样的逻辑。包中可以包含至多一个库 crate(library crate)。包中可以包含任意多个二进制 crate(binary crate),但是必须至少包含一个 crate(无论是库的还是二进制的)。

  • 从 crate 根节点开始:当编译一个 crate,编译器首先在 crate 根文件(通常,对于一个库 crate 而言是?src/lib.rs,对于一个二进制 crate 而言是?src/main.rs)中寻找需要被编译的代码。
  • 声明模块: 在 crate 根文件中,你可以声明一个新模块;比如,你用mod garden声明了一个叫做garden的模块。编译器会在下列路径中寻找模块代码:
    • 内联,在大括号中,当mod garden后方不是一个分号而是一个大括号
    • 在文件?src/garden.rs
    • 在文件?src/garden/mod.rs
  • 声明子模块: 在除了 crate 根节点以外的其他文件中,你可以定义子模块。比如,你可能在src/garden.rs中定义了mod vegetables;。编译器会在以父模块命名的目录中寻找子模块代码:
    • 内联,在大括号中,当mod vegetables后方不是一个分号而是一个大括号
    • 在文件?src/garden/vegetables.rs
    • 在文件?src/garden/vegetables/mod.rs
  • 模块中的代码路径: 一旦一个模块是你 crate 的一部分,你可以在隐私规则允许的前提下,从同一个 crate 内的任意地方,通过代码路径引用该模块的代码。举例而言,一个 garden vegetables 模块下的Asparagus类型可以在crate::garden::vegetables::Asparagus被找到。
  • 私有 vs 公用: 一个模块里的代码默认对其父模块私有。为了使一个模块公用,应当在声明时使用pub mod替代mod。为了使一个公用模块内部的成员公用,应当在声明前使用pub
  • use?关键字: 在一个作用域内,use关键字创建了一个成员的快捷方式,用来减少长路径的重复。在任何可以引用crate::garden::vegetables::Asparagus的作用域,你可以通过?use crate::garden::vegetables::Asparagus;创建一个快捷方式,然后你就可以在作用域中只写Asparagus来使用该类型。

示例:创建一个名为backyard的二进制 crate 来说明这些规则。该 crate 的路径同样命名为backyard,该路径包含了这些文件和目录:

这个例子中的 crate 根文件是src/main.rs,该文件包括了:

文件名:src/main.rs

use crate::garden::vegetables::Asparagus;

pub mod garden;

fn main() {
? ? let plant = Asparagus {};
? ? println!("I'm growing {:?}!", plant);
}

pub mod garden;行告诉编译器应该包含在?src/garden.rs?文件中发现的代码:

文件名:src/garden.rs

pub mod vegetables;

在此处,?pub mod vegetables;意味着在src/garden/vegetables.rs中的代码也应该被包括。这些代码是:

#[derive(Debug)]
pub struct Asparagus {}

模块的路径有两种形式:

  • 绝对路径absolute path)是以 crate 根(root)开头的全路径;对于外部 crate 的代码,是以 crate 名开头的绝对路径,对于当前 crate 的代码,则以字面值?crate?开头。
  • 相对路径relative path)从当前模块开始,以?selfsuper?或当前模块的标识符开头。

绝对路径和相对路径都后跟一个或多个由双冒号(::)分割的标识符。

4、通过例子学 Rust

英文文档:https://doc.rust-lang.org/rust-by-example/

中文文档:https://rustwiki.org/zh-CN/rust-by-example/

查看更多?Rust 官方文档中英文双语教程,包括双语版《Rust 程序设计语言》(出版书名为《Rust 权威指南》),?Rust 标准库中文版

《通过例子学 Rust》(Rust By Example 中文版)翻译自?Rust By Example,中文版最后更新时间:2022-1-26。查看此书的?Github 翻译项目和源码

开始学习吧!

  • Hello World?- 从经典的 “Hello World” 程序开始学习。

  • 原生类型?- 学习有符号整型,无符号整型和其他原生类型。

  • 自定义类型?- 结构体?struct?和 枚举?enum

  • 变量绑定?- 变量绑定,作用域,变量遮蔽。

  • 类型系统?- 学习改变和定义类型。

  • 类型转换

  • 表达式

  • 流程控制?-?if/elsefor,以及其他流程控制有关内容。

  • 函数?- 学习方法、闭包和高阶函数。

  • 模块?- 使用模块来组织代码。

  • Crate?- crate 是 Rust 中的编译单元。学习创建一个库。

  • Cargo?- 学习官方的 Rust 包管理工具的一些基本功能。

  • 属性?- 属性是应用于某些模块、crate 或项的元数据(metadata)。

  • 泛型?- 学习编写能够适用于多种类型参数的函数或数据类型。

  • 作用域规则?- 作用域在所有权(ownership)、借用(borrowing)和生命周期(lifetime)中起着重要作用。

  • 特性 trait?- trait 是对未知类型(Self)定义的方法集。

  • 错误处理?- 学习 Rust 语言处理失败的方式。

  • 标准库类型?- 学习?std?标准库提供的一些自定义类型。

  • 标准库更多介绍?- 更多关于文件处理、线程的自定义类型。

  • 测试?- Rust 语言的各种测试手段。

  • 不安全操作

  • 兼容性

  • 补充?- 文档和基准测试

文章来源:https://blog.csdn.net/freeking101/article/details/134928544
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。