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,坑!

好处: 写驱动时 ULONGULONG_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 编译器在编译期直接强制执行读写锁规则。写驱动时帮你在编译期干掉数据竞争。

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