Rust学习 - 02 基础语法 变量 数据类型 宏 Cargo命令 类型转换
Rust 基础语法 vs C/C++ 对比笔记
学习日期:2026-03-12
对应教材:TRPL Chapter 3 — Common Programming Concepts
1. 变量声明
Rust 默认不可变(跟 C/C++ 反过来)
let x = 5; // 不可变,类似 C++ 的 const int x = 5;
let mut y = 10; // 可变,类似 C++ 的 int y = 10;
| C/C++ | Rust | |
|---|---|---|
| 默认 | 可变 | 不可变 |
| 要不可变 | 加 const |
默认就是 |
| 要可变 | 默认就是 | 加 mut |
为什么? 默认不可变 = 编译器帮你防止意外修改。写驱动时尤其重要——不想共享变量被随便改。
Shadowing(变量遮蔽)
let x = 5; // i32
let x = x + 1; // i32,值为 6,重新 let 声明了新变量
let x = "hello"; // &str,可以换类型!因为是新变量
Shadowing vs mut 的区别:
// Shadowing —— 每次 let 创建新变量,可以换类型
let x = 5;
let x = "hello"; // ✅ 合法
// mut —— 修改同一个变量的值,不能换类型
let mut x = 5;
x = "hello"; // ❌ 编译错误!类型不能变
Shadowing 主要用在类型转换场景,避免取一堆烂名字:
let input = "42";
let input = input.parse::<i32>().unwrap(); // 同名,string → int
类比 C++ 内层作用域遮蔽外层变量:
int x = 5;
{
const char* x = "hello"; // 遮蔽了外层的 int x
}
2. 数据类型
整数类型(大小明确,不像 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 |
Rust 的 char 是 4 字节 Unicode 标量值,'中' 也是合法的 char。
3. 类型转换——必须显式
Rust 不允许隐式类型转换! 这是跟 C/C++ 的重大区别。
let x: i32 = 5;
let y: i64 = x; // ❌ 编译错误!不能隐式转换
let y: i64 = x as i64; // ✅ 必须用 as 显式转换
C/C++ 中隐式转换合法但可能藏 bug:
int x = 5;
long long y = x; // C 合法,隐式转换
unsigned int a = -1; // C 合法,但值变成 4294967295,坑!
好处: 写驱动时 ULONG 和 ULONG_PTR 混用在 C 里不报错,在 Rust 里编译器直接拦住你。
4. 宏 vs 函数
println! 末尾有 !,是宏(macro),不是函数。
C 宏 (#define) |
Rust 宏 (macro_rules!) |
|
|---|---|---|
| 展开时机 | 预处理器,纯文本替换 | 编译器,操作语法树 |
| 类型安全 | 无 | 有 |
| 卫生性 | 无(变量名会冲突) | 卫生宏(内部变量不污染外部) |
关键一句话:C 的宏是文本替换,Rust 的宏是语法树变换,不会出现 C 宏的括号优先级 bug。
5. Cargo 常用命令
| 命令 | 作用 | 类比 VS |
|---|---|---|
cargo new xxx |
新建项目(自动建目录) | 文件→新建→项目 |
cargo init |
在当前目录初始化项目 | — |
cargo build |
编译 | F7 |
cargo run |
编译并运行 | Ctrl+F5 |
cargo new xxx --lib |
新建库项目 | 新建→DLL 项目 |
cargo run 必须在有 Cargo.toml 的目录下执行。
6. 类型转换进阶——as vs try_into()
let x: u64 = 0x1FFFFFFFF;
// as —— 直接截断,不报错(跟 C 强转一样)
let a: u32 = x as u32;
// try_into() —— 转换失败返回 Err,不会静默截断
let b: Result<u32, _> = x.try_into(); // Err(TryFromIntError(()))
| 方式 | 行为 | 类比 C++ |
|---|---|---|
as |
直接转,可能截断 | (int)x 强转 |
try_into() |
安全检查,失败返回 Err |
无直接对应 |
Rust 没有异常(没有 try/catch),错误通过 Result<T, E> 返回值处理,编译器会逼你处理。
7. 函数
fn add(a: i32, b: i32) -> i32 {
a + b // 没分号 = 返回值(表达式)
}
对比 C++:int add(int a, int b) { return a + b; }
| C/C++ | Rust | |
|---|---|---|
| 关键字 | 返回类型前置 | fn + -> 返回类型 后置 |
| 返回值 | return x; |
最后一行不加分号(也可以用 return) |
| main 返回 | int(必须) |
()(默认)或 Result |
坑:加了分号就变成语句,返回 (),编译报错!
fn add(a: i32, b: i32) -> i32 {
a + b // ✅ 表达式,返回 i32
a + b; // ❌ 语句,返回 (),类型不匹配
}
8. 控制流
if(条件不用括号,且是表达式)
// 基本用法
if x > 5 {
println!("大");
} else if x > 0 {
println!("中");
} else {
println!("小");
}
// if 是表达式,可以返回值(替代 C 的三元运算符 ?:)
let msg = if x > 5 { "大" } else { "小" };
Rust 没有三元运算符 ?:,用 if else 表达式代替。
match(类似 switch,但更强大)
match x {
1 => println!("一"),
2 => println!("二"),
_ => println!("其他"), // _ 类似 default
}
循环——三种
// 1. loop —— 无限循环(C 的 while(1)),break 可以带返回值!
let result = loop {
if count == 5 {
break count * 2; // C 的 break 做不到这个
}
count += 1;
};
// 2. while —— 跟 C 一样
while n > 0 {
n -= 1;
}
// 3. for —— 范围遍历(最常用)
for i in 0..5 { // 0, 1, 2, 3, 4(不含 5)
print!("{} ", i);
}
注意:Rust 没有 i++ / i--,用 i += 1 / i -= 1。
也没有 C 风格的 for(int i=0; i<5; i++)。
9. 打印
| 宏 | 作用 | 类比 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 格式输出 | 无直接对应 |
10. 所有权(Ownership)——Rust 最核心的概念
Move 语义(默认就是 std::move)
let s1 = String::from("hello");
let s2 = s1; // s1 的所有权 move 给 s2
println!("{}", s1); // ❌ 编译错误!s1 已经被 move 走了
对比 C++:
std::string s1 = "hello";
std::string s2 = std::move(s1); // 等价于 Rust 的 let s2 = s1;
std::cout << s1; // C++ 不报错(未定义行为),Rust 直接编译拦住
| C++ | Rust | |
|---|---|---|
| 默认赋值行为 | 拷贝 | 移动 |
| move 后用原变量 | 运行时未定义行为,编译器不管 | 编译期报错 |
| 显式 move | std::move(x) |
默认就是 |
| 显式拷贝 | 默认就是 | .clone() |
设计目的: 编译期杜绝 use-after-free,不需要 GC,零运行时开销。
Copy trait——基本类型不 move
let x: i32 = 5;
let y = x; // copy,不是 move!
println!("{}", x); // ✅ 没问题
规则:栈上的小类型(i32, f64, bool, char 等)自动 copy,堆上的(String, Vec 等)默认 move。
三种解决 move 后不能用的方法
let s1 = String::from("hello");
// 方法 1:clone(深拷贝)
let s2 = s1.clone(); // s1 还能用
// 方法 2:引用(借用,不转移所有权)
let s2 = &s1; // s1 还能用
// 方法 3:接受 move,后面不再用 s1
let s2 = s1; // s1 不再使用就没问题
11. 引用与借用(Borrowing)
不可变引用 &T
fn print_len(s: &String) {
println!("长度: {}", s.len());
}
fn main() {
let s1 = String::from("hello");
print_len(&s1); // 借用,不转移所有权
println!("{}", s1); // ✅ s1 还能用
}
可变引用 &mut T
fn add_world(s: &mut String) {
s.push_str(", world!");
}
fn main() {
let mut s1 = String::from("hello");
add_world(&mut s1); // 传可变引用
println!("{}", s1); // 输出: hello, world!
}
注意:函数签名要 &mut,调用时也要传 &mut,两边必须匹配。
借用规则(编译期读写锁)
| 规则 | 类比 C++ | 类比并发 |
|---|---|---|
可以有多个 &T(不可变引用) |
多个 const T& 同时存在 |
多读并发 ✅ |
只能有一个 &mut T(可变引用) |
只允许一个 T& 在写 |
写必须独占 ✅ |
&T 和 &mut T 不能同时存在 |
— | 读写互斥 ✅ |
let mut s = String::from("hello");
// ❌ 不能同时有两个可变引用
let r1 = &mut s;
let r2 = &mut s; // 编译错误!
// ❌ 不可变和可变不能共存
let r1 = &s;
let r2 = &mut s; // 编译错误!
// ✅ 多个不可变引用没问题
let r1 = &s;
let r2 = &s; // OK
本质: C/C++ 里数据竞争全靠程序员自觉加锁,Rust 编译器在编译期直接强制执行读写锁规则。写驱动时帮你在编译期干掉数据竞争。

浙公网安备 33010602011771号