Rust基础之变量和组织管理

1 变量

1.1 变量

1.1.1 简介

Rust强类型语言,具有自动判断变量类型的能力,声明变量需要使用 let 关键字,Rust 中的变量是不可变的,除非使用 mut 关键字声明为可变变量,

let a = 123;       // 不可变变量
let mut b = 10;  // 可变变量
b = 345; // b 重新赋值

1.1.2 常量&重影&不可变变量

变量和常量区别:变量的值可以重新绑定,但在重新绑定以前不能私自被改变,这样可以确保在每一次绑定之后的区域里编译器可以充分的推理程序逻辑,而常量声明了就不能重新绑定

重影:指变量的名称可以被重新使用的机制
重影可变变量的赋值不是一个概念,重影是指用同一个名字重新代表另一个变量实体,其类型可变属性都可以变化,但可变变量赋值仅能发生值变化
Rust 中,以下程序是合法的:

let a = 123;   // 可以编译,但可能有警告,因为该变量没有被使用
let a = 456;  // 重影

但是如果 a 是常量就不合法:

const a: i32 = 123;
let a = 456;

static:静态变量,有固定内存地址,程序全程存活

const vs static 的核心区别

const static
有没有内存地址 没有,编译时直接内联到用到的地方 有,整个程序共享同一份
生命周期 'static,活到程序结束
能否可变 不能 可以,但要 mut + unsafe
适合场景 数字常量、标志位 全局配置、单例、计数器

1.1.3 下划线开头变量名

Rust 中,变量名以下划线 _ 开头有特殊含义

  • 抑制未使用变量警告,编译器看到 _ 开头的变量名,就知道是故意不用,不会报 warning
  • 完全丢弃值(_ 单独使用),比如:
    let result = some_function(); 返回 (i32, String)
    let (x, _) = result;只关心第一个值,第二个丢弃
    let (_, y) = result; 只关心第二个值

常见场景

// 解构时忽略某些字段
let Point { x, _y } = point;   // 只用 x,忽略 y
// 匹配时忽略分支
match opt {
    Some(v) => println!("{}", v),
    _ => {}                    // 忽略其他所有情况
}
// 迭代器消费但不用值
for _ in 0..10 {               // 循环 10 次,不需要索引
    println!("hello");
}

1.2 数据类型

1.2.1 整数型

整数型简称整型,按照比特位长度和有无符号分为以下种类:

位长度 有符号 无符号
8-bit i8 u8
16-bit i16 u16
32-bit i32 u32
64-bit i64 u64
128-bit i128 u128
arch isize usize

isizeusize 两种整数类型是用来衡量数据大小的,它们的位长度取决于所运行的目标平台,如果是 32 位架构的处理器将使用 32 位位长度整型。

1.2.2 字符和字符串

1.2.2.1 字符型

字符型用 char 表示,Rustchar 类型大小为 4 个字节,代表 Unicode 标量值,这意味着它可以支持中文,日文和韩文字符等非英文字符甚至表情符号和零宽度空格在 Rust 中都是有效的 char

1.2.2.2 字符串

String&str 是两种完全不同的类型

String &str
本质 拥有所有权的字符串(堆分配) 字符串的引用(借用)
类比 Java 一个可变的 StringBuilder对象 指向字符串的指针
是否可变 可变(mut) 不可变,只能读
内存 堆上,自己管理容量 只是一块已有内存的视图

注意:字符串字面量默认就是 &str

let s = "hello"; // s的类型是 &'static str

两者可以互转,也容易混淆
String 可以自动转成 &str(deref coercion),很多地方两者混用没问题;但反过来不行,&str不能隐式变 String,需要调用 .to_string() 方法

let owned: String = String::from("abc");
let borrowed: &str = &owned; // String → &str:取引用即可(自动 deref)

let s: &str = "abc";
let owned2: String = s.to_string(); // &str → String:要复制一份数据

1.2.3 复合类型

元组(Tuple)和数组(Array)是 Rust 语言底层原生支持的复合类型,不需要标准库

元组是用一对 ( ) 包括的一组数据,可以包含不同种类的数据,数组用一对 [ ] 包括的同类型数据。

特性 元组 tuple 数组 array
长度 固定 固定
元素类型 可以不同 (i32, &str, bool) 必须相同 [i32; 3] (长度为 3 的 i32 数组)
访问方式 t.0, t.1(索引) a[0], a[1](索引)
用途 异构数据组合(如函数返回多个值) 同类型数据批量存储
内存 各元素可能大小不同 连续内存,元素等大小

附:数组有重复初始化语法:[值; 次数],比如[3; 5]=[3, 3, 3, 3, 3]
示例

// 元组:不同类型
let person: (i32, &str, bool) = (25, "Alice", true);
println!("年龄: {}", person.0);   // 25

// 数组:同类型
let scores: [i32; 3] = [85, 90, 78];
println!("第一个: {}", scores[0]); // 85

let c: [i32; 5] = [1, 2, 3, 4, 5];
// c 是一个长度为 5 的 i32 数组

let d = [3; 5];
// 等同于 let d = [3, 3, 3, 3, 3];

1.3 循环

像其他语言中 类似 for 循环的三元语句控制循环,Rust 中没有这种用法,需要用 while 循环来代替,for一般用来遍历数组,示例:

fn main() {
    let mut number = 1;
    while number != 4 {
        println!("{}", number);
        number += 1;
    }
    println!("EXIT");
}

遍历示例

let a = [10, 20, 30, 40, 50];
for i in a.iter() {
    println!("值为 : {}", i);
}
或
for i in 0..5 {
 	println!("a[{}] = {}", i, a[i]);
}

loop循环类似 while (true) 需要通过 break 跳出循环,loop可以返回值,break 后面跟的值就是整个 loop 表达式的返回值。

fn main() {
    let mut counter = 0;   
    let result = loop {
        counter += 1;    
        if counter == 10 {
            break counter * 2;   // 返回 20
        }
    };
    println!("{}", result);  // 20
}

1.4 迭代器

1.4.1 简介

Rust 的迭代器是惰性求值的,这意味着迭代器本身不会立即执行操作,而是在需要时才会产生值。迭代器允许以一种声明式的方式来遍历序列,如数组、切片、链表等集合类型的元素。迭代器背后的核心思想是将数据处理过程数据本身分离,使代码更清晰、更易读、更易维护。
Rust 中,迭代器通过实现 Iterator trait 来定义。
最基本的 trait 方法是 next,用于逐一返回迭代器中的下一个元素,直到返回 None 表示结束。

所有迭代器必须实现的方法

pub trait Iterator {
    type Item; // 关联类型:每次迭代返回的元素类型
    fn next(&mut self) -> Option<Self::Item>; // 核心方法:取下一个元素
    // 其他默认实现的方法如 map, filter 等。
}

迭代器特点:

  • 惰性求值 (Laziness):迭代器是惰性的,意味着迭代器本身不会立即进行任何计算或操作,直到显式地请求数据
  • 所有权和借用检查 (Ownership and Borrowing Checks):迭代器严格遵守所有权和借用规则,避免数据竞争和内存错误。
  • 链式调用 (Chaining):迭代器支持链式调用,即可以将多个迭代器方法链接在一起进行组合操作,这使得代码简洁且具有高度可读性
  • 高效内存管理 (Efficient Memory Management):迭代器避免了不必要的内存分配,因为大多数操作都是惰性求值的,并且在使用时直接进行遍历操作,对于处理大数据集合尤其重要
  • 抽象和通用性 (Abstraction and Generality):通过 Iterator trait 实现抽象和通用性。任何实现了 Iterator trait 的类型都可以在不同的上下文中作为迭代器使用

1.4.2 创建迭代器

常见方式是通过集合的 .iter().iter_mut().into_iter() 方法来创建迭代器:

  • .iter():返回集合的不可变引用迭代器。
  • .iter_mut():返回集合的可变引用迭代器。
  • .into_iter():将集合转移所有权并生成值迭代器。
let v = vec![1, 2, 3];
// into_iter: 拿走所有权
for i in v.into_iter() {
    println!("{}", i);  // i 是 i32
}
// println!("{:?}", v);  // 报错!v 已经被 move 了
// iter: 借用
for i in v.iter() {
    println!("{}", i);  // i 是 &i32
}
println!("{:?}", v);  // v 还能用
// iter_mut: 可变借用
for i in v.iter_mut() {
    *i += 1;  // 可以修改
}
println!("{:?}", v);  // [2, 3, 4]

1.4.3 自定义迭代器

为自己的类型实现 Iterator trait,只需定义 next() 方法即可。
例如,实现一个从 1 到 5 的简单迭代器:

struct Counter {
    count: usize,
}
impl Counter {
    fn new() -> Counter {
        Counter { count: 0 }
    }
}

impl Iterator for Counter {
    type Item = usize; 
    fn next(&mut self) -> Option<Self::Item> {
        self.count += 1;
        if self.count <= 5 {
            Some(self.count)
        } else {
            None
        }
    }
}

let mut counter = Counter::new();
while let Some(num) = counter.next() {
    println!("{}", num);  // 输出 1 到 5
}

2 组织管理

2.1 简介

2.1.1 箱包模块区别

Rust 中有三个重要的组织概念:模块

概念 英文 说明
Package 由 Cargo 管理,包含一个 Cargo.toml
Crate 编译单元,分二进制和库两种
模块 Module 代码组织单位,控制可见性

2.1.2 包

cargo new my_project        # 创建二进制包
cargo new --lib my_lib    # 创建库包

结构:

my_project/          ← 包(Package)
├── Cargo.toml       ← 包配置文件
└── src/
    ├── main.rs      ← 二进制 crate 根(默认入口)
    └── lib.rs       ← 库 crate 根(如果有)

规则:

  • 一个包最多 1 个 库 crate,比如:src/lib.rs
  • 可以有 多个二进制 crate(放 src/bin/ 目录)
  • 至少要有 1 个 crate

2.1.3 箱

箱的分类:

  • 二进制crate:可执行程序,一般是.exe二进制文件
  • 库 crate:供他人引用,比如.rlib 文件
cargo run     # 运行二进制 crate
cargo build   # 编译
cargo test    # 运行测试

2.2 模块

2.2.1 定义模块

mod nation {
    pub mod government {
        pub fn govern() {
            println!("执政");
        }
    }    
    mod congress {
        pub fn legislate() {
            println!("立法");
        }
    }
}

2.2.2 路径

在文件系统中,目录结构往往以斜杠在路径字符串中表示对象的位置,Rust 中的路径分隔符是 ::,路径分为绝对路径和相对路径。绝对路径从 crate 关键字开始描述。相对路径从 selfsuper 关键字或一个标识符开始描述
路径类型:

  • 绝对路径:从 crate 根开始,比如:crate::nation::government::govern()
  • 相对路径: 从当前模块开始,self::指引用当前模块内的东西,super::指引用上一级模块的东西
mod nation {
    pub mod government {
        pub fn govern() {}
    }   
    mod court {
        fn judicial() {
            // 用 super 访问父模块
            super::government::govern();
        }
    }
}
fn main() { // 绝对路径
    crate::nation::government::govern();
}

2.2.3 文件作为模块

文件 src/second_module.rs 自动对应模块 second_module:

// src/second_module.rs
pub fn message() -> String {
    String::from("这是第二个模块")
}
// src/main.rs
mod second_module;  // 声明模块,Rust 自动找 second_module.rs

fn main() {
    println!("{}", second_module::message());
}

目录作为模块:

src/
├── main.rs
└── nation/
    ├── mod.rs          ← 模块入口
    ├── government.rs
    └── congress.rs
// src/nation/mod.rs
pub mod government;
pub mod congress;

2.2.4 lib.rs(库 Crate的根 )

属性 说明
位置 src/lib.rs
作用 定义库 crate 的入口
特殊含义 告诉 Cargo:这个包是库(或同时有二进制
能否改名 不能,Rust/Cargo 硬编码
数量 一个包只能有一个 lib.rs

示例:

my_project/
├── Cargo.toml
└── src/
    ├── lib.rs      ← 库 crate 根,从这里开始编译库
    └── main.rs     ← 可选,二进制 crate 根

2.2.5 mod.rs(模块目录入口)

属性 说明
位置 src/xxx/mod.rs(模块目录内)
作用 定义模块目录的入口
特殊含义 告诉 Rust:xxx/ 目录是一个模块,代码从这里开始
能否改 不能,必须是 mod.rs
数量 每个模块目录 1 个
src/
├── lib.rs          ← 库根
└── nation/         ← nation 模块目录
    ├── mod.rs      ← nation 模块入口
    ├── government.rs
    └── congress.rs

mod.rs 里声明子模块:

// src/nation/mod.rs
pub mod government;
pub mod congress;

lib.rs 里的声明

pub mod nation;

新模式声明
Rust 2018 后,模块文件有两种组织方式:

方式 文件结构 说明
传统 mod.rs src/nation/mod.rs + src/nation/*.rs 模块入口在目录内
新方式 同名文件 src/nation.rs + src/nation/*.rs 模块入口和目录同名
// 传统方式
src/
└── nation/
    ├── mod.rs          ← 入口
    └── government.rs

// 新方式(Rust 2018+)
src/
└── nation.rs           ← 入口
└── nation/
    └── government.rs

2.3 访问权限

Rust 中有两种简单的访问权:公共(public)和私有(private),默认情况下,如果不加修饰符,模块中的成员访问权将是私有的,如果想使用公共权限,需要使用 pub 关键字。

对于结构体和枚举可见性

mod back_of_house {
    // 结构体本身要 pub,字段默认私有
    pub struct Breakfast {
        pub toast: String,      // 公开字段
        seasonal_fruit: String, // 私有字段
    }   
    impl Breakfast {
        pub fn summer(toast: &str) -> Breakfast {
            Breakfast {
                toast: String::from(toast),
                seasonal_fruit: String::from("桃子"),
            }
        }
    }  // 枚举:pub 后所有变体自动公开
    pub enum Appetizer {
        Soup,
        Salad,
    }
}

2.4 use和mod关键字

2.4.1 定义

mod:声明/引入模块,use:给路径起别名,mod 是前提,use 是便利,mod 让模块"存在",use 让它"好用"

核心区别

mod use
作用 声明一个子模块(或引入外部 crate 的模块) 把一个长路径绑定到短名字
类比 在文件系统里"创建一个子目录" 给文件创建"快捷方式"
是否产生新结构 是,扩展了模块树 否,只是别名,不改变模块树
是否必须 想用某子模块,必须先 mod 声明它 不用 use 也能写全路径,只是繁琐

usepub use 区别:

  • use:引入到当前模块,仅自己用。
  • pub use:引入到当前模块,同时对外公开,别人也能从这个新路径用。

注意:如果是写的代码需要 mod,外部 crate通过Cargo.toml引入后直接use即可,系统自带的也是直接use,如果是写的代码,需要告诉编译器在哪里找到并编译它,若不写 mod`,那个文件就根本不会被编译进项目,编译器完全不知道它存在

示例分析

src/
├── main.rs          // crate 根
├── models.rs        // models模块
└── utils.rs         // utils 模块

// main.rs
mod models; // 声明:有一个子模块叫 models
mod utils;      // 声明:有一个子模块叫 utils

fn main() {
    // 不写 mod models; 的话,下面这行会报错"找不到 models"
    let u = models::User::new();
}

mod models; 做了两件事:

  • 告诉编译器:"存在一个子模块 models"
  • 编译器会去找对应的文件(models.rs 或 models/mod.rs)并编译它
  • 不写 mod,模块就不存在,编译器根本不会去编译那个文件。 这是 mod 的关键职责。

use 不会让模块"出现",它只已经能用全路径访问到某个东西的前提下,把它绑定到一个短名字上

2.4.2 例子分析

引入路径,简化调用:

mod nation {
    pub mod government {
        pub fn govern() {}
    }
}
// 引入
use crate::nation::government::govern;
fn main() {
    govern();  // 直接用,不用写完整路径
}

别名(as):

use crate::nation::government::govern as gov;

fn main() {
    gov();
}

重新导出(pub use):

mod nation {
    pub mod government {
        pub fn govern() {}
    }
    // 把 government::govern 提升到 nation 层级
    pub use government::govern;
}

fn main() {
    nation::govern();  // 直接访问,不用 nation::government::govern()
}
posted @ 2026-08-01 13:46  上善若泪  阅读(6)  评论(0)    收藏  举报