About Hello Rust

Important

最好的学习方法是间隔性重复学习。

一个编程高手是怎样练成的呢? 惟手熟尔。重在刻意练习。 这意为着,就是不断重复练习,实践,再实践,熟练掌握各种技能。因为,只有反复练习,才能真正掌握。

Hello,Rust 是如何产生的呢? 这是我在学习Rust过程中,不断地编写样例代码,不断点滴积累经验,最终形成的。

Rust 是一个非常优秀的系统编程语言,它简洁易读,性能高,更安全,功能强大。然而,它也存在学习曲线陡峭的问题,需要花费大量时间和精力去理解借用检查、所有权机制等新的编程概念,确实不太容易上手。

对于新手来说,Hello, Rust 是一个绝佳的起点。通过这个项目,你不仅能快速入门 Rust 编程,还能通过编程、调试、运行示例代码,迅速掌握 Rust 的核心知识点,熟悉基础语法和基本概念。更棒的是,它还涵盖了高级进阶知识和精选的 Rust Crates 库应用示例。

本书的当前版本假设你使用 Rust 1.86.0(2025-04-03 发布)或更高版本并在所有项目的 Cargo.toml 文件中通过 edition = "2024"将其配置为使用 Rust 2024 edition 惯用法。请查看Getting Started的 “安装” 部分了解如何安装和升级 Rust。

Introduction

Rust 是一种通用的编程语言,强调性能、类型安全和并发性。它强制执行内存安全,确保所有引用都指向有效内存。与传统的垃圾回收机制不同,Rust 通过“借用检查器”在编译时跟踪引用的对象生命周期,从而防止内存安全错误和数据竞争。

Rust 支持多种编程范式。它受到函数式编程思想的影响,包括不可变性、高阶函数、代数数据类型和模式匹配。同时,它通过结构体、枚举、特性和方法支持面向对象编程。

为什么选择 Rust?

  • 安全性:Rust 的所有权和借用系统确保了内存安全。
  • 性能:Rust 的编译器可以生成高效的机器码,接近C的性能。
  • 并发性:Rust 提供了强大的并发模型,支持异步编程
  • 可读性:Rust 的代码风格简洁易读。

Getting Started

安装 Rust

首先,你需要安装 Rust。你可以从 Rust 官方网站 下载并安装。

Windows 上,你可以从 Rust 官方网站 下 载并安装。

Linux 上,你可以使用包管理器来安装 Rust:

    $ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
    $ rustup update
    # rustup toolchain install stable
    $ rustup default stable

或者 Ubuntu 下使用以下命令来安装 Rust:

    $ sudo apt-get install rustup
    $ rustup update
    # rustup toolchain install stable
    $ rustup default stable

MacOS 上,你可以使用 Homebrew 来安装 Rust:

    $ brew install rustup
    $ rustup update
    # rustup toolchain install stable
    $ rustup default stable

创建项目

安装完成后,你可以使用以下命令来创建一个新的 Rust项目:

    $ cargo new hello-rust

这将创建一个名为 hello-rust 的项目目录,并在其中创建一个 Cargo.toml 文件,其中包含了项目的依赖信息。 进入项目目录:

    $ cd hello-rust

你应该会看到以下目录结构:

.
├── Cargo.toml
└── src
    └── main.rs

现在你可以编辑 src/main.rs 文件来编写你的 Rust 代码。cargo 会默认生成一个 main.rs 文件,并在其中包含以下代码:

fn main() {
    println!("Hello, world!");
}

main.rs 是一个 Rust 程序的入口点。

或者你可以使用cargo new -lib hello-rust命令,创建的是一个crates库。Cargo 会创建一个lib.rs文件,它包含库的入口点,并且可以被其他 Rust 项目导入和使用。

Cargo.toml 文件内容如下:

[package]
name = "hello-rust"
version = "0.1.0"
edition = "2024"

[dependencies]

Cargo.toml 文件中,

  • [package] 部分定义了项目的名称、版本和 Rust 版本。
  • [dependencies] 部分定义了项目的依赖。在这个例子中,我们没有添加任何依赖,所以我们不需要添加任何依赖。

编译和运行

你可以使用以下命令来编译和运行项目

    $ cargo build
    $ cargo run

这将编译项目并运行 main.rs 文件。运行后,你会看到输出 Hello, world!

一个完整的 Rust 项目结构

Cargo 推荐的目录结构,如下:

  • Cargo.toml 和 Cargo.lock 保存在 package 根目录下
  • 源代码放在 src 目录下
  • Crate子模块源代码放在 crates 目录下
  • 默认的 lib 包根是 src/lib.rs
  • 默认的二进制包根是 src/main.rs
    • 其它二进制包根放在 src/bin/ 目录下
  • 基准测试 benchmark 放在 benches 目录下
  • 示例代码放在 examples 目录下
  • 集成测试代码放在 tests 目录下

测试你的代码

tip

良好的编程习惯,一定要写单元测试。下面先认识下,如何编写一个简单的单元测试,后面会有单独的章节来详细介绍如何编写单元测试。可以先使用Copy的技能,照着样例去写,然后慢慢深入理解。接下来去测试你的第一个 Rust 程序吧。

单元测试的结构

单元测试通常包含以下部分:

  1. 导入模块:使用 use 语句导入需要的模块。
  2. 定义测试函数:使用 #[test] 注解定义测试函数。测试函数应该以 fn 开头,并且返回 ResultOption 类型。
  3. 编写测试代码:在测试函数中编写实际的测试代码。你可以使用断言来验证函数的行为。
  4. 运行测试:使用 cargo test 命令来运行测试。

示例:单元测试


fn main() {
    println!("Hello, world!");
}

pub fn add(left: u64, right: u64) -> u64 {
    left + right
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn it_works() {
        let result = add(2, 2);
        assert_eq!(result, 4);
    }
}

cargo test 会运行 tests 目录下的所有测试文件。你可以使用以下命令来编译和运行测试,运行上述命令后,你会看到以下输出:

running 1 test
test tests::it_works ... ok

successes:

successes:
    tests::it_works

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

说明你的单元测试通过了。

在这个例子中,add 函数的测试通过了。如果 add 函数返回的值不是 4,测试将会失败。你可以通过修改 add 函数的返回值来验证这一点。

caution

如果你的测试失败了,你可以通过查看 test 目录下的输出文件来找到具体的错误信息。

running 1 test
test tests::it_works ... FAILED

successes:

successes:

failures:

---- tests::it_works stdout ----

thread 'tests::it_works' panicked at src/main.rs:16:9:
assertion `left == right` failed
  left: 4
 right: 5
stack backtrace:
   0: rust_begin_unwind
             at /rustc/05f9846f893b09a1be1fc8560e33fc3c815cfecb/library/std/src/panicking.rs:695:5
   1: core::panicking::panic_fmt
             at /rustc/05f9846f893b09a1be1fc8560e33fc3c815cfecb/library/core/src/panicking.rs:75:14
   2: core::panicking::assert_failed_inner
   3: core::panicking::assert_failed
             at /rustc/05f9846f893b09a1be1fc8560e33fc3c815cfecb/library/core/src/panicking.rs:380:5
   4: hello_rust::tests::it_works
             at ./src/main.rs:16:9
   5: hello_rust::tests::it_works::{{closure}}
             at ./src/main.rs:14:18
   6: core::ops::function::FnOnce::call_once
             at /Users/weirenyan/.rustup/toolchains/stable-aarch64-apple-darwin/lib/rustlib/src/rust/library/core/src/ops/function.rs:250:5
   7: core::ops::function::FnOnce::call_once
             at /rustc/05f9846f893b09a1be1fc8560e33fc3c815cfecb/library/core/src/ops/function.rs:250:5
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.


failures:
    tests::it_works

test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.02s

error: test failed, to rerun pass `-p hello-rust --bin hello-rust`

Rust 返回的测试失败结果信息,是很详细的,所以你一定要详细阅读错误信息,看清楚问题所在。最好的方法是通过错误问题,调试代码并解决这些问题,最终可以成功编译和运行项目,整体过程能快速提升代码能力。

note

经过上述简单的旅程,我们已经对 Rust 有了初步的了解。接下来,我们将深入探索 Rust 的核心概念和特性。那么,让我们继续前进吧!开始进入 Rust 的世界旅行吧!

基础入门

变量与表达式

变量

变量绑定是指将一个值赋给一个名称,这样就可以在程序的其他地方使用这个名称来引用该值了。 变量绑定有两部分组成:let关键字和类型注解(type annotation)。

常量

表达式

表达式是计算结果的代码块。它们可以是任何有效的 Rust 代码,包括函数调用、条件语句、循环等。表达式的值会被赋给变量或用于其他目的计算。

基础数据类型

Rust 支持常见的基本类型:

  • 整型: i8, i16, i32, i64, i128, isize
  • 无符号整型: u8, u16, u32, u64, u128, usize
  • 浮点数: f32, f64
  • 布尔值: bool
  • 字符: char

需要注意的是, Rust 中, "xxx" 是一个字符串字面量切片, 类型为 &str, 是在编译时就固定不可变的。 而 String 是一个字符串类型, 编译时动态分配, 可变长度。

了解所有权

  1. 如何解决函数内部定义变量如何返回出来的问题? 这是 Rust 所有权系统的核心问题。你不能返回一个指向函数内部栈上定义的局部变量的引用。 函数执行完毕后,栈上的局部变量会被清理掉,返回的引用将指向无效内存(悬垂指针
// 这是错误的示例!悬垂指针!
fn create_value_and_return_ref<'a>() -> &'a i32 {
    let value = 42; // value 在函数栈上
    // &value // 错误!不能返回指向栈上局部变量的引用,因为它在函数返回后就无效了
}

// 这是正确的,但需要一个外部的生命周期 'a
fn get_ref_from_external_source<'a>(data: &'a i32) -> &'a i32 {
    data // data 是从外部借用进来的,它的生命周期 >= 函数返回的引用生命周期
}

解决方法:

要将函数内部创建的数据“返回”出来,你必须转移该数据的所有权。Rust 的移动语义(Move Semantics)使得这变得简单且安全:

  • 直接返回数据 (按值返回): 函数返回类型是 T,你直接返回函数内部创建的变量。数据的所有权从函数内部转移到调用者。
fn create_value_and_return_owned() -> i32 {
    let value = 42; // value 在函数栈上
    value // value 的所有权被移出函数
} // value 在这里不会被 drop,因为它已经被移出

fn create_string_and_return_owned() -> String {
    let text = String::from("hello"); // text 在函数栈上,但其数据在堆上
    text // text 的所有权被移出函数。堆上的数据不会被清理。
} // text 在这里不会被 drop

fn create_box_and_return_owned() -> Box<i32> {
    let boxed_value = Box::new(100); // boxed_value 在函数栈上,它指向堆上的数据
    boxed_value // boxed_value 的所有权被移出函数。堆上的数据不会被清理。
} // boxed_value 在这里不会被 drop

  • 返回智能指针: 如果你需要共享数据,可以将内部创建的数据包装在 Rc 或 Arc 等智能指针中,并返回智能指针的副本。数据的实际所有权由智能指针管理,而你返回的是智能指针的共享引用或智能指针本身(所有权转移)。
use std::sync::Arc;
use std::cell::RefCell;

fn create_shared_data() -> Arc<RefCell<i32>> {
    let data = RefCell::new(0); // data 在函数栈上,它包装了堆上的数据
    Arc::new(data) // Arc::new 会将 RefCell 移动到堆上,并返回 Arc 的所有权
} // data 在这里不会被 drop,因为它内部的 RefCell 已经被移到堆上并被 Arc 拥有

fn ownership_shared_sample() {
    let shared = create_shared_data(); // shared 现在拥有 Arc 的所有权
    println!("{}", shared.borrow());
}
///
/// 单元测试
/// #[cfg(test)]
///
#[cfg(test)]
mod tests {
    // 注意这个惯用法:在 tests 模块中,从外部作用域导入所有名字。
    use super::*;

    #[test]
    fn test_ownership_shared_sample() {
        ownership_shared_sample();
        println!("print test in mdbook")
    }
}

结构体

结构体字段

结构体方法

枚举

模块

模块命名限制:

  • 以字母开头,可以包含数字和下划线。
  • 不可使用特殊字符。

命名约定

自定义模块不能与标准库中的模块冲突。例如: core, std

若模块命名为core,则会出现以下错误。

#![allow(unused)]
fn main() {
error[E0433]: failed to resolve: could not find marker in core
 --> crates/domain/src/repository/monitor_repository.rs:8:1
  |
8 | #[async_trait]
  | ^^^^^^^^^^^^^^ could not find marker in core

}

高级进阶

文件目录操作

临时文件

在 Rust 中,你可以使用 std::fs 模块来创建和管理临时文件。

数据库

并发异步

服务

序列化

精选实战

Database

  • SurrealDB

Services

Sequences

Message Queue

MQ Broker 和 Client 样例。

  • MQTT
    • Rumqtt: Rumqtt 是由 Rust 开发的支持 MQTT 3.1/5 协议规范的全功能的MQTT 的Broker 和 Client 。

数据库

服务

序列ID

消息队列

模板

Algo Sample

LeetCode Solution Sample