【Rust】简要描述学习 Rust 的一些心得
# 简要描述学习 Rust 的一些心得本文旨在简要描述 Rust 相对于 C++ 的设计上的差异,有 C++ 基础的朋友们应该能看懂。如果你还会 Python 那就更容易了解 Rust 了。
Rust 给我的感觉是「像 Python 那样使用方便,但是静态类型 + RAII,有编译器优化,有完善的库,有包管理器」的编程语言。可以帮助程序员提高生产力,使用更低的思考成本去编写出运行效率更高、更适合团队合作的程序源码。
本文并不会全面介绍 Rust,如果想要全面学习 Rust,请阅读 (https://doc.rust-lang.org/book/title-page.html)。
如果你没有使用过 C++ 的经验,请勿阅读本文以节省您的时间。
## 安装 Rust
以下为 Windows 安装 Rust 的方式。
1. 下载 `rustup-init.exe` (自己搜)
2. 准备梯子(没有梯子的话,它的下载速度是 1B/s,四舍五入等于 0B/s)
3. 启动 Powershell
$proxy='http://<梯子IP>:<梯子端口>'
$ENV:HTTP_PROXY=$proxy
$ENV:HTTPS_PROXY=$proxy
cd ~\Downloads
.\rustup-init.exe
然后跟着向导安装就行。Windows 上你需要准备好你的 MSVC,因为 Rust 依赖 MSVC 生成 exe。Linux 上你需要有 gcc。
## 包管理
Python 有 pip,Rust 有 cargo。
## 编辑器
我用 Sublime Text。装个 Rust 语法高亮就行。有人会推荐你用 VSCode。看个人习惯,我更习惯 Sublime Text,我写 Python 用的也是这个。
写 Rust 不用像写 C++ 那样高度依赖 VS2022 编辑器。
我的同事认为我太变态了,直接拿简单的编辑器写 Python,没必要。他使用 VSCode 编辑并调试 Python。
## 变量
请看例子代码,对比 Rust 和 C++ 在定义变量的语法上的差异。
```
let x = 5; // Rust
const auto x = 5; // C++
let mut y = 5; // Rust
auto y = 5; // C++
let z: u32 = 5; // Rust
const uint32_t z = 5; // C++
```
Rust 的变量默认不可变,加了 `mut` 修饰后就可变了。作为对比,C++ 的变量默认可变,加了 `const` 就不可变了。
Rust 允许重复定义同名的变量,新定义的同名变量会覆盖掉旧的,但是如果新的同名变量没了(比如 Out of scope 了),旧的变量就又能用了。这个叫变量的 Shadowing 概念。
Rust 也有像 C++ 那样的「引用」的概念,用 `&` 表示引用。但是 Rust 的变量不是你想引用几次就引用几次的,它有条件限制,不过如果你写过正经的 C++,这个限制并不会对你造成什么妨碍,反而会让你舒适。
## 整数溢出检查
以 Debug 方式编译的 Rust 程序的整数运算会像 VB6 那样,检查整数是否溢出,如果有溢出就会造成 panic,可以使用专门的“允许溢出”的计算方式去计算。而如果是 Release 方式编译的 Rust 程序则不会检查整数溢出。
## 语句和表达式
Rust 的特性,请看例子代码:
```
let x =
{
语句1;
语句2;
函数调用();
返回值 // 注意没有分号
};
```
它会按顺序运行 `语句1; 语句2; 函数调用();` 然后把 `返回值` 赋给 `x`。
这里面的 `{}` 块是一个表达式,它是有返回值的(也可以没有)。下面有个更直观的例子,写成一行就是:
```
let x = {语句1; 语句2; 语句3; 语句4; 返回值};
```
它会按顺序运行 `语句1; 语句2; 语句3; 语句4;` 然后把 `返回值` 赋给 `x`。
相同的规则也可以应用到函数定义上,请看例子代码:
```
fn foo() -> i32
{
let a = 40;
let b = 2;
a + b
}
```
这个函数运行后会返回 42。如果你不喜欢这种方式,你也可以用 `return`,如下:
```
fn foo() -> i32
{
let a = 40;
let b = 2;
return a + b;
}
```
如果你曾经是 C 语言的宏孩儿,那这个特性应该能让你爽到。
此外,Rust 有类似于 Python 的一些写法,比如 `` 是数组,`(1, 2, 3)` 是 `tuple`,而 `(u32, i32, i8)` 这样的则是声明一个 `tuple` 的每个成员的具体类型。
## 资源 Ownership
Rust 的资源 Ownership 会让人觉得这是它的一个独特的语法特性,似乎难以理解,但其实如果你知道 C++ 的 `std::move()`,理解 `移动构造`,你就比较容易理解 Rust 的 Ownership 概念。
C++ 的 `std::move()` 是移动语义,比如智能指针的操作,使用移动语义可以把智能指针本身(而不是其指向的内容)移动到另一个智能指针身上。
* Rust 有 RAII,规则基本上和 C++ 相同。
* Rust 的结构体有 `Copy()` 和 `Clone()` 方法,用于区分一个结构体是否可以用移动语义。如果你实现了 `Clone()` (深拷贝),那么你的这个结构体就可以用移动语义。
* Rust 的赋值语句 `=` 是移动语义,也就是相当于进行一个 C++ 的 `std::move()` 操作。基本变量类型除外。
* 同理,Rust 的函数调用,其参数也是这样传递的(移动语义)。函数结束的时候,这个资源被回收。绝大多数情况下是不这样整的,因此通常都是传递引用。
有一种每个优化细节都被把握在自己手掌心上的安全感。
在移动语义这块,C++ 用好 `std::move()` 可以达到接近的效果。
除了默认的移动语义外,Rust 还有以下的关于引用的限定规则:
* 有只读引用和可变引用,前者指的是不能修改被引用的变量,后者允许修改被引用的变量。
* 同一个 Scope 里,对一个变量可以有多个只读引用,但是只能有最多一个可变引用,一旦有了可变引用,就不能有只读引用。
不过实际上规则并没有这么死板,引用只在被你使用的时候(比如作为函数参数传递的时候,或者结构体成员方法会改变结构体自身数据的时候),它检查变量是否符合引用规则。
Rust 编译器在编译期间可以判断每个引用是否有效,任何时候你使用了一个引用了 Out of scope 的变量的时候(最简单的例子,比如函数返回一个 Scope 内的变量的引用),它会报错提示。
## 字符串或者数组的 Slicing
Rust 的字符串或者数组可以像 Python 那样去取 Slice(视为对字符串或者数组的引用,遵循引用规则)。但是对于字符串,一般不轻易取 Slice,因为 Rust 的字符串是 UTF-8 编码的,但你去 Slice 它的时候,你用的 Index 是基于字节而不是字符的。Rust 认为数组、字符串用 `[]` 去取其中的元素的这种写法,必须符合 O(1) 复杂度。Python 则不一定,有时候时间复杂度即使是 O(n) ,它也有东西是把接口设计成让你用 `[]` 去取的。
那如果你真的去 Slice 一个 UTF-8 的字符串,会怎样?Rust 会判断你是不是取完整了一个 UTF-8 字符串,如果取完整了,那就没事;否则它就 panic,提示你截断了 UTF-8 的字符。Rust 的字符串提供了遍历每个 UTF-8 字符的方法。
Rust 的字符串有两个类型,一个是 `str`,一个是 `String`,后者拥有一块堆上内存,存储字符串的内容。
Rust 的字符串常量属于 `str`,而 Rust 的字符串 Slice 也是 `str`。
C++ 没有这么方便的 Slice。C++ 从 UTF-8 字符串里面取出来的是 C++ 的 `char`。C++ 没有针对 UTF-8、UTF-16、UTF-32 进行互相转换的标准库。惨。
## 枚举
当你看到以下的代码的时候,你是不是觉得 Rust 的 `enum` 和 C++ 的几乎是一样的?
```
enum IpAddrKind
{
V4,
V6,
} // 这里没有分号
```
但是当你看到以下代码的时候,你会懵圈,为啥 Rust 的 `enum` 还能包含数据类型?
事实上,Rust 的 `enum` 的每一个枚举项,都可以携带一个属于它的专属的变量类型的数据。
```
enum IpAddr
{
V4(String),
V6(String),
}
```
或者:
```
enum IpAddr
{
V4(u8, u8, u8, u8),
V6(String),
}
```
其实,这段代码相当于以下的 C++ 代码:
```
enum IpAddr_enum
{
V4,
V6,
};
struct IpAddr
{
IpAddr_enum tag;
union
{
uint8_t V4_value; // 对应上面的 (u8, u8, u8, u8),猜猜看我为什么不使用 std::tuple
std::string V6_value; // 此处假设这个 V6_value 能得到正确的初始化和资源释放
};
};
```
Rust 自带的 `Option<T>` 是一个很有用的枚举,它用于消灭 `null`。它的定义如下:
```
enum Option<T> {
None,
Some(T),
}
```
当你的数据有可能是“没有”的状态的时候,也就是你如果有可能需要使用 `是否为 null` 的写法的时候,这个枚举就派上用场了。使用 `match` 块(类似于 C++ 的 `switch`)来判断这个类型的变量,如果是 `None` 那就没有数据;如果是 `Some` 那么你可以提取出对应类型的数据。具体看《The Book》的 *The match Control Flow Construct* 章节,可以在每个 case 的位置用括弧给你的数据定义变量名,然后拿来用。
为什么它被用于“消灭 `null`”?因为它强制你首先判断一个数据是不是 `None`,然后才允许你去用这个数据。这种麻烦让你首先就去思考要不要把一个数据整成“可 `null` 的数据”。
## 结构体
Rust 的结构体声明方式有很多种,熟悉 C++ 的人应该更适应下面这种:
```
struct User
{
active: bool,
username: String,
email: String,
sign_in_count: u64,
}
```
Rust 可以直接用 `tuple` 的方式声明结构体,比如:
```
struct Color(i32, i32, i32); // 这种结构体的成员可以用 x.0、x.1、x.2 的方式访问。
```
Rust 不像 C++ 那样拥有默认构造函数,正常情况下,创建结构体实例的时候必须初始化每一个成员。
Rust 没有结构体继承的概念。
Rust 的结构体可以很方便地调试,使用 `dbg!()` 可以在运行的时候打印结构体的每个成员名字和值,而且有多种方式控制打印出来的格式。
Rust 结构体方法的定义写在 `struct` 块的外面,如下:
```
struct Rectangle
{
width: u32,
height: u32,
}
impl Rectangle
{
fn area(&self) -> u32
{
self.width * self.height
}
}
```
看到 `&self` 了吗?像不像 Python?如果你不会 Python,你可以理解为这个 `&self` 相当于 C++ 的 `*this`。
一个 `impl` 块可以包含多个成员方法。Rust 允许你给一个结构体编写多个 `impl` 块。
构造函数怎么写?参考下面的代码:
```
impl Rectangle
{
fn square(size: u32) -> Self
{
Self
{
width: size,
height: size,
}
}
}
```
这个函数构造一个正方形的 `Rectangle`。
## 库与目录结构,命名空间
这块,Rust 像 Python。Python 用 `import` 引入一个 Python 的库,这个 `import` 遵循一定的目录结构规则,而 Rust 的 `mod` 也有差不多的规则,每个模块有它自己的命名空间。
除此以外,在单个源码文件里,Rust 可以使用 `mod xxx {}` 块来定义命名空间,类似于 C++ 的 `namespace {}` 块。
Rust 使用双冒号 `::` 来访问子命名空间,像 C++。
Rust 可以用 `use` 来简化命名空间,类似于 C++ 的 `using namespace`。
Rust 使用 Cargo 管理模块,爽。
### 公共,私有的控制
Rust 用 `pub` 前缀来修饰模块、结构体、成员方法、函数的公有性。把一个子模块整成公有的以后,外部就能访问你的子模块。结构体被整成公有的以后,外部就能使用你的结构体。把结构体的成员方法整成公有的后,外部就能调用你的结构体成员方法。
Python 使用下划线 `_` 前缀修饰模块或者类的成员,使其成为私有的。惨。
##数据结构
### 向量
Rust 有 `Vec<T>`,约等于 C++ 的 `std::vector<T>`。在 Rust 引用 `Vec<T>` 里面的元素的时候,有两种写法:
```
let v = vec!;
// 写法1
let third: &i32 = &v; // 如果 index 超出了,它就 panic
// 写法2
let third: Option<&i32> = v.get(2);
// 利用了 Option<T> 的特性,如果 index 超出了,它就返回 None,否则返回 Some(数据)
```
Rust 可以直接用 `for i in &v {}` 来遍历 `v`。写法像 Python。C++ 用 `for (auto i: v) {}` 来遍历 `v`。如果要在循环的时候修改数据,Rust 用这种写法:`for i in &mut v {*i += 50;}` (此处需要解引用 `i`)
### 哈希表
今天先写到这,等我继续学 Rust。
页:
[1]