学习网站:
【官方文档】[https://doc.rust-lang.org/book/title-page.html]
【HOH共学营】[https://github.com/gpteth/lets-rust/blob/main/tutorial/02_memory_management.md]

开始

先搭建环境,太久远了,这里不赘述了,可去官网下载。
以下命令最好在GitBash里面执行。
创建新项目:

1
cargo new <hello-rust>

有可能:

1
cargo new --lib my_lib

编译并运行:

1
2
cargo build
cargo run

基础编程概念

关于变量

一旦将值绑定到名称,就无法更改该值。

1
2
3
4
5
6
7
fn main() {
let x = 5;
println!("The value of x is: {x}");
//错误!
x = 6;
println!("The value of x is: {x}");
}

可以添加mut关键字,表示“可变”。

1
2
3
4
5
6
fn main() {
let mut x = 5;
println!("The value of x is: {x}");
x = 6;
println!("The value of x is: {x}");
}

Rust 的常量声明使用const关键字,命名约定是全部大写。
Rust 允许使用相同名称的声明。

1
2
3
4
5
6
7
8
9
10
fn main() {
    let x = 5;
    let x = x + 1;  // 遮蔽了前面的 x

    {
        let x = x * 2;  // 在内部作用域中遮蔽
        println!("内部作用域中 x 的值是: {}", x);  // 输出 12
    }
    println!("外部作用域中 x 的值是: {}", x);  // 输出 6
}

数据类型

Rust 是一种静态类型语言,这意味着它必须在编译时知道所有变量的类型。

标量类型

标量类型表示单个值。Rust 有四种主要的标量类型:整数、浮点数、布尔值和字符。(整数类型默认为i32。)
Rust 的浮点类型是f32f64。(默认类型是 f64

1
2
3
4
5
fn main() {
let x = 2.0; // f64

let y: f32 = 3.0; // f32
}

如果你尝试将该变量更改为该范围之外的值,就会发生整数溢出。如果发生这种情况,程序会在运行时崩溃。

Rust 的char类型是该语言最原始的字母类型。它可以表示的内容远不止 ASCII。重音字母、中文、日文和韩文字符、表情符号以及零宽度空格char在 Rust 中都是有效值。

1
2
3
4
5
fn main() {
let c = 'z';
let z: char = 'ℤ'; // with explicit type annotation
let heart_eyed_cat = '😻';
}

单引号指定字面值,而不是使用双引号的字符串字面值。

复合类型

元组(Tuple)可以将多个不同类型的值组合在一起

1
2
3
4
5
6
7
8
9
10
11
12
fn main() {
let tup: (i32, f64, u8) = (500, 6.4, 1);

// 解构
let (x, y, z) = tup;
println!("y 的值是: {}", y);

// 使用点号访问
let five_hundred = tup.0;
let six_point_four = tup.1;
let one = tup.2;
}

数组

1
2
3
4
5
6
7
8
9
10
fn main(){
let a = [1,2,3,4,5];
//显式声明类型与长度
let a:[i32;5] = [1,2,3,4,5];
//创建相同值的数组
let a = [3,5];//等同于[3,3,3,3,3]
//访问元素
let first = a[0];
let second = a[1];
}

函数

Rust 是基于表达式的语言,理解语句和表达式的区别非常重要:

  • 语句:执行操作但不返回值
  • 表达式:计算并产生一个值

函数使用->指定返回类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
fn five() -> i32 {
5 // 没有分号,作为表达式返回
}

fn plus_one(x: i32) -> i32 {
x + 1 // 最后一个表达式作为返回值
/* x + 1; // 错误!这变成了语句 */
}

fn main() {
let x = five();
let y = plus_one(5);

println!("x = {}, y = {}", x, y);
}

程序控制流

1
2
3
4
5
6
7
8
9
10
11
12
13
fn main() {
let number = 6;

if number % 4 == 0 {
println!("number 能被 4 整除");
} else if number % 3 == 0 {
println!("number 能被 3 整除");
} else if number % 2 == 0 {
println!("number 能被 2 整除");
} else {
println!("number 不能被 4、3 或 2 整除");
}
}

在let语句中使用if:

1
2
3
4
5
6
7
8
9
10
11
12
fn main() {
let condition = true;
let number = if condition { 5 } else { 6 };
/*let number;
if condition {
number = 5;
} else {
number = 6;
} */

println!("number 的值是: {}", number); //5
}

代码块的计算结果取决于其中最后一个表达式,数字本身也是一种表达式。在这种情况下,整个if表达式的值取决于执行哪个代码块。这意味着每个分支可能返回的值必须是同一类型。

循环

Rust 有三种循环:loopwhilefor

loop关键字告诉Rust永远重复执行一个代码块,或者直到你明确告诉它停止。

1
2
3
4
5
6
7
8
9
10
11
12
13
fn main() {
let mut counter = 0;

let result = loop {
counter += 1;

if counter == 10 {
break counter * 2;//将counter*2赋给result
}
};

println!("The result is {result}");//20
}

双层循环:
'counting_up是外层循环的名称,可以自己取其他名字。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
fn main() {
let mut count = 0;
'counting_up: loop {
println!("count = {count}");
let mut remaining = 10;

loop {
println!("remaining = {remaining}");
if remaining == 9 {
break;
}
if count == 2 {
break 'counting_up;
}
remaining -= 1;
}

count += 1;
}
println!("End count = {count}");
}

运行结果:

1
2
3
4
5
6
7
8
9
count = 0  
remaining = 10
remaining = 9
count = 1
remaining = 10
remaining = 9
count = 2
remaining = 10
End count = 2
  • while循环
1
2
3
4
5
6
7
8
9
10
fn main() {
let mut number = 3;

while number != 0 {
println!("{}!", number);
number -= 1;
}

println!("发射!");
}
  • for循环
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
fn main() {
let a = [10, 20, 30, 40, 50];

// 遍历数组
for element in a {
println!("值是: {}", element);
}

// 使用范围(Range) rev()是一个rust内置的反转迭代器 前闭后开
for number in (1..4).rev() {
println!("{}!", number);
}
println!("发射!");

//这里要加.iter()   
for xixi in arr.iter().rev(){
        println!("反转迭代{xixi}");
    }
}

(1..4)创建了一个从 1 开始到 4 之前结束的范围(不包含 4);
.rev() 方法将这个范围反转,变成 3、2、1 的顺序;
for number in ... 是一个循环,依次取出范围中的每个数字并赋值给变量 number;
运行结果:

1
2
3
4
5
6
7
8
9
值是: 10
值是: 20
值是: 30
值是: 40
值是: 50
3!
2!
1!
发射!

程序与内存管理

程序的组成

一个 Rust 程序在运行时,内存主要分为以下几个区域:

  1. 代码段(Text Segment):存储程序的机器码
  2. 数据段(Data Segment):存储全局变量和静态变量
  3. 栈(Stack):存储函数调用信息和局部变量
  4. 堆(Heap):动态分配的内存区域
1
2
3
4
5
6
7
8
9
10
11
12
13
// 全局变量/静态变量 - 存储在数据段
static GLOBAL_COUNTER: i32 = 0;
const MAX_SIZE: usize = 100;

fn main() {
// 局部变量 - 存储在栈上
let x = 42;
let y = "hello";

// 动态分配 - 数据存储在堆上
let v = vec![1, 2, 3, 4, 5];
let s = String::from("world");
}

程序执行流程

  1. 程序启动:操作系统加载程序,分配内存空间
  2. 初始化:设置栈指针,初始化全局变量
  3. 执行 main 函数:程序从 main 函数开始执行
  4. 函数调用:通过栈来管理函数调用和返回
  5. 程序结束:清理资源,返回操作系统

栈与堆

栈(Stack)

栈是一种后进先出(LIFO)的数据结构,具有以下特点:

  • 快速:分配和释放都很快,只需移动栈指针
  • 有序:数据按照固定顺序存储
  • 大小限制:栈空间有限(通常几 MB)
  • 自动管理:函数结束时自动清理

堆(Heap)

堆是用于动态内存分配的区域,特点如下:

  • 灵活:可以存储大小可变的数据
  • 较慢:分配需要搜索可用空间
  • 无序:数据可能分散存储
  • 手动管理:需要明确的分配和释放(Rust 通过所有权系统自动管理)

对比示例

这里可以去看:关于所有权 -> 借用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
fn main() {
//栈上的数据
let x = 5; // 栈:5
let y = x; // 栈:复制值,y = 5

//堆上的数据
let s1 = String::from("hello"); // 栈:指针、长度、容量
// 堆:"hello"
let s2 = s1; // 移动:s1 不再有效

//使用clone进行深拷贝
let s3 = String::from("world");
let s4 = s3.clone(); // 堆上复制了数据

println!("s4 = {}", s4); // s3 和 s4 都有效
println!("s3 = {}", s3);
}

指针类型

引用是 Rust 中最常见的指针类型,使用&符号。

Rust中的原始指针(Raw Pointers) 是对标C/C++裸指针的底层特性,核心是绕开Rust的所有权/借用规则,直接操作内存地址 —— 但代价是失去Rust编译器的安全检查,属于 “不安全” 特性,仅用于底层开发、FFI(外部函数接口)或性能极致优化场景。

Rust 提供两种原始指针类型,语法上以*开头(区别于引用&):

指针类型 语法 含义 类比 C/C++
不可变原始指针 *const T 只读指针,不能修改指向的值 const T*
可变原始指针 *mut T 可写指针,能修改指向的值 T*
◆无所有权/借用检查:可同时存在多个*mut T指针指向同一内存,也可出现悬垂指针;
◆无自动解引用:需手动解引用(*ptr),且解引用必须在unsafe块中;
◆可空:原始指针可以指向null(Rust引用 &T 绝对不能为null)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
fn main() {
let mut num = 42;

// 1. 从引用创建原始指针(无需 unsafe)
let ptr_const: *const i32 = &num; // &T → *const T
let ptr_mut: *mut i32 = &mut num; // &mut T → *mut T

// 2. 解引用/修改原始指针(必须在 unsafe 块中)
unsafe {
// 读取不可变指针的值
println!("*ptr_const = {}", *ptr_const); // 输出 42
// 修改可变指针的值
*ptr_mut = 100;
println!("*ptr_mut = {}", *ptr_mut); // 输出 100
}
}

原始指针是不安全的,需要在unsafe块中使用。
代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
fn main() {
    let x = 5;
    let raw_ptr = &x as *const i32;  //不可变原始指针
    let mut y = 10;
    let raw_mut_ptr = &mut y as *mut i32;  //可变原始指针
    unsafe{
        println!("*raw_ptr = {}", *raw_ptr); //5
        *raw_mut_ptr = 20;
        println!("y = {}", y);  //20
        println!("*raw_mut_ptr = {}", *raw_mut_ptr);  //20
    }
}

关于所有权

概述

  1. 每个值在 Rust 中都有一个唯一的所有者(Owner);
  2. 所有者离开自己的作用域(Scope)时,这个值会被自动销毁(调用 drop 函数释放内存);
  3. 一个值同一时间,要么被多次只读借用(&T),要么被一次可变借用(&mut T)

所有者与所有域

作用域就是值的生命周期范围,所有者是持有这个值的变量,生命周期结束 → 所有者消失 → 值被销毁

1
2
3
4
5
6
fn main() {
//s为字符串"Hello, owner!"的所有者。
    let s = String::from("Hello, owner!");
    println!("{s}");//可以正常使用
}//main函数结束,s作用域消失,所有者也消失
//Rust自动调用drop函数,释放"Hello, owner!"占用的内存(无内存泄漏)

对比其他语言):
◆Java:靠GC后台扫描释放内存,运行期有开销;
◆C++:需手动写 delete,容易遗漏(内存泄漏)或重复删除(double free);
◆Rust:编译期绑定所有者-作用域,自动释放,无开销、无风险。

所有权的转移:
非copy值在赋值 / 传参时会转移所有权,原所有者会失效,无法再使用(避免 double free)。

那么哪些是非copy值?
◆非 Copy 类型(复杂值,栈存指针、堆存数据):String、Vec、自定义结构体等。
◆Copy 类型(简单值,存在栈上):整数(i32)、bool、浮点数、字符(char)等。

为什么要转移所有权???
因为像String这种数据存在堆上,栈上只存一个指向数据的指针。如果不转移所有权,s1和s2 会同时指向堆上的同一个数据。当s1、s2都出作用域时,会两次调用drop→重复释放堆数据(double free,内存安全漏洞)。所以Rust用「转移所有权」来避免这个问题。同一时间,只有一个所有者能操作堆数据

1
2
3
4
5
6
7
8
9
10
11
12
fn main() {
    //所有权的转移:非copy类型
    let s1 = String::from("Hello, owner!");
    let s2 = s1;
    //println!("{s}"); //报错!
    println!("{s2}"); //Hello, owner!

    //所有权的转移:copy类型
    let x =10;
    let y = x;
    println!("{x}, {y}"); //10, 10
}

借用

如果我们想使用某个值,但不想拿走它的所有权(比如函数传值,用完还能自己用),这是就需要借用—— 本质是临时引用

分类:
(1)不可变借用(&T):可以有多个。(多人同时读,不冲突)
(2)可变借用(&mut T):可以修改值,但是!只能有一个可变借用。(避免多人同时修改,数据混乱)

代码示例1:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
fn main() {
//不可变借用
    let s1 = String::from("Hello, Borrow1!");
    let s2 = &s1;
    let s3 = &s1;
    println!("{}, {}", s2, s3);//Hello, Borrow1!, Hello, Borrow1!

    let mut t1 = String::from("Hello, Borrow2!");
    let t2 = &mut t1;
    //报错!只能一个可变借用。
    //let t3 = &mut t1;
    // println!("{}, {}", t2, t3);
    println!("{}", t2);//Hello, Borrow2!
}

代码示例2:函数传参。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//非Copy类型直接传参 → 转移所有权
fn take_ownership(s: String) { //s成为传入值的新所有者
    println!("{}", s);
} //s出作用域,值被销毁

//传参借用(不转移所有权)
fn borrow_ownership(s: &String) { //s是引用,临时借用
    println!("{}", s);
} //借用结束,引用失效,值不会被销毁

//返回非Copy类型 → 转移所有权
fn return_ownership() -> String {
    let s = String::from("hello");
    s //返回s,所有权转移给调用者
}

fn main() {
    let s1 = String::from("hello");
    take_ownership(s1);
    //println!("{}", s1); //报错,s1不再有效

    let s2 = String::from("world");
    borrow_ownership(&s2);
    println!("{}", s2); //world

    let s3 = return_ownership();
    println!("{}", s3); //hello
}

补充

1
2
let s1 = String::from("hello");
let s2 = s1.clone(); // 手动复制堆数据,s1和s2各自拥有所有权,都能使用。

上面这个方法中s1、s2的引用相同吗?
不相同。
String的内存结构:栈上有指向堆数据的指针、字符串长度、容量。而实际的字符串内容存储在堆上。
clone() 的不仅复制栈上的指针、长度、容量(浅拷贝部分),还会在堆上重新分配一块内存,复制原字符串的内容(深拷贝核心)

切片

Rust 中的切片(Slice) 是一种无所有权的引用类型,创建切片不会转移 / 剥夺原集合的所有权,核心作用是 “指向集合(如数组、String、Vec)中连续的一段元素”,让你无需拷贝数据就能操作部分内容 —— 可以把它理解为 “带长度的引用”,而非完整的容器。

代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
fn main() {
    // 原数组(拥有所有权)
    let arr = [1, 2, 3, 4, 5];
    // 创建切片:&arr[起始索引..结束索引](左闭右开)
    let slice1 = &arr[1..4];
    let slice2 = &arr[..3];  
    let slice3 = &arr[2..];
    let slice4 = &arr[..];

    println!("slice1: {:?}", slice1);
    println!("切片长度:{}", slice1.len());
    println!("slice2: {:?}", slice2);
    println!("slice4: {:?}", slice4);

    println!("数组元素访问:arr[2] = {}", arr[2]);
}

运行结果:

1
2
3
4
5
slice1: [2, 3, 4]
切片长度:3
slice2: [1, 2, 3]
slice4: [1, 2, 3, 4, 5]
数组元素访问:arr[2] = 3

切片不就是借用吗?
借用是 “如何安全使用别人的数据” 的通用规则(比如 “不可变借用只读、可变借用独占”),所有引用(&T/&mut T)都必须遵守;
切片是 “针对连续元素的引用” 这个具体类型,它只是 “借用规则” 的一个应用场景—— 切片必然是借用,但借用不一定是切片。

悬垂引用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 这个函数试图返回悬垂引用
// fn dangle() -> &String { // 错误!
// let s = String::from("hello");
// &s // 返回s的引用
// } //s离开作用域并被丢弃,其内存被释放

// 正确的做法:返回 String
fn no_dangle() -> String {
let s = String::from("hello");
s // 所有权被移出
}

fn main() {
let string = no_dangle();
println!("{}", string);
}

结构体

自定义复合数据类型,将多个不同类型的数据组合成一个整体,用于封装有逻辑关联的数据。与前面的元组很像,不同的是结构体一般为每个数据项命名,比元组更灵活,无需依赖数据的顺序来指定或访问实例的值

关键字:struct

结构体的三种类型:
单元结构体。
元组结构体:元组结构体有结构体名称,但没有具体的字段名。
具体字段结构体。

代码示例:创建一个简单的结构体。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//定义一个结构体
struct User {
    active: bool,
    username: String,
    email: String,
    sign_in_count: u64,
}
//定义一个函数用来初始化结构体
fn build_user(email: String, username: String) -> User {
    User {
        email,      // 字段初始化简写
        username,   // 当变量名与字段名相同时
        active: true,
        sign_in_count: 1,
    }
}
//主函数
fn main() {
//创建一个结构体并初始化
    let user = build_user(
        String::from("sut@gmail.com"),
        String::from("March")
    );
    println!("{} 已创建", user.username);
    println!("邮箱: {}", user.email);
}

运行结果:

1
2
March 已创建
邮箱: sut@gmail.com

基于一个结构体实例创建新实例时,可通过 .. 复用原有字段,仅修改需要变更的部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
struct User {
    username: String,
    age: u8,
    is_active: bool,
}

fn main() {
    let user1 = User {
        username: String::from("bob"),
        age: 28,
        is_active: true,
    };

    // 基于user1创建user2,仅修改username,复用age和is_active
    let user2 = User {
        username: String::from("charlie"),
        ..user1 //这个必须放最后,复用剩余字段
    };

    println!("User2: {}, {}, {}", user2.username, user2.age, user2.is_active); //User2: charlie, 28, true
}

self 是Rust的关键字,代表调用该方法的结构体实例本身
impl 是implementation的缩写,核心作用是为某个类型(如结构体、枚举、基础类型等)绑定方法、关联函数,或实现特质,简单说,impl 是 “给类型添加功能” 的语法块

代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
struct Rectangle {
width: u32,
height: u32,
}

//为Rectangle实现方法
impl Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}

fn set_width(&mut self, new_width: u32) {
self.width = new_width;
}
}

fn main() {
let mut rect = Rectangle { width: 10, height: 20 };
println!("面积:{}", rect.area()); // 调用方法:rect.area()
rect.set_width(15);
println!("修改后面积:{}", rect.area());
}

如何打印结构体?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//这里!让结构体支持调试打印
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}

fn main() {
let rect = Rectangle {
width: 30,
height: 50,
};

// 使用 {:?} 打印
println!("rect is {:?}", rect);

// 使用 {:#?} 美化打印
println!("rect is {:#?}", rect);

// 使用 dbg! 宏
dbg!(&rect);

let scale = 2;
let rect2 = Rectangle {
width: dbg!(30 * scale), // dbg! 返回表达式的值
height: 50,
};
}

运行结果:

1
2
3
4
5
6
7
8
9
10
rect is Rectangle { width: 30, height: 50 }
rect is Rectangle {
width: 30,
height: 50,
}
[src\main.rs:21:5] &rect = Rectangle {
width: 30,
height: 50,
}
[src\main.rs:25:16] 30 * scale = 60

dbg!(&rect) 是Rust中用于调试打印的内置宏,核心作用是 “输出变量的详细信息(值 + 代码位置),且不获取变量所有权”。

枚举

基础

上代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//定义枚举:表示“交通灯”的所有可能状态
enum TrafficLight {
Red, // 红灯
Yellow, // 黄灯
Green, // 绿灯
}

fn main() {
let light = TrafficLight::Red; //实例化枚举(通过"枚举名::变体名"访问)

//匹配枚举:用match覆盖所有可能!
match light {
TrafficLight::Red => println!("红灯停"),
TrafficLight::Yellow => println!("黄灯警示"),
TrafficLight::Green => println!("绿灯行"),
}
}

运行结果:红灯停

携带数据

Rust的枚举的一个很大的特点是可以携带不同类型、不同数量的数据,甚至是结构体 / 另一个枚举。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 先定义一个结构体
struct Point(i32, i32);

// 枚举变体携带结构体或另一个枚举
enum Shape {
    Circle(Point, f64), // 携带 Point 结构体 + 半径(f64)
    Rectangle(Point, Point), // 携带两个 Point 结构体(对角顶点)
}

fn main() {
    let circle = Shape::Circle(Point(5, 5), 3.0); // 圆形:中心(5,5),半径3.0
    let rect = Shape::Rectangle(Point(0, 0), Point(10, 20)); // 矩形:对角(0,0)和(10,20)

    match circle {
        Shape::Circle(Point(x, y), r) => println!("圆形:中心({},{}),半径{}", x, y, r),
        Shape::Rectangle(Point(x1, y1), Point(x2, y2)) => println!("矩形:({},{})-({},{})", x1, y1, x2, y2),
    }
    match rect {
        Shape::Circle(Point(x, y), r) => println!("圆形:中心({},{}),半径{}", x, y, r),
        Shape::Rectangle(Point(x1, y1), Point(x2, y2)) => println!("矩形:({},{})-({},{})", x1, y1, x2, y2),
    }
}

和结构体一样,枚举也能通过impl块添加方法。

Option

Rust里面没有null,但有一个枚举类型可以表示值存在或不存在。

1
2
3
4
enum Option<T> {
None, //表示“无值”(其他语言是null)
Some(T), //表示“有值”,T是泛型
}

Option<T>有什么用?显式表达 “值可能缺失” 的场景

Option<T> 是Rust预导入的(不用手动 use)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
fn main() {
let maybe_num: Option<i32> = Some(5);

// match 必须匹配 None 和 Some(T) 所有变体,否则编译报错
match maybe_num {
Some(num) => println!("有值:{},加 10 等于 {}", num, num + 10), // 提取值到 num
None => println!("无值"), // 必须处理 None
}

// 测试无值的情况
let no_num: Option<i32> = None;
match no_num {
Some(num) => println!("有值:{}", num),
None => println!("确实没值"), // 不写这行编译报错
}
}

简化版:

1
2
3
4
5
6
7
8
9
10
11
fn main() {
let maybe_str = Some(String::from("hello"));

// 只处理 Some 情况,忽略 None(但仍显式承认“可能无值”)
if let Some(s) = maybe_str {
println!("提取到字符串:{}", s);
} else {
// 可选:处理 None(不写 else 也可以)
println!("无字符串");
}
}

代码实现:写一个“除法函数”,避免除以0崩溃。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
fn divide(a: i32, b: i32) -> Option<i32>{
    if b == 0 {
        None
    } else {
        Some(a / b)
    }
}

fn main(){
    let result1 = divide(8, 8);
    let result2 = divide(9, 0);
    match result1 {
        Some(num) => println!("Result1: {}", num),
        None => println!("Error: Division by zero in result1"),
    }
    match result2 {
        Some(num) => println!("Result2: {}", num),
        None => println!("Error: Division by zero in result2"),
    }
}

运行结果:

1
2
Result1: 1
Error: Division by zero in result2

常用类

::

:: 被称为 “路径解析运算符”,核心作用是 “访问某个命名空间下的项(类型、函数、常量等)。是 Rust 通用的语法规则,可以类比其他语言的 .

符号 作用 适用场景 示例
:: 路径解析,访问 “类型 / 模块” 的项 关联函数、枚举变体、常量、模块 Vec::new()Option::Some(5)
. 访问 “实例” 的方法 / 字段 实例方法、结构体字段 v.push(1)rect.width

调用类型的关联函数:关联函数是属于类型本身,而非类型实例的函数(不需要先创建实例就能调用),必须用 类型::函数名() 调用。
例如Vec::new()new() 是 Vec 类型的关联函数。String::from("hello")from() 是 String 类型的关联函数。

1
2
3
4
5
let s = String::from("hello"); // :: 调用关联函数创建实例
s.len(); // . 调用实例方法(len() 属于 s 这个 String 实例)

let v = Vec::new(); // :: 调用关联函数创建实例
v.push(1); // . 调用实例方法(push() 属于 v 这个 Vec 实例)

访问模块/命名空间下的项:Rust 用模块组织代码,:: 用于从模块中取出类型 / 函数 / 常量。

1
2
3
4
5
//访问标准库std中fs模块下的File类型
use std::fs::File;

//直接调用std中io模块下的函数(不用use导入时)
let result = std::io::read_to_string("file.txt");

访问枚举的变体:枚举的变体也需要用 :: 访问,因为变体属于枚举类型的命名空间。

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

let x: Option<i32> = Option::Some(5); // :: 访问枚举变体
let y = Option::None;

访问常量:类型/模块下的常量也通过 :: 访问。

1
2
3
// 访问 std 中 i32 类型的最大值常量
let max_i32 = i32::MAX;
println!("i32 最大值:{}", max_i32); // 输出 2147483647

Vector

概述

Rust 中的 Vector(简称 Vec<T>)是动态可变的连续内存数组,也是 Rust 最核心的集合类型 —— 可以把它理解为 “能自动扩容的数组”,支持在运行时添加 / 删除元素,且严格遵循 Rust 的所有权规则,保证内存安全。

动态长度:编译时无需确定长度,运行时可按需扩容(区别于固定长度的数组 [T; N]);
连续内存:元素在堆上连续存储,访问速度快(随机访问时间复杂度 O (1));
所有权管理Vec<T> 拥有其所有元素的所有权,销毁时会自动释放所有元素的内存;
泛型支持Vec<T> 是泛型类型,<T> 可以是任意 Rust 类型(如 i32String、自定义结构体等),但一个 Vec 只能存储同类型元素。

创建Vector:

1
2
3
4
5
6
7
8
9
10
11
```rust
fn main() {
//方式1:空Vec + 类型标注(后续添加元素)
let mut v1: Vec<i32> = Vec::new();

//方式2:vec!宏(最常用,直接初始化元素)
let v2 = vec![1, 2, 3, 4]; // 自动推断为Vec<i32>,但底层还是调用了Vec::new()

//方式3:创建指定长度+默认值的Vec
let v3 = vec![0; 5]; // [0, 0, 0, 0, 0],Vec<i32>
}

基本方法