说在前面
做了一个长期学习Rust的计划,每天的学习都以博客的形式记录,同时也作为给自己查询的笔记。 任何错误欢迎斧正,爱来自a@likaix.in
7月4日内容补充修正
Rust 环境搭建(Linux)
开发环境
毋庸置疑,我选择Visual Studio Code作为我的开发环境(code)。如果是Ubuntu有图形化页面,进入软件商店搜code下载即可。如果没有就去Visual Studio Code官网下载对应的deb包安装即可。
安装Rust编译工具
Rust的编译工具依赖于C的编译工具,所以在linux下你首先可以安装GCC,在Ubuntu中使用sudo apt install gcc安装。安装后输入g++ --version出现相应版本号即表示安装成功。做完这些前置步骤,终端运行curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh自动安装。下载完毕后会出现安装脚本页面,选择默认回车即可安装。注意在安装后一定要重启终端,否则通过rustc -V也不会正确输出版本号。只有重启终端后,输入rustc -V出现版本号即表示安装成功。
Rust 基础学习
输出命令行与创建项目
cargo new project-name即可快速创建一个Rust项目,在src文件夹下找到对应的main.rs文件即可开始进行hello world。终端cd进入src文件夹,输入rustc main.rs会在src文件夹下生成main可执行文件,./main就会发现终端顺利的输输出了hello world。
fn main() {
println!("hello world");
}Rust 的基础语法
Rust作为一直强类型语言,但是又同时具有上下文自动去推测变量类型的功能。默认情况下Rust声明的变量类型是不可变变量。除非使用mut关键字声明为可变变量。
let a = 111; //不可变变量
let mut b = 111; //可变变量听起来有点反人类,不可变变量,既然是变量为何又说其不可变?那不是和常量没有区别吗?所谓常量简单来说就是程序运行期间都不会改变的变量。听起来似乎和不可变变量一致。其实不然,看下面的代码。
let a = 111;
let a = 222;编译器可能会有警告,但是没有语法错误,但是使用以下代码是不合法的。
const a: i32 = 111; //必须显示声明类型
let a = 222; 二者的作用域也不同,let声明的不可变变量在块级作用域内(例如函数内),const声明的常量在全局/模块级作用域内。 为什么要设计这样的默认不可变变量呢:
- 安全性(Memory Safety)避免意外的修改
- 并发友好(Concurrency)多个线程也可以安全的共享,无需锁机制
- 编译器优化 静态分析 不可变性让编译器更容易优化代码
Rust数据类型
整数(Integer)
| 位长度 | 有符号 | 无符号 |
|---|---|---|
| 8-bit | i8 | u8 |
| 16-bit | i16 | u16 |
| 32-bit | i32 | u32 |
| 64-bit | i64 | u64 |
| 128-bit | i128 | u128 |
| arch | isize | usize |
浮点数型(Floating-Point)
Rust与其它语言一样支持32位浮点数(f32)和64位浮点数(f64)。默认情况下,64.0 将表示 64 位浮点数,因为现代计算机处理器对两种浮点数计算的速度几乎相同,但 64 位浮点数精度更高。
fn main() {
let x = 2.0; // f64
let y: f32 = 3.0; // f32
}布尔型(bool)
值只能为 true 或 false。这个只能就表示{-1,0,1}并不会被隐式的转化为布尔值。所以类似于C语言中的while(1)之类的是不合法的。
字符型(char)
Rust的char类型大小为4个字节,代表Unicode标量值,这意味着它可以支持中文,日文和韩文字符等非英文字符甚至表情符号和零宽度空格在Rust中都是有效的char值。
注意:由于中文文字编码有两种(GBK 和 UTF-8),所以编程中使用中文字符串有可能导致乱码的出现,这是因为源程序与命令行的文字编码不一致,所以在 Rust 中字符串和字符都必须使用 UTF-8 编码,否则编译器会报错。
复合类型
元组是用一对 ( ) 包括的一组数据,可以包含不同种类的数据:
let tup: (i32, f64, u8) = (500, 6.4, 1);
// tup.0 等于 500
// tup.1 等于 6.4
// tup.2 等于 1
let (x, y, z) = tup;
// y 等于 6.4数组是用一对 [ ] 包括同类型数据:
let a = [1,2,3,4,5]
Rust 数学运算
简单的加减乘除和其他语言基本上一致,但是Rust没有自加(++)和自减(—)运算。也就是说i++可能要写为i=i+1。
Rust注释
和C语言的单多行数据基本上一致,rust把双//后的第一个/也会算作注释,所以///也是合法的。
// 这是第一种注释方式
/* 这是第二种注释方式 */
/*
* 多行注释
* 多行注释
* 多行注释
*/
/// 也是可以的Rust 函数
不是function也不用说明返回值,Rust函数的基本形式:
fn <函数名> ( <参数> ) <函数体> Rust 函数名称的命名风格是小写字母以下划线分割:
fn main(){
hello_world();
}
fn hello_world()
{
println!("Hell0 world!");
} Rust不在乎在何处定义函数,只需在某个地方定义它们即可。
函数参数
Rust中定义函数如果需要具备参数必须声明参数名称和类型:
fn main() {
another_function(5, 6);
}
fn another_function(x: i32, y: i32) {
println!("x 的值为 : {}", x);
println!("y 的值为 : {}", y);
}运行结果:
x 的值为 : 5
y 的值为 : 6函数体的语句和表达式
Rust函数体由一系列可以以表达式(Expression)结尾的语句(Statement)组成。到目前为止,我们仅见到了没有以表达式结尾的函数,但已经将表达式用作语句的一部分。
语句是执行某些操作且没有返回值的步骤。
let a = 6;let a = (let b = 2); //不正确的语句在Rust中
- 表达式(Expression):会计算并返回一个值(如 1 + 2、foo())。
- 语句(Statement):执行操作但不返回值(如 let b = 2;、return x;)。
let b = 2 是一个语句,它没有返回值(不像 b = 2 在 C 中会返回赋值后的值)。因此,它不能嵌套在另一个 let 赋值中。
但是以下是合理:
fn main() {
let x = 5;
let y = {
let x = 3;
x + 1
};
println!("x 的值为 : {}", x);
println!("y 的值为 : {}", y);
} 可以在{}中编写一个较为复杂的表达式,其中的let x = 3是语句,不返回任何值。但是x + 1是表达式,返回了x + 1的值,所以是合法的。x + 1后面不能加分号,不然就会变成一条语句。
函数返回值
在参数声明之后用 -> 来声明函数返回值的类型(不是 : )。在函数体中,随时都可以以 return 关键字结束函数运行并返回一个类型合适的值。
fn add(a: i32, b: i32) -> i32 {
return a + b;
} 如果说没有声明返回值的类型,就不能使用return,同时函数表达式不能使用return。举个例子:
fn main() {
let result = {
let x = 3;
let y = 5;
return x + y; // 错误!`return` 会直接退出 `main()`
};
println!("Result: {}", result); // 这行永远不会执行
}fn main() {
let value = if true {
return 42; // 错误!`return` 会退出 `main()`
} else {
0
};
println!("Value: {}", value); // 这行不会执行
}
Rust 条件语句
Rust中的条件语句如下所示:
fn main() {
let a = 1;
if a < 5 {
println!(1);
}
else
{
println!(2);
}
}
和C语言相比有几点区别:
- 不需要用()去包裹条件语句
a<5,但是也可以用()去包裹。 - 不存在单语句可以不用{}包裹的语法。
- 表达式必须是bool类型。
Rust中没有三元表达式,但是可以通过以下语句达成同样的效果:
if <condition> { block 1 } else { block 2 } Rust 循环
while 循环
fn main() {
let mut number = 1;
while number != 4 {
println!("{}", number);
number += 1;
}
println!("EXIT");
} 没有do-while的语法,但是do依旧作为保留字存在。
Rust中不存在类似C语言中的,for(int i = 1;i<n;i++)使用三元语句控制循环。需要使用while循环
let mut i = 0;
while i < 10 {
// 循环体
i += 1;
}for 循环
这里的.iter是迭代器,后文会详细说明
fn main() {
let a = [10, 20, 30, 40, 50];
for i in a.iter() {
println!("值为 : {}", i);
}
}也可以通过下标来访问数组
fn main() {
let a = [10, 20, 30, 40, 50];
for i in 0..5 {
println!("a[{}] = {}", i, a[i]);
}
}loop 循环
Rust语言具有原生的无限循环结构loop:
fn main() {
let s = ['1', '2', '3', '4', '5', '6'];
let mut i = 0;
loop {
let ch = s[i];
if ch == 'O' {
break;
}
println!("\'{}\'", ch);
i += 1;
}
} Rust支持使用break i;的样式,也就是说可以在结束循环的时候返回一个值,如下所示:
fn main() {
let s = ['R', 'U', 'N', 'O', 'O', 'B'];
let mut i = 0;
let location = loop {
let ch = s[i];
if ch == 'O' {
break i;
}
i += 1;
};
println!(" \'O\' 的索引为 {}", location);
} 当loop循环结束的时候返回了i的值,此时location就获得了i的值.
Rust 迭代器
Rust 中的迭代器(Iterator)是一个强大且灵活的工具,用于对集合(如数组、向量、链表等)进行逐步访问和操作。 迭代器通过实现Iterator trait来定义。最基本的trait方法是next,用于逐一返回迭代器中的下一个元素。
迭代器有这么几种原则:
惰性求值(Lazy Evaluation)
迭代器不应预先计算所有值,而应在调用next()时按需生成。
let v = vec![1, 2, 3];
let iter = v.iter().map(|x| {
println!("Processing {}", x);
x * 2
}); // 此时不会执行 map 中的逻辑
for x in iter { // 调用 next() 时才会实际计算
println!("Got {}", x);
}
线性消费(Linear Consumption)
迭代器通常是一次性的,调用next()会消耗当前元素,无法回退或重置。某些迭代器(如 std::iter::Rev)可能支持双向遍历,但需明确实现 DoubleEndedIterator。
所有权清晰(Ownership Semantics)(后文会详细解释所有权)
三种变体:
- 不可变借用迭代器:
iter() → Iterator<Item = &T> - 可变借用迭代器:
iter_mut() → Iterator<Item = &mut T> - 所有权迭代器:
into_iter() → Iterator<Item = T>(消费集合)
三种实例如下:
let vec = vec![1, 2, 3, 4, 5];
let iter = vec.iter();
let mut vec = vec![1, 2, 3, 4, 5];
let iter_mut = vec.iter_mut();
let vec = vec![1, 2, 3, 4, 5];
let into_iter = vec.into_iter();无副作用(Side-Effect Free next())
next() 的逻辑应尽量纯粹,避免修改外部状态(除非是迭代器自身的必要状态)。 反例:
// 不良设计:next() 修改外部变量
let mut count = 0;
let bad_iter = std::iter::from_fn(|| {
count += 1; // 副作用!
Some(count)
});
终止性(Termination)
迭代器必须能在有限次 next() 后返回 None(除非明确设计为无限迭代器)。 无限迭代器需明确文档说明(如 std::iter::repeat)。 示例:
// 有限迭代器
let finite = (0..3).into_iter(); // 最终返回 None
// 无限迭代器需配合 take 等适配器使用
let infinite = std::iter::repeat(1).take(5);
方法链式调用(Method Chaining)
迭代器适配器(如 map/filter)应返回新的迭代器,而非修改原有迭代器。 示例:
let result = (1..5)
.map(|x| x * 2) // 返回新迭代器
.filter(|x| x > 3) // 再返回新迭代器
.collect::<Vec<_>>();
性能保证(Performance Guarantees)
零成本抽象:迭代器应编译为与手写循环相近的高效代码。短路操作:如 find/any 应在满足条件时立即停止迭代。
遵循 Iterator Trait 约定
必须实现:
type Itemfn next(&mut self) ->Option<Self::Item>
一个良好设计的迭代器应该是这样的:
// 良好设计的迭代器
struct Countdown(u32);
impl Iterator for Countdown {
type Item = u32;
fn next(&mut self) -> Option<Self::Item> {
if self.0 == 0 {
None
} else {
let val = self.0;
self.0 -= 1;
Some(val)
}
}
// 优化:覆盖默认的 size_hint
fn size_hint(&self) -> (usize, Option<usize>) {
(self.0 as usize, Some(self.0 as usize))
}
}
fn main() {
let countdown = Countdown(3);
assert_eq!(countdown.collect::<Vec<_>>(), vec![3, 2, 1]);
}
刚看到以上代码你可以会非常困惑,没事这是正常的。我先告诉你代码实现了一个倒计时功能,然后让我们来逐行解析一下:
struct Countdown(u32); // 这个结构体叫做元组结构体,里面只含有一个u32类型的整数。
impl Iterator for Countdown { // 使得这个元组结构体有迭代器的功能
type Item = u32; // 这个Item是一个关联类型,这相当于在trait中声明了一个"占位符类型",具体实现时需要填充。
fn next(&mut self) -> Option<Self::Item> { // next是函数名,&mut self表示可变借用接收者,说明会修改迭代器的内部状态,-> Option<Self::Item> 说明返回一个Option枚举,并且这个类型和前面声明的 type Item 中声明的u32绑定。
if self.0 == 0 { // 这里的self和Python的self有异曲同工之处都表示"当前对象实例"的引用,作用是检查是否倒计时结束
None
} else {
let val = self.0; // 访问self中的第一个字段
self.0 -= 1;
Some(val) //注意这边是一个表达式 说明整个函数的返回值之一就是这个Some(val),Some是Rust标准库中Option枚举的一个变体,Some的作用是不返回裸的u32类型,而是返回一个Option枚举类型。
}
}
// 优化:覆盖默认的 size_hint
fn size_hint(&self) -> (usize, Option<usize>) {// size_hint 是 Rust 迭代器协议中的一个重要方法,它为迭代器消费者提供了关于剩余元素数量的提示信息,返回一个元组,上限是Option<usize>,表示剩余元素最多的数量。下限是usize类型,保证剩余的最少元素数量
(self.0 as usize, Some(self.0 as usize))
}
}
fn main() {
let countdown = Countdown(3);// 创建一个从三开始倒数的迭代器
assert_eq!(countdown.collect::<Vec<_>>(), vec![3, 2, 1]); // collect() 是迭代器的消费方法,将迭代结果收集到集合中,::<Vec<_>> 是 turbofish 语法,显式指定收集为 Vec 类型_ 让编译器推断 Vec 的元素类型(这里是 u32),_ 让编译器推断 Vec 的元素类型(这里是 u32)
}