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

好处: 写驱动时 ULONGULONG_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.name
  • mut 是整个对象级别的开关,不能只让某个字段可变

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 环境还会用到——写驱动时会遇到。

posted @ 2026-03-12 18:19  BitWarden  阅读(35)  评论(0)    收藏  举报