3615 字
18 分钟
rust学习笔记
2025-07-04

说在前面#

  做了一个长期学习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-biti8u8
16-biti16u16
32-biti32u32
64-biti64u64
128-biti128u128
archisizeusize

浮点数型(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)#

  值只能truefalse。这个只能就表示{-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 Item
  • fn 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)
}
rust学习笔记
https://lixxxuan.github.io/posts/rust学习笔记/
作者
Lixxxuan
发布于
2025-07-04
许可协议
CC BY-NC-SA 4.0