变量与数据类型:从“默认不可变”说起

变量与数据类型:从"默认不可变"说起

Rust核心语法:安全与优雅的代码基石

上一篇我们初步认识了Rust的核心优势,搭建了开发环境,还写了一个复刻Linux wc命令的小工具。但要写出真正符合Rust哲学的**"安全、高性能"**代码,核心语法的掌握是基础------特别是Rust和其他语言差异极大的「默认不可变变量」「match表达式」「Option空值处理」「堆上集合」,这些是Rust"编译时安全检查"的核心载体。

今天我们就来系统学习这些核心语法,最后用一个「学生成绩管理系统」的实战案例,把所有知识点串起来,让你真正理解Rust语法的**"安全设计""优雅表达"**。


🔢 2.1 变量与数据类型:从"默认不可变"说起

变量是所有编程语言的基础,但Rust的变量设计从根本上颠覆了我们的传统认知 ------默认不可变。这不是"语法糖",而是Rust"安全优先"设计哲学的直接体现。

2.1.1 变量:默认不可变的设计逻辑

在C/Java/Python中,变量默认是可变的------你可以随时修改它的值。但Rust认为:大多数变量在生命周期内不需要修改,不可变变量天然具备"线程安全""可预测性"的优势。

▶ 基本使用

⌨️

rust 复制代码
// 默认不可变变量:定义后无法修改
let x = 5;
println!("x = {}", x); // 输出:x = 5

// 如果尝试修改,会直接编译错误:cannot assign twice to immutable variable `x`
// x = 6;

// 可变变量:用 `let mut` 声明
let mut y = 10;
println!("y = {}", y); // 输出:y = 10
y = 20;
println!("y = {}", y); // 输出:y = 20

💡 为什么默认不可变? 不可变变量是"线程安全"的充分条件------多个线程读取同一个不可变变量,永远不会出现数据竞争。这是Rust"无畏并发"的基础之一。

2.1.2 常量:比不可变变量更严格的"不变性"

常量用const声明,是编译时固定的"不可变值",它的规则比不可变变量更严格:

  1. 必须显式指定类型(不可省略);
  2. 只能用编译时可计算的表达式赋值(不能用运行时结果,比如函数返回值);
  3. 生命周期贯穿整个程序。

⌨️

rust 复制代码
// 正确:编译时可计算的表达式
const MAX_SCORE: u32 = 100;
const PI: f64 = 3.1415926;

// 错误:用函数返回值赋值(运行时结果)
// const RANDOM: u32 = rand::random();

⚠️ 常量 vs 不可变变量 :不可变变量可以在运行时赋值(比如let x = get_user_input();),但常量不行;常量在编译时会被替换为实际值,而不可变变量会在栈上分配内存。

2.1.3 数据类型:标量与复合的分层设计

Rust的类型系统分为标量类型 (单个值)和复合类型(多个值组合),所有变量都必须有明确的类型(编译器可以自动推导,但本质是强类型语言)。

▶ 标量类型:最基础的"原子值"
  • 整型 :表示整数,分有符号(i8/i16/i32/i64,默认i32)和无符号(u8/u16/u32/u64);
  • 浮点型 :表示小数,分f32(单精度)和f64(双精度,默认);
  • 布尔型 :只有truefalse(占用1字节);
  • 字符型 :用单引号'表示,支持完整的UTF-8编码(包括中文、表情等)。

⌨️

rust 复制代码
// 整型
let a: i32 = 10; // 有符号32位,默认
let b: u64 = 20; // 无符号64位

// 浮点型
let c = 3.14; // 默认f64
let d: f32 = 2.718;

// 布尔型
let is_true = true;

// 字符型:支持UTF-8
let ch1 = 'a';
let ch2 = '中';
let ch3 = '😀'; // 表情符号也能直接使用!
println!("{} {} {}", ch1, ch2, ch3); // 输出:a 中 😀
▶ 复合类型:组合多个值

Rust有两种基础复合类型:

  1. 元组(Tuple):固定长度,不同类型值的组合;
  2. 数组(Array):固定长度,相同类型值的组合。

💡 为什么都是固定长度? 因为Rust的栈内存大小在编译时必须确定------固定长度的类型才能在栈上分配。如果需要动态大小,必须用堆上的集合类型(比如Vec<T>)。

元组的使用

⌨️

rust 复制代码
// 定义元组:(类型1, 类型2, ...)
let student = ("202401", "Alice", 95, 88);
// 类型自动推导为 (&str, &str, i32, i32)

// 方式1:用索引访问
println!("学号:{}", student.0); // 输出:202401
println!("数学成绩:{}", student.2); // 输出:95

// 方式2:模式匹配解构(Rust特色,优雅!)
let (id, name, math, chinese) = student;
println!("{} {} 数学:{} 语文:{}", id, name, math, chinese); // 输出完整信息
数组的使用

⌨️

rust 复制代码
// 定义数组:[类型; 长度]
let scores = [95, 92, 88, 90];
// 类型自动推导为 [i32; 4]

// 安全访问:用 get() 方法返回 Option<&T>(避免越界panic)
match scores.get(5) {
    Some(score) => println!("成绩:{}", score),
    None => println!("无效索引"), // 越界时返回此信息,不会崩溃!
} // 输出:无效索引

// 不安全访问:直接用 [index],越界会panic(生产环境不推荐)
// println!("{}", scores[5]); // 运行时崩溃:index out of bounds

⚠️ Rust数组 vs 其他语言数组 :其他语言的数组大多是"动态长度"或"指针+长度",而Rust的数组是真正的"固定长度"------编译时会检查所有访问是否越界。


🔄 2.2 函数与流程控制:安全与优雅的逻辑表达

Rust的函数和流程控制设计兼顾了"类型安全"和"语法优雅" ,特别是match表达式和Option类型,是Rust"编译时错误检查"的核心武器。

2.2.1 函数:类型安全的定义与返回

Rust的函数用fn声明,规则明确:

  1. 参数必须显式指定类型
  2. 返回值用->声明,支持表达式返回 (省略return,函数最后一个表达式自动作为返回值)。

⌨️

rust 复制代码
// 定义函数:参数a和b是i32类型,返回i32
fn add(a: i32, b: i32) -> i32 {
    // 表达式返回:a + b 是函数最后一个表达式,自动作为返回值
    a + b
}

fn main() {
    let sum = add(5, 3);
    println!("5 + 3 = {}", sum); // 输出:8
}

💡 为什么省略return? Rust的设计哲学是"少写冗余代码",表达式返回让函数更简洁。如果需要提前返回,可以用return(比如条件判断时)。

2.2.2 流程控制:从"语句"到"表达式"

Rust的流程控制大多是"表达式"而非"语句"------这意味着你可以把流程控制的结果直接赋值给变量。

▶ if表达式:条件赋值的优雅方式

⌨️

rust 复制代码
let x = 10;
// if是表达式,直接赋值给result
let result = if x > 5 {
    "大于5"
} else {
    "小于等于5"
};
println!("result = {}", result); // 输出:大于5

💡 为什么是表达式? 因为Rust没有"三元运算符"(a ? b : c)------if表达式已经足够优雅地替代它。

▶ loop循环:可以返回值的无限循环

Rust的loop无限循环 ,但它支持break返回值------这是其他语言极少具备的特性。

⌨️

rust 复制代码
let mut counter = 0;
// loop返回值赋值给result
let result = loop {
    counter += 1;
    if counter == 10 {
        break counter * 2; // 返回counter*2
    }
};
println!("result = {}", result); // 输出:20

💡 使用场景 :需要重复尝试直到成功的场景(比如重试网络请求、解析数据),直接用loop返回成功结果,代码更简洁。

▶ for循环:遍历集合的"安全首选"

Rust的for循环自动遍历集合的所有元素,无需手动管理索引------从根本上避免了"数组越界"问题。

⌨️

rust 复制代码
// 遍历Vec(动态数组)
let mut numbers = vec![1, 2, 3, 4];
// 不可变遍历:iter()返回不可变引用
for num in numbers.iter() {
    println!("num = {}", num);
}

// 可变遍历:iter_mut()返回可变引用,修改元素值
for num in numbers.iter_mut() {
    *num *= 2; // 注意:需要用*解引用
}
println!("修改后:{:?}", numbers); // 输出:[2, 4, 6, 8]

// 遍历范围:a..b是左闭右开,a..=b是闭区间
for i in 1..=5 {
    println!("i = {}", i); // 输出:1-5
}

⚠️ 为什么不推荐while循环遍历集合? while循环需要手动管理索引(比如let mut i = 0; while i < len { ... i +=1 }),容易出现越界或逻辑错误,而for循环由编译器保证安全。

2.2.3 match表达式:Rust的"语法瑞士军刀"

match是Rust最强大的流程控制结构,它可以:

  • 匹配类型范围
  • 解构元组数组结构体
  • 穷尽性检查------必须覆盖所有可能的情况,否则编译错误!

这是Rust"编译时安全"的核心特性之一------永远不会出现"未处理的分支"

▶ 范围匹配(替代其他语言的switch)

⌨️

rust 复制代码
let score = 85;
// 范围匹配:90..=100 表示90到100(包括两端)
let grade = match score {
    90..=100 => 'A',
    80..=89 => 'B',
    70..=79 => 'C',
    0..=69 => 'D',
    _ => '?', // 通配符,匹配所有其他情况,确保穷尽性
};
println!("成绩等级:{}", grade); // 输出:B
▶ 解构匹配

⌨️

rust 复制代码
// 匹配元组
let point = (3, 4);
match point {
    (0, 0) => println!("原点"),
    (x, 0) => println!("x轴:{}", x),
    (0, y) => println!("y轴:{}", y),
    (x, y) => println!("点({},{})", x, y),
} // 输出:点(3,4)

// 匹配Option类型(空值处理,后面会讲)
let maybe_score = Some(95);
match maybe_score {
    Some(s) => println!("成绩:{}", s),
    None => println!("没有成绩"),
} // 输出:成绩:95

2.2.4 Option类型:安全的"空值替代方案"

其他语言的null运行时错误的"万恶之源" (空指针解引用会导致程序崩溃)。Rust用Option<T>类型完全替代了null

  • Option<T>有两种变体:Some(T)(有值)和None(空值);
  • 必须显式处理None的情况,否则编译错误

这是Rust"零空指针解引用"承诺的核心实现。

⌨️

rust 复制代码
// 定义函数:查找数组中索引为index的元素,返回Option<&i32>
fn find_element(arr: &[i32], index: usize) -> Option<&i32> {
    if index < arr.len() {
        Some(&arr[index]) // 找到,返回Some(值)
    } else {
        None // 没找到,返回None
    }
}

fn main() {
    let scores = [95, 92, 88];
    // 匹配返回结果,必须处理None
    match find_element(&scores, 5) {
        Some(score) => println!("找到成绩:{}", score),
        None => println!("无效索引"), // 必须处理None
    } // 输出:无效索引

    // 简化处理:if let(只关心Some的情况)
    if let Some(score) = find_element(&scores, 0) {
        println!("第一个成绩:{}", score); // 输出:95
    }
}

💡 为什么Option能保证安全? 因为Option<T>T不同的类型 ------你不能直接把Option<T>当作T使用,必须先"解包"(处理None的情况)。


📦 2.3 核心集合类型:堆上的数据结构

前面学的元组和数组都是栈上的固定长度类型 ,但实际开发中我们经常需要动态大小的数据结构。Rust的标准库提供了三种核心堆上集合:

  1. Vec<T>:动态数组;
  2. String:动态字符串;
  3. HashMap<K, V>:键值对映射。

这些集合的所有权由变量持有,当变量离开作用域时,会自动释放堆上的内存------无需手动管理。

2.3.1 Vec:动态数组

Vec<T>可变长度的数组,支持添加、删除、修改元素,适合存储"相同类型的序列数据"。

⌨️

rust 复制代码
// 创建Vec的三种方式
let mut v1 = Vec::new(); // 空Vec
let mut v2 = vec![1, 2, 3]; // 用宏创建,自动推导类型

// 添加元素
v1.push(1);
v1.push(2);
v1.push(3);

// 安全访问:get()返回Option<&T>
match v2.get(2) {
    Some(num) => println!("第三个元素:{}", num), // 输出:3
    None => println!("无效索引"),
}

// 可变访问:get_mut()返回Option<&mut T>
if let Some(num) = v2.get_mut(0) {
    *num = 10; // 修改第一个元素为10
}
println!("v2 = {:?}", v2); // 输出:[10, 2, 3]

// 遍历Vec
for num in v1.iter() {
    println!("{}", num);
}

⚠️ Vec的内存管理 :当Vec的容量不足时,会自动扩容(通常是翻倍)。扩容时会分配新的堆内存,将旧数据拷贝到新内存------这会有一定的性能开销,但Rust的扩容策略已经过优化,实际使用中影响很小。

2.3.2 String:动态字符串

Rust的String堆上分配的UTF-8编码字符串,支持拼接、切片、替换等操作,是Rust开发中最常用的字符串类型。

⌨️

rust 复制代码
// 创建String的方式
let s1 = String::from("Hello"); // 从&str创建
let s2 = "World".to_string(); // 从字符串字面量创建

// 拼接字符串
let mut s3 = s1 + " " + &s2; // + 会转移s1的所有权,所以s2需要用&引用
println!("s3 = {}", s3); // 输出:Hello World

// 更灵活的拼接:用format!宏(不会转移所有权)
let s4 = String::from("Rust");
let s5 = format!("{} {}!", s3, s4);
println!("s5 = {}", s5); // 输出:Hello World Rust!

// 字符串切片:注意是按**字节**切片(不是字符!)
let s6 = String::from("你好,Rust");
let slice1 = &s6[0..3]; // 前3个字节 → "你"
let slice2 = &s6[4..7]; // 第4-6字节 → "好"
// let slice3 = &s6[0..4]; // 错误:跨UTF-8字符边界,运行时panic

// 正确的字符切片方式:用chars()迭代器
let slice3: String = s6.chars().take(2).collect();
println!("slice3 = {}", slice3); // 输出:你好

💡 为什么String切片是按字节? 因为Rust的String是UTF-8编码------每个字符的字节数不固定(中文是3字节)。如果按字符切片,需要遍历整个字符串计算字节边界,这会影响性能。Rust选择"显式性"而非"自动性",避免隐藏的性能开销。

2.3.3 HashMap<K, V>:键值对映射

HashMap<K, V>哈希表 ,用于存储"键值对"数据,支持快速查找、插入、删除(时间复杂度O(1))。它位于std::collections模块中,需要手动导入。

⌨️

rust 复制代码
use std::collections::HashMap;

// 创建HashMap
let mut scores = HashMap::new();

// 插入键值对
scores.insert(String::from("Alice"), 95);
scores.insert(String::from("Bob"), 92);

// 查找键对应的值
match scores.get(&String::from("Alice")) {
    Some(score) => println!("Alice的成绩:{}", score), // 输出:95
    None => println!("未找到"),
}

// 遍历HashMap
for (name, score) in &scores {
    println!("{}: {}", name, score);
}

// 只在键不存在时插入
scores.entry(String::from("Charlie")).or_insert(88);
println!("Charlie的成绩:{}", scores.get(&String::from("Charlie")).unwrap()); // 输出:88

⚠️ HashMap的注意事项

  • 键类型必须实现HashEq traits(Rust的基本类型都自动实现了);
  • 默认的哈希函数是SipHash (安全性高但性能中等),如果需要更高的性能,可以使用第三方库(比如rustc-hash)。

💼 2.4 实战案例:学生成绩管理系统

现在我们用前面学习的所有核心语法,实现一个完整的学生成绩管理系统,功能包括:

  1. 添加学生(学号、姓名、数学/语文/英语成绩);
  2. 查看所有学生的成绩和平均分;
  3. 按学号查找学生;
  4. 按学号删除学生;
  5. 退出系统。

2.4.1 系统设计

  1. 数据结构 :用Student结构体封装学生信息,HashMap<u32, Student>存储所有学生(学号作为唯一键);
  2. 功能模块 :每个功能对应一个函数(add_student/list_students/find_student/delete_student);
  3. 输入输出 :用标准输入输出处理用户交互,用Result类型处理输入错误;
  4. 流程控制 :用loop循环实现菜单,match匹配用户选择。

2.4.2 完整代码

⌨️

rust 复制代码
use std::collections::HashMap;
use std::io::{self, BufRead, Write};

// 学生结构体:封装学号、姓名、三科成绩
struct Student {
    id: u32,           // 学号
    name: String,      // 姓名
    math: u32,         // 数学成绩
    chinese: u32,      // 语文成绩
    english: u32,      // 英语成绩
}

// 为Student实现方法:计算平均分
impl Student {
    fn average(&self) -> f32 {
        (self.math as f32 + self.chinese as f32 + self.english as f32) / 3.0
    }
}

// 1. 添加学生
fn add_student(map: &mut HashMap<u32, Student>) -> io::Result<()> {
    // 提示并读取学号
    print!("请输入学号:");
    io::stdout().flush()?; // 刷新输出,确保提示先显示
    let mut input = String::new();
    io::stdin().read_line(&mut input)?;
    let id: u32 = input.trim().parse()?; // 转换为u32

    // 提示并读取姓名
    print!("请输入姓名:");
    io::stdout().flush()?;
    input.clear();
    io::stdin().read_line(&mut input)?;
    let name = input.trim().to_string();

    // 提示并读取数学成绩
    print!("请输入数学成绩:");
    io::stdout().flush()?;
    input.clear();
    io::stdin().read_line(&mut input)?;
    let math: u32 = input.trim().parse()?;

    // 提示并读取语文成绩
    print!("请输入语文成绩:");
    io::stdout().flush()?;
    input.clear();
    io::stdin().read_line(&mut input)?;
    let chinese: u32 = input.trim().parse()?;

    // 提示并读取英语成绩
    print!("请输入英语成绩:");
    io::stdout().flush()?;
    input.clear();
    io::stdin().read_line(&mut input)?;
    let english: u32 = input.trim().parse()?;

    // 创建学生对象并插入HashMap
    let student = Student { id, name, math, chinese, english };
    if map.insert(id, student).is_some() {
        println!("警告:学号 {} 已存在,已更新信息", id);
    } else {
        println!("学生信息添加成功");
    }

    Ok(())
}

// 2. 查看所有学生
fn list_students(map: &HashMap<u32, Student>) {
    if map.is_empty() {
        println!("暂无学生信息");
        return;
    }

    // 打印表头
    println!("{}", "=".repeat(65));
    println!("{:>10} {:<12} {:>8} {:>8} {:>8} {:>12}", "学号", "姓名", "数学", "语文", "英语", "平均分");
    println!("{}", "-".repeat(65));

    // 遍历并打印学生信息
    for (_, student) in map {
        println!(
            "{:>10} {:<12} {:>8} {:>8} {:>8} {:>12.2}",
            student.id,
            student.name,
            student.math,
            student.chinese,
            student.english,
            student.average()
        );
    }

    println!("{}", "=".repeat(65));
}

// 3. 按学号查找学生
fn find_student(map: &HashMap<u32, Student>) -> io::Result<()> {
    print!("请输入学号:");
    io::stdout().flush()?;
    let mut input = String::new();
    io::stdin().read_line(&mut input)?;
    let id: u32 = input.trim().parse()?;

    match map.get(&id) {
        Some(student) => {
            println!("{}", "=".repeat(50));
            println!("学号:{}", student.id);
            println!("姓名:{}", student.name);
            println!("数学成绩:{}", student.math);
            println!("语文成绩:{}", student.chinese);
            println!("英语成绩:{}", student.english);
            println!("平均分:{:.2}", student.average());
            println!("{}", "=".repeat(50));
        }
        None => println!("未找到学号为 {} 的学生", id),
    }

    Ok(())
}

// 4. 按学号删除学生
fn delete_student(map: &mut HashMap<u32, Student>) -> io::Result<()> {
    print!("请输入学号:");
    io::stdout().flush()?;
    let mut input = String::new();
    io::stdin().read_line(&mut input)?;
    let id: u32 = input.trim().parse()?;

    if map.remove(&id).is_some() {
        println!("学号 {} 的学生信息已删除", id);
    } else {
        println!("未找到学号为 {} 的学生", id);
    }

    Ok(())
}

// 主函数:系统入口
fn main() -> io::Result<()> {
    // 初始化学生信息HashMap
    let mut student_map: HashMap<u32, Student> = HashMap::new();

    // 菜单循环
    loop {
        // 打印菜单
        println!("\n=== 学生成绩管理系统 ===");
        println!("1. 添加学生");
        println!("2. 查看所有学生");
        println!("3. 按学号查找学生");
        println!("4. 按学号删除学生");
        println!("5. 退出系统");
        print!("请选择操作(1-5):");
        io::stdout().flush()?;

        // 读取用户选择
        let mut input = String::new();
        io::stdin().read_line(&mut input)?;
        let choice: u32 = match input.trim().parse() {
            Ok(num) => num,
            Err(_) => {
                println!("无效输入,请输入1-5的数字");
                continue; // 重新循环
            }
        };

        // 匹配选择并执行对应功能
        match choice {
            1 => add_student(&mut student_map)?,
            2 => list_students(&student_map),
            3 => find_student(&student_map)?,
            4 => delete_student(&mut student_map)?,
            5 => {
                println!("退出系统,感谢使用!");
                break; // 退出循环
            }
            _ => println!("无效选择,请输入1-5的数字"),
        }
    }

    Ok(())
}

2.4.3 运行与测试

  1. 编译运行 :在项目目录下执行cargo run
  2. 测试流程
    ① 选择1添加学生:学号202401,姓名Alice,数学95,语文92,英语88;
    ② 选择2查看所有学生:显示Alice的信息和平均分91.67;
    ③ 选择3查找学号202401:显示详细信息;
    ④ 选择4删除学号202401:提示删除成功;
    ⑤ 选择5退出系统。

2.4.4 代码亮点

  • 结构体封装 :用Student结构体清晰地封装了学生信息;
  • 方法实现 :为Student实现average方法,符合"面向对象"的封装思想;
  • 错误处理 :所有I/O操作都用Result类型处理,避免运行时崩溃;
  • 安全访问 :HashMap的get/insert/remove方法都经过编译器安全检查;
  • 用户体验 :用io::stdout().flush()确保提示信息先显示,避免用户输入混乱。

✅ 总结

这一篇我们系统学习了Rust的核心语法,包括:

  1. 变量与数据类型:默认不可变的设计哲学,标量与复合类型的安全使用;
  2. 函数与流程控制 :类型安全的函数定义,if/loop/for的优雅表达,match表达式的强大功能,Option类型的安全空值处理;
  3. 核心集合类型Vec<T>/String/HashMap<K, V>的堆上内存管理与安全访问;
  4. 实战案例:用所有核心知识点实现了一个完整的学生成绩管理系统,展示了Rust语法的"安全与优雅"。

Rust的语法设计处处体现了"编译时安全"的哲学 ------从默认不可变变量到match的穷尽性检查,从Option的空值处理到集合的安全访问,这些设计都把"运行时错误"提前到"编译时解决",让你写出"无畏崩溃"的代码。

下一篇我们会深入学习Rust的所有权、借用、生命周期------这是Rust的"灵魂",也是写出真正"零内存安全问题"代码的关键。

相关推荐
Ralph_Y8 小时前
多重继承与虚继承
开发语言·c++
今晚务必早点睡8 小时前
写一个Python接口:发送支付成功短信
开发语言·python
jghhh018 小时前
基于C#实现与三菱FX系列PLC串口通信
开发语言·算法·c#·信息与通信
ada7_8 小时前
LeetCode(python)22.括号生成
开发语言·数据结构·python·算法·leetcode·职场和发展
喵了meme8 小时前
C语言实战练习
c语言·开发语言
imkaifan8 小时前
bind函数--修改this指向,返回一个函数
开发语言·前端·javascript·bind函数
love530love8 小时前
EPGF 新手教程 12在 PyCharm(中文版 GUI)中创建 Poetry 项目环境,并把 Poetry 做成“项目自包含”(工具本地化为必做环节)
开发语言·ide·人工智能·windows·python·pycharm·epgf
White_Can8 小时前
《C++11:列表初始化》
c语言·开发语言·c++·vscode·stl
White_Can8 小时前
《C++11:右值引用与移动语义》
开发语言·c++·stl·c++11
比奇堡派星星8 小时前
Linux4.4使用AW9523
linux·开发语言·arm开发·驱动开发