Rust学习 - 02 基础语法 变量 数据类型 宏 Cargo命令 类型转换
Rust 基础语法 vs C/C++ 对比笔记
对应教材:TRPL Ch1-Ch8 / Programming Rust Ch3, Ch6, Ch9, Ch10
最后更新:2026-03-13
一、变量与类型
1.1 变量声明——默认不可变
let x = 5; // 不可变,类似 C++ 的 const int x = 5;
let mut y = 10; // 可变,类似 C++ 的 int y = 10;
| C/C++ | Rust | |
|---|---|---|
| 默认 | 可变 | 不可变 |
| 要不可变 | 加 const |
默认就是 |
| 要可变 | 默认就是 | 加 mut |
为什么? 默认不可变 = 编译器帮你防止意外修改。写驱动时尤其重要——不想共享变量被随便改。
1.2 Shadowing(变量遮蔽)
let x = 5;
let x = x + 1; // 重新 let,创建新变量,可以换类型
let x = "hello"; // ✅ 合法
let mut x = 5;
x = "hello"; // ❌ mut 只能改值,不能换类型
主要用在类型转换场景,避免取一堆烂名字:
let input = "42";
let input = input.parse::<i32>().unwrap(); // 同名,string → int
1.3 基本数据类型
整数类型(大小明确,不像 C 的 int 可能 2 或 4 字节):
| Rust | C/C++ | 大小 |
|---|---|---|
i8 / u8 |
char / unsigned char |
1 字节 |
i16 / u16 |
short / unsigned short |
2 字节 |
i32 / u32 |
int / unsigned int |
4 字节 |
i64 / u64 |
long long / unsigned long long |
8 字节 |
isize / usize |
intptr_t / size_t |
指针大小(64位=8字节) |
usize 写驱动常用——指针运算、数组索引都用它,等价于 SIZE_T。
其他类型:
| Rust | C/C++ | 说明 |
|---|---|---|
f32 / f64 |
float / double |
默认推导为 f64 |
bool |
bool |
1 字节 |
char |
— | 4 字节 Unicode,不是 C 的 1 字节 ASCII |
1.4 类型转换——必须显式
let x: i32 = 5;
let y: i64 = x; // ❌ 编译错误!不能隐式转换
let y: i64 = x as i64; // ✅ 必须用 as 显式转换
as vs try_into():
let x: u64 = 0x1FFFFFFFF;
let a: u32 = x as u32; // 直接截断(跟 C 强转一样)
let b: Result<u32, _> = x.try_into(); // 安全检查,失败返回 Err
| 方式 | 行为 | 类比 C++ |
|---|---|---|
as |
直接转,可能截断 | (int)x 强转 |
try_into() |
安全检查,失败返回 Err |
无直接对应 |
as 转换细节:
// 大转小:截断低位
let big: i32 = 256;
let byte = big as u8; // 0
// 浮点转整数:向 0 舍入
let f = -1.99_f64;
let i = f as i32; // -1(不是 -2)
// 超范围:饱和到边界值
let huge = 1e6_f64;
let b = huge as u8; // 255
好处: 写驱动时 ULONG 和 ULONG_PTR 混用在 C 里不报错,在 Rust 里编译器直接拦住你。
二、函数与表达式
2.1 函数
fn add(a: i32, b: i32) -> i32 {
a + b // 没分号 = 返回值(表达式)
}
| C/C++ | Rust | |
|---|---|---|
| 关键字 | 返回类型前置 | fn + -> 返回类型 后置 |
| 返回值 | return x; |
最后一行不加分号(也可以用 return) |
| main 返回 | int(必须) |
()(默认)或 Result |
2.2 一切皆表达式——Rust 和 C 最大的思维差异
Rust 是表达式语言。{} 本身就是表达式,能产生值。
核心规则:没分号 = 返回值,有分号 = 丢掉值返回 ()。
let msg = {
let x = 5;
let y = 10;
x + y // 没分号 → 块返回 15
};
let msg2 = {
x + 1; // 有分号 → 值被丢弃,返回 ()
};
C/C++ 里 {} 只是作用域分隔符。Rust 把 {} 升级成了表达式——函数返回值、if、match、loop 全部是这条规则的推论。
2.3 控制流——全部是表达式
if 表达式(替代三元运算符):
let status = if temp > 100 { "过热" } else { "正常" };
// Rust 没有三元运算符 ?:,if else 就是替代品
// 所有分支返回类型必须一致!
match 表达式(替代 switch,无 fall-through):
let desc = match code {
0 => "成功",
1 => "文件未找到",
_ => "未知错误", // _ 类似 default,必须有
};
循环——三种:
// 1. loop —— 无限循环,break 可以带值!(C 做不到)
let result = loop {
if count == 5 { break count * 2; }
count += 1;
};
// 2. while —— 跟 C 一样
while n > 0 { n -= 1; }
// 3. for —— 范围遍历(最常用)
for i in 0..5 { } // 0,1,2,3,4(不含 5)
for i in 0..=5 { } // 0,1,2,3,4,5(含 5)
带标签跳出多层循环(C 要用 goto):
let answer = 'outer: loop {
for i in 1..100 {
if i * i == 49 { break 'outer i; } // 跳出外层,返回 7
}
};
注意:Rust 没有 i++ / i--,用 += 1 / -= 1。没有 C 风格 for(int i=0; i<5; i++)。
2.4 赋值返回 ()
// C 可以链式赋值:a = b = 3; (b=3 返回 3)
// Rust 不行!
a = b = 3; // ❌ b = 3 返回 (),不能赋给 a
防止 C 里 if (x = 5) 的经典 bug(本意 == 写成 =)。
2.5 运算符优先级改进
// C 经典坑:
if (x & MASK != 0) // 实际解析为 x & (MASK != 0) !!!
// Rust 修正了:
if x & MASK != 0 { } // 解析为 (x & MASK) != 0 ✅ 符合直觉
Rust 把位运算优先级提到比较运算符之上,写驱动做位操作不用疯狂加括号。
2.6 发散类型 !
永不返回的函数,返回类型为 !(never type):
fn serve_forever() -> ! {
loop { handle_request(); }
}
panic!()、std::process::exit()、无限 loop 都是 ! 类型。
三、所有权与借用
3.1 Move 语义(默认就是 std::move)
let s1 = String::from("hello");
let s2 = s1; // s1 的所有权 move 给 s2
println!("{}", s1); // ❌ 编译错误!s1 已经被 move 走了
| C++ | Rust | |
|---|---|---|
| 默认赋值行为 | 拷贝 | 移动 |
| move 后用原变量 | 运行时未定义行为 | 编译期报错 |
| 显式 move | std::move(x) |
默认就是 |
| 显式拷贝 | 默认就是 | .clone() |
Copy trait——基本类型不 move:栈上小类型(i32, f64, bool, char)自动 copy,堆上类型(String, Vec)默认 move。
三种解决 move 后不能用:
let s1 = String::from("hello");
let s2 = s1.clone(); // 方法 1:深拷贝
let s2 = &s1; // 方法 2:借用
let s2 = s1; // 方法 3:接受 move,后面不再用 s1
3.2 引用与借用
不可变引用 &T:
fn print_len(s: &String) {
println!("长度: {}", s.len());
}
let s1 = String::from("hello");
print_len(&s1); // 借用,不转移所有权
println!("{}", s1); // ✅ s1 还能用
可变引用 &mut T:
fn add_world(s: &mut String) {
s.push_str(", world!");
}
let mut s1 = String::from("hello");
add_world(&mut s1); // 两边必须匹配,都要 &mut
借用规则(编译期读写锁):
| 规则 | 类比并发 |
|---|---|
多个 &T 同时存在 |
多读并发 ✅ |
只能一个 &mut T |
写必须独占 ✅ |
&T 和 &mut T 不能共存 |
读写互斥 ✅ |
本质: C/C++ 数据竞争全靠程序员自觉,Rust 编译器在编译期强制执行读写锁规则。
四、字符串
4.1 String vs &str
| Rust | C/C++ | 存储 |
|---|---|---|
"hello" → &str |
const char* |
.rdata 段(只读) |
String::from("hello") |
std::string |
堆上(可修改、可增长) |
let s1 = "hello"; // &str,只读
let mut s2 = String::from("hello"); // String,可修改
s2.push_str(" world"); // ✅
mut 加在 &str 上只能重新绑定(改指向),不能改底层数据。
转换:
&str → String:"hello".to_string()或String::from("hello")String → &str:&s(自动解引用,零成本)
函数参数尽量用 &str(更通用),需要拥有/修改时用 String。
五、结构体与枚举
5.1 结构体(struct)+ impl
Rust 没有 class,数据(struct)和行为(impl)分开写:
struct Process {
pid: u32,
name: String,
is_running: bool,
}
impl Process {
// 关联函数(无 self)—— 用 :: 调用
fn new(pid: u32, name: &str) -> Process {
Process { pid, name: String::from(name), is_running: true }
}
// 只读方法(&self)—— 用 . 调用
fn get_name(&self) -> &String { &self.name }
// 可变方法(&mut self)—— 用 . 调用
fn set_name(&mut self, new_name: &str) {
self.name = String::from(new_name);
}
}
| Rust | C++ | 调用方式 |
|---|---|---|
fn new(...) -> Self |
静态方法/构造函数 | Process::new() |
fn info(&self) |
void info() const |
p.info() |
fn kill(&mut self) |
void kill() |
p.kill() |
fn consume(self) |
无对应(对象被 move) | p.consume() |
关键区别:
new不是关键字,只是约定俗成,叫create/init都行self代替 C++ 的this,不是指针,直接self.namemut是整个对象级别的开关,不能只让某个字段可变
5.2 枚举(enum)
简单枚举(跟 C 一样):
enum Color { Red, Green, Blue }
携带数据的枚举(类型安全的 tagged union):
enum IoctlRequest {
Read(u32), // 携带地址
Write(u32, Vec<u8>), // 携带地址 + 数据
Query { name: String }, // 携带匿名结构体
Close, // 不携带数据
}
C 里要 struct + union + enum 三件套,Rust 一个 enum 搞定。
标准库两个最重要的枚举:
enum Option<T> { Some(T), None } // 替代 NULL
enum Result<T, E> { Ok(T), Err(E) } // 替代异常/errno
Rust 没有 NULL,没有异常,编译器强制你处理"没有值"和"出错"。
5.3 元组结构体与单元结构体(TRPL Ch5 / PR Ch9)
元组结构体——字段没名字,用 .0 .1 访问:
struct Color(u8, u8, u8);
struct Meters(f64);
let red = Color(255, 0, 0);
println!("R={}", red.0);
核心用途:newtype 模式(编译期防混淆)
struct Meters(f64);
struct Seconds(f64);
// 底层都是 f64,但类型不同,不能混用!
// C 的 typedef 只是别名,编译器不区分
写驱动时防止把地址和长度搞混特别有用。
单元结构体——零大小的类型标记:
struct KernelMode; // 没有 {},没有 (),0 字节
struct UserMode;
// 用作泛型的类型标记,后面学泛型时会大量用到
5.4 内部可变性预览(PR Ch9 / 类比 C++ mutable)
Rust 的 mut 是整个对象级别的,但有时需要"外面不可变,内部某字段能改":
use std::cell::Cell;
struct Counter {
value: Cell<i32>, // 即使 Counter 不是 mut,也能改
}
let c = Counter { value: Cell::new(0) }; // c 没有 mut!
c.value.set(42); // ✅ 可以改
| 编译期检查 | 运行时检查 | |
|---|---|---|
正常引用 &T / &mut T |
✅ | — |
Cell<T> |
— | 无(直接 get/set,Copy 类型) |
RefCell<T> |
— | ✅(违规 panic) |
类比 C++:Cell 约等于 mutable 关键字。阶段二深入学。
5.5 模式匹配进阶(TRPL Ch6 / PR Ch10)
if let——只关心一个分支时的简写:
// 完整 match
match config_max {
Some(max) => println!("最大值: {}", max),
_ => (), // 必须写但不关心
}
// if let 简写(等价)
if let Some(max) = config_max {
println!("最大值: {}", max);
}
while let——循环版 if let:
let mut stack = vec![1, 2, 3];
while let Some(top) = stack.pop() {
println!("{}", top); // 3, 2, 1
}
嵌套模式 + 匹配守卫(match guard):
match nested {
Some(Some(value)) => println!("值: {}", value),
Some(None) => println!("外层有,内层空"),
None => println!("全空"),
}
// match guard:额外条件过滤
match request {
IoctlRequest::Write(addr, ref data) if addr > 0x1000 => {
println!("写入高地址 {:X}", addr);
}
_ => {}
}
穷尽性(Exhaustiveness):
match 必须覆盖所有可能,漏了编译器报错。C 的 switch 忘了 case 不报错,Rust 直接拦住。加新枚举变体时编译器会告诉你所有没处理的地方。
六、工具链与打印
6.1 Cargo 常用命令
| 命令 | 作用 | 类比 VS |
|---|---|---|
cargo new xxx |
新建项目(自动建目录) | 文件→新建→项目 |
cargo init |
在当前目录初始化项目 | — |
cargo build |
编译 | F7 |
cargo run |
编译并运行 | Ctrl+F5 |
cargo new xxx --lib |
新建库项目 | 新建→DLL 项目 |
cargo run 必须在有 Cargo.toml 的目录下执行。
6.2 打印
| 宏 | 作用 | 类比 C |
|---|---|---|
println!("x={}", x) |
打印 + 换行 | printf("x=%d\n", x) |
print!("x={}", x) |
打印,不换行 | printf("x=%d", x) |
println!("x={:X}", x) |
十六进制 | printf("x=%X", x) |
println!("x={:?}", x) |
Debug 格式 | 无直接对应 |
6.3 宏 vs 函数
println! 末尾有 !,是宏不是函数。
C 宏 (#define) |
Rust 宏 (macro_rules!) |
|
|---|---|---|
| 展开时机 | 预处理器,纯文本替换 | 编译器,操作语法树 |
| 类型安全 | 无 | 有 |
| 卫生性 | 无(变量名冲突) | 卫生宏(内部不污染外部) |
七、预览(阶段二详细学)
7.1 闭包
let is_even = |x| x % 2 == 0; // 类型自动推导
// 类比 C++ lambda:auto is_even = [](int x) { return x % 2 == 0; };
捕获规则(Fn/FnMut/FnOnce)→ 阶段二 2.7
7.2 ? 操作符
let f = File::open("data.txt")?;
// 等价于 match + return Err
错误处理详细 → 阶段二 2.3
附录:老语法备忘
遇到老代码或网上教程可能看到,知道是什么就行:
| 老写法 | 新写法 | 说明 |
|---|---|---|
ref r |
match &x |
match 里借用 |
ref mut r |
match &mut x |
match 里可变借用 |
try!(expr) |
expr? |
错误传播 |
extern crate xxx |
直接 use xxx |
导入依赖(2018+) |
#[macro_use] |
use xxx::macro_name |
导入宏(2018+) |
extern crate alloc 在 no_std 环境还会用到——写驱动时会遇到。

浙公网安备 33010602011771号