Rust学习 - 变量与类型

0 阅读8分钟

2026-04-03

今日目标(工程师视角)

作为系统级编程工程师,理解Rust的变量绑定和类型系统是编写安全高效代码的基础。

今日目标:

  1. 理解Rust变量设计的哲学 - 为什么默认不可变?与C/C++/Go的区别
  2. 掌握所有权与变量绑定的关系 - 变量何时move、何时copy?
  3. 深入理解类型系统 - 零成本抽象、类型推断的边界
  4. 实战类型转换与错误处理 - 安全转换vs不安全转换

核心原理

1. 不可变性的设计哲学

// Rust默认不可变
let x = 5;
// x = 6; // 编译错误!cannot assign twice to immutable variable

// 显式声明可变
let mut y = 5;
y = 6; // OK

为什么Rust选择默认不可变?

语言默认可变性设计哲学
C/C++可变性能优先,程序员负责安全
Java/Python可变易用优先,GC处理内存
Go可变简洁优先,值/指针区分
Rust不可变安全优先,编译器保证

核心优势:

  1. 数据竞争防护:不可变数据可安全地在多线程间共享
  2. 优化机会:编译器可激进优化不可变变量
  3. 逻辑清晰:代码意图明确,减少副作用

2. 所有权与变量绑定

┌─────────────────────────────────────────────────────────────────┐
│                    Rust变量绑定与所有权                         │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│   let s1 = String::from("hello");  // s1拥有堆内存              │let s2 = s1;                      // 所有权转移给s2            │// s1 不再有效!                                                │
│                                                                  │
│   ┌─────────────────────┐      ┌─────────────────────┐          │
│   │       s1            │      │       s2            │          │
│   │  ┌───────────────┐  │      │  ┌───────────────┐  │          │
│   │  │ ptr ────────┐ │  │      │  │ ptr ────────┐ │  │          │
│   │  │ len = 5     │ │  │      │  │ len = 5     │ │  │          │
│   │  │ cap = 5     │ │  │      │  │ cap = 5     │ │  │          │
│   │  └─────────────┘ │  │      │  └─────────────┘ │  │          │
│   │         │        │  │      │         │        │  │          │
│   │         ▼        │  │      │         ▼        │  │          │
│   │   ┌──────────┐   │  │      │   ┌──────────┐   │  │          │
│   │   │ "hello"  │   │  │      │   │ "hello"  │   │  │          │
│   │   │  (堆)     │   │  │      │   │  (堆)     │   │  │          │
│   │   └──────────┘   │  │      │   └──────────┘   │  │          │
│   └─────────────────────┘      └─────────────────────┘          │
│                                                                  │
│   Move后:s1的ptr被设为null(无效状态),s2接管所有权              │
│   编译时检查:防止use after move                                  │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

Copy vs Move:

// Copy trait:按位复制,原变量仍有效
// 实现Copy的类型:基本数值类型、不可变引用、元组(成员都实现Copy)
let x = 5;
let y = x;  // Copy,x和y都有效
println!("{}", x); // OK

// Move:所有权转移,原变量无效
// 未实现Copy的类型:String、Vec、Box、自定义struct(默认)
let s1 = String::from("hello");
let s2 = s1;  // Move,s1无效
// println!("{}", s1); // 编译错误!value borrowed here after move

3. 类型系统的零成本抽象

// 类型别名 - 零成本,编译后完全消失
type Kilometers = i32;  // 只是别名,无运行时开销

// 新类型模式 - 零成本,但提供类型安全
struct Kilometers(i32);  // 单字段元组结构体

// 泛型 - 单态化(Monomorphization)
fn identity<T>(x: T) -> T { x }
// 编译后生成:identity_i32, identity_f64等具体函数

// trait对象 - 动态分发,有运行时开销(虚表指针)
fn draw(obj: &dyn Drawable) { ... }

代码实例

实例1:变量绑定与作用域

fn main() {
    // 不可变绑定
    let x = 5;
    println!("x = {}", x);
    // x = 6; // 编译错误!
    
    // 可变绑定
    let mut y = 5;
    y = 6;
    println!("y = {}", y);
    
    // 遮蔽(Shadowing)- 创建新变量,非修改
    let x = x + 1;  // 新x,值为6
    let x = x * 2;  // 新x,值为12
    println!("shadowed x = {}", x);  // 12
    
    // 作用域与所有权
    {
        let s = String::from("inner");
        println!("inner s = {}", s);
    } // s在这里drop,释放内存
    // println!("{}", s); // 编译错误!s not found in this scope
    
    // 常量 - 编译期确定,类型必须显式标注
    const MAX_POINTS: u32 = 100_000;
    println!("MAX_POINTS = {}", MAX_POINTS);
}

编译运行:

$ cargo new variables_demo && cd variables_demo
# 替换src/main.rs内容
$ cargo run
x = 5
y = 6
shadowed x = 12
inner s = inner
MAX_POINTS = 100000

实例2:基本类型系统

fn main() {
    // 整数类型 - 明确选择有符号/无符号、位数
    let a: i8 = -128;           // 有符号8位:-128~127
    let b: u8 = 255;            // 无符号8位:0~255
    let c: i32 = 1_000_000;     // 千分位分隔符增强可读性
    let d: i64 = 9_223_372_036_854_775_807; // i64最大值
    
    // 浮点数 - 默认f64(现代CPU上f32无性能优势)
    let e: f32 = 3.14;          // 单精度
    let f = 3.14159265358979;   // 默认f64,双精度
    
    // 布尔值
    let g: bool = true;
    
    // 字符 - Unicode标量值,4字节
    let h: char = '中';  // 支持Unicode
    let i: char = '🦀';  // 支持emoji
    
    // 类型推断
    let j = 42;         // 默认i32
    let k = 3.14;       // 默认f64
    
    // 显式类型标注(推荐在API边界处)
    let l: u64 = 1000;
    
    println!("整数: {}, {}, {}", a, c, d);
    println!("浮点数: {}, {}", e, f);
    println!("字符: {}, {}", h, i);
}

实例3:复合类型

fn main() {
    // 元组 - 固定长度,可混合类型
    let tup: (i32, f64, &str) = (500, 6.4, "hello");
    let (x, y, z) = tup;  // 解构
    println!("x={}, y={}, z={}", x, y, z);
    println!("tup.0={}, tup.1={}", tup.0, tup.1);  // 索引访问
    
    // 数组 - 固定长度,同类型
    let arr = [1, 2, 3, 4, 5];
    let first = arr[0];
    let second = arr[1];
    println!("first={}, second={}", first, second);
    
    // 显式类型标注数组
    let arr2: [i32; 5] = [1, 2, 3, 4, 5];  // [类型; 长度]
    
    // 初始化重复值
    let zeros = [0; 5];  // [0, 0, 0, 0, 0]
    
    // 切片 - 动态视图
    let slice = &arr[1..3];  // 索引1到2(不含3)
    println!("slice = {:?}", slice);  // [2, 3]
}

实例4:类型转换(安全vs不安全)

fn main() {
    // 显式转换(as)- 可能截断或失真
    let a: i32 = 255;
    let b: i8 = a as i8;  // 截断,b = -1(溢出)
    println!("255 as i8 = {}", b);  // -1
    
    // 浮点转整数 - 截断小数部分
    let c: f64 = 3.99;
    let d: i32 = c as i32;  // 3,非四舍五入
    println!("3.99 as i32 = {}", d);
    
    // 安全转换:TryFrom/TryInto
    use std::convert::TryFrom;
    
    let e: i32 = 255;
    let f: Result<i8, _> = i8::try_from(e);
    match f {
        Ok(val) => println!("转换成功: {}", val),
        Err(e) => println!("转换失败: {}", e),  // 255超出i8范围
    }
    
    // 解析字符串
    let g: i32 = "42".parse().expect("Not a number!");
    println!("parsed: {}", g);
    
    // 类型推断配合parse
    let h = "3.14".parse::<f64>().unwrap();
    println!("parsed f64: {}", h);
}

最佳实践

1. 变量命名与可变性选择

// ✅ 默认使用不可变
let config = load_config();

// ✅ 需要修改时显式声明mut
let mut counter = 0;
for item in &items {
    if item.active {
        counter += 1;
    }
}

// ✅ 使用有意义的遮蔽而非mut
let data = fetch_data();
let data = parse_data(data);  // 转换后遮蔽
let data = validate_data(data); // 验证后遮蔽

// ❌ 避免不必要的mut
let mut name = String::from("Alice");
println!("{}", name);  // 只是读取,不需要mut

2. 整数类型选择指南

场景推荐类型说明
通用计数i32默认,性能与范围平衡
数组索引usize与平台相关,用于索引
文件大小u64大文件支持
位操作u8/u32明确位数
时间戳i64Unix时间戳到2038年后

3. 类型转换最佳实践

// ✅ 使用TryFrom进行安全转换
use std::convert::TryFrom;

fn safe_convert(x: i64) -> Option<i32> {
    i32::try_from(x).ok()  // 溢出时返回None
}

// ✅ 使用checked_*系列方法进行算术检查
let a: i32 = 1_000_000;
let b: i32 = 3_000;
if let Some(result) = a.checked_mul(b) {
    println!("Safe result: {}", result);
} else {
    println!("Overflow would occur!");
}

// ❌ 避免不必要的as转换
let x: i32 = 100;
let y: i64 = x as i64;  // 安全但冗余,i32自动提升为i64

常见陷阱

陷阱1:整数溢出

fn main() {
    let mut x: i8 = 127;
    x += 1;  // Debug模式panic,Release模式wrap
    println!("{}", x);  // -128(Release模式)
    
    // 解决方案:使用checked_add
    let y: i8 = 127;
    match y.checked_add(1) {
        Some(v) => println!("Result: {}", v),
        None => println!("Overflow!"),
    }
}

陷阱2:浮点数比较

fn main() {
    let a = 0.1 + 0.2;
    let b = 0.3;
    
    // ❌ 直接比较可能失败
    println!("{}", a == b);  // false!
    
    // ✅ 使用近似比较
    let diff = (a - b).abs();
    println!("{}", diff < 1e-10);  // true
    
    // ✅ 或使用标准库方法
    println!("{}", (a - b).abs() < f64::EPSILON);
}

陷阱3:类型推断的边界

fn main() {
    // 编译器无法推断类型
    // let x = "hello".parse().unwrap();  // 错误!无法推断类型
    
    // ✅ 显式标注
    let x: i32 = "42".parse().unwrap();
    
    // ✅ 使用turbofish语法
    let y = "42".parse::<i32>().unwrap();
    
    // 数组长度推断
    let arr = [1, 2, 3];  // 类型:[i32; 3]
    // let arr2 = [];  // 错误!无法推断元素类型
    let arr2: [i32; 0] = [];  // ✅ 显式标注
}

练习题

练习1:温度转换器

要求: 实现华氏度与摄氏度相互转换,处理可能的溢出。

提示:

  • 摄氏度 = (华氏度 - 32) × 5/9
  • 华氏度 = 摄氏度 × 9/5 + 32
  • 使用checked运算处理溢出
参考答案
fn fahrenheit_to_celsius(f: i32) -> Option<i32> {
    // (f - 32) * 5 / 9
    f.checked_sub(32)?
     .checked_mul(5)?
     .checked_div(9)
}

fn celsius_to_fahrenheit(c: i32) -> Option<i32> {
    // c * 9 / 5 + 32
    c.checked_mul(9)?
     .checked_div(5)?
     .checked_add(32)
}

fn main() {
    let f = 100;
    match fahrenheit_to_celsius(f) {
        Some(c) => println!("{}°F = {}°C", f, c),
        None => println!("Conversion overflow!"),
    }
}

练习2:斐波那契数列

要求: 实现一个函数,返回第n个斐波那契数,使用u64类型,处理溢出。

参考答案
fn fibonacci(n: u32) -> Option<u64> {
    if n == 0 { return Some(0); }
    if n == 1 { return Some(1); }
    
    let mut prev: u64 = 0;
    let mut curr: u64 = 1;
    
    for _ in 2..=n {
        let next = prev.checked_add(curr)?;
        prev = curr;
        curr = next;
    }
    
    Some(curr)
}

fn main() {
    for i in 0..=20 {
        match fibonacci(i) {
            Some(v) => println!("fib({}) = {}", i, v),
            None => println!("fib({}) overflow!", i),
        }
    }
}

权威引用

官方文档

Rust设计文档

性能优化


学习记录: 2026-04-03