Rust 是一门偏底层的,安全的,高效的开源编程语言。

本文记录了作者学习Rust语言的笔记和心得,涵盖从工具链到语言特性的核心内容。首先介绍了rustup、cargo等基础工具的安装和使用方法;然后详细讲解了Rust的变量特性、数据类型和内存管理模型,包括不可变变量、栈与堆的区别、所有权系统等独特概念;最后总结了Rust借鉴其他语言的优点以及自身的创新特性。这篇文章为Rust初学者提供了简明扼要的入门指南和关键知识点概览。

工具链

rustup 是管理 Rust 版本和相关工具的命令行工具(rust toolchain installer)

  • rustup update 更新 Rust 版本

  • rustup self uninstall 卸载 Rust 以及 rustup 本身

Rust 工具链中包括 rustc 编译器工具,rustfmt 格式化工具,rustdoc 文档化工具等。

rustc 类似于 C/C++中的 gcc/clang

CargoRust 的构建系统和包管理器。可以进行构建代码、下载依赖库并编译这些库等。

cargo new rust-proj 创建一个 Rust 项目,

1
2
3
4
5
6
7
8
9
$ cargo new rust-proj
Created binary (application) `rust-proj` package
$ tree rust-proj
rust-proj
├── Cargo.toml
└── src
└── main.rs

1 directory, 2 files

Cargo 会在 hello_cargo 目录初始化一个 git 仓库,以及一个 .gitignore 文件。如果你在现有的 git 仓库中运行 cargo new,则不会生成 git 文件;你可以通过使用 cargo new --vcs=git 来覆盖此行为。

可以通过 --vcs 参数使 cargo new 切换到其它版本控制系统(VCS),或者不使用 VCS。运行 cargo new --help 参看可用的选项。

cargo build 构建项目,目标可执行文件在 target/debug/

cargo run 则是构建项目并运行。

cargo check 检查项目代码正确,确保可编译。不会产生可执行文件。

cargo build 默认构建是 debug 模式。如果项目一切都 OK,可以运行 cargo build --release 执行发布构建,这会对项目进行一定编译优化,从而使得代码运行得更快,但是相应地,编译时间会更长。

cargo doc --open 会生成当前项目中的依赖库(crate)的文档,并转到浏览器可以查询。

Hello, World!

1
2
3
fn main() {
println!("Hello, world!");
}
  • println! 调用了一个 Rust 宏(macro)。如果是调用函数,则应输入 println(没有 !)。我们将在第十九章详细讨论宏。现在你只需记住,当看到符号 ! 的时候,就意味着调用的是宏而不是普通函数,并且宏并不总是遵循与函数相同的规则。

cargo new 生成的项目中,包含配置文件 Cargo.toml

文件名: Cargo.toml

1
2
3
[dependencies]

rand = "0.8.3"

其中"0.8.3"指定依赖版本,其语法遵循语义化版本 (Semantic Versioning)

这里的 0.8.3 实际上是 ^0.8.3 的简写,它表示 任何不低于 0.8.3, 但是低于 0.9.0 的版本。Cargo 将这些版本视作与 0.8.3 版本公有 API 相兼容的版本,这个声明确保你将获得最新的补丁版本,它仍然可以与本章中的代码正常编译。0.9.0 或以上版本不保证拥有接下来示例中使用到的 API。


一些定义

  • Rust 变量默认是不可改变的(immutable),如果想声明可变变量,需要使用 mut 关键字。

  • Rust 不允许对常量使用 mut。常量不光默认不能变,它总是不能变。其声明使用 const,且必须标注类型。

  • 整型变量分有符号和无符号,如 i8 表示有符号 8 位整数,u8 表示无符号 8 位整数,i128 表示有符号 128 位整数。

  • 还有一种整型依赖计算机架构,isize, usize 在不同的架构中表示的长度会不同。

  • 整型字面值有十六进制(0xff),八进制(0o77),二进制(0b11),以及十进制(99_999),单字节字符(仅限 u8)

  • Rust 默认数字类型是 i32

  • 浮点型变量有两种,f32 表示单精度浮点小数,f64 表示双精度浮点小数。默认是 f64

  • 字符类型(char)用单引号表示,其大小为 4 个字节,表示了一个 Unicode 标量值。

  • 复合类型元组(tuple)一旦声明,其长度不变,其中每一个位置都有一个类型的值,类型可以不同。

  • 复合类型数组(array)长度也是固定的,而且其中类型必须相同。

语句(statement)和表达式(expression)

Rust 是一门基于表达式的语言。

语句是执行一些操作,但是不返回值的指令。

函数定义 就是一个语句。

表达式计算并产生一个值,可以赋予其它变量。

函数调用 是一个表达式,大括号创建的块作用域也是一个表达式。

表达式的结尾没有分号,如果结尾加上分号,则变成了语句。

数字本身就是一个表达式,所以它可以返回本身的值赋值给其它变量。

栈(stack)中的数据必须占用已知且大小固定的大小。

当数据大小未知,或大小可能变化的时候,需要用堆(heap)。比如使用内存分配器 memory allocator 分配指定大小的内存,此时该块内存会被标记为已用,并且以指针来表示这块内存。

而将数据放入栈中的过程并不叫做分配内存,因为这个过程并没有新的内存被分配。

堆的指针可以存储在栈上。

入栈比在堆上分配内存要快。

访问堆中的数据比访问栈上的数据要慢,因为访问堆首先要通过指针来访问,而指针在栈上。


一些笔记

  • 定义变量用动词 let

  • 借鉴 Python 的元组

  • 借鉴 C++ 的指针

  • 完善的包管理工具 cargo

  • 友好的文档管理

  • 使用先进的 VCS 管理项目

  • 无垃圾回收机制,使用变量所有权管理内存

  • 编译与执行分开,更早的发现错误

  • 浅拷贝与深拷贝,默认不进行数据的深拷贝,深拷贝使用 clone 方法

  • 移动(move)操作

  • Copy trait

  • 栈上数据;堆上数据;既在栈上,又在推上(数据指针在栈上,数据内容在堆上)数据

  • 默认行为:默认变量不可变

  • 引用(ref)变量默认也不能修改其引用的值

  • 一个引用的作用域从声明的地方开始一直持续到最后一次使用为止

  • 字符串 slice


  • Rust 可以直接将数据附加到枚举的每个成员上,这样就不需要一个额外的结构体了。

1
2
3
4
5
6
7
enum IpAddr {
V4(String),
V6(String),
}
let home = IpAddr::V4(String::from("127.0.0.1"));

let loopback = IpAddr::V6(String::from("::1"));
  • Rust 没有空值(Null),因为空值会导致很多错误,而是实现了一个枚举变量 Option<T>

1
2
3
4
5
6
7
8
9
10
11
enum Option<T> {
Some(T),
None,
}


let some_number = Some(5);
let some_string = Some("a string");
# 当为 `None` 时,必须显示指定类型 `<T>`
let absent_number: Option<i32> = None;

关于字符串方法,以下两种方法等效:

1
2
let s1 = String::from("initial contents");
let s2 = "initial contents".to_string();

参考链接: