About Hello Rust
一个编程高手是怎样练成的呢? 惟手熟尔。重在刻意练习。 这意为着,就是不断重复练习,实践,再实践,熟练掌握各种技能。因为,只有反复练习,才能真正掌握。
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 程序吧。
单元测试的结构
单元测试通常包含以下部分:
- 导入模块:使用
use
语句导入需要的模块。 - 定义测试函数:使用
#[test]
注解定义测试函数。测试函数应该以fn
开头,并且返回Result
或Option
类型。 - 编写测试代码:在测试函数中编写实际的测试代码。你可以使用断言来验证函数的行为。
- 运行测试:使用
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 是一个字符串类型, 编译时动态分配, 可变长度。
了解所有权
- 如何解决函数内部定义变量如何返回出来的问题? 这是 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 。