第三篇:《变量、类型与函数:Rust 的“基本盘”》

在上一篇文章中,我们完成了环境搭建并跑通了第一个程序。现在,是时候进入 Rust 语言的核心语法了。Rust 的语法设计有一条贯穿始终的主线:默认安全,显式选择。变量默认不可变,需要修改时必须显式声明 mut;类型必须明确,但编译器又足够智能地支持类型推断。本文从变量声明讲起,系统讲解 Rust 的基本类型、复合类型、函数定义和控制流,并通过一个完整的斐波那契数列实战帮你巩固所学。

一、变量与可变性:默认不可变的设计哲学

1.1 let 与 let mut

在 Rust 中,变量默认是不可变的(immutable)。要修改变量的值,必须使用 mut 关键字显式声明:

rust 复制代码
fn main() {
    let x = 5;
    println!("x = {}", x);
    // x = 6;  // ❌ 编译错误:cannot assign twice to immutable variable

    let mut y = 5;
    y = 6;     // ✅ 可以修改
    println!("y = {}", y);
}

为什么默认不可变? 这并非随意的设计选择,而是 Rust 的核心理念之一:让编译器帮你发现意外修改。在大型项目中,很多 bug 源于"某个变量在你不期望的地方被修改了"。默认不可变让这种情况在编译期就被捕获。

1.2 变量遮蔽(Shadowing)

Rust 允许用同一个名字声明新变量,覆盖之前的变量------这叫遮蔽:

rust 复制代码
fn main() {
    let x = 5;

    let x = x + 1;      // 新的 x,值为 6

    let x = x * 2;      // 新的 x,值为 12

    println!("x = {}", x);  // 输出 12
}

遮蔽 vs 可变变量的区别:

遮蔽创建了一个新变量,可以改变类型。

mut 修改的是同一个变量,不能改变类型。

rust 复制代码
let spaces = "   ";         // &str 类型
let spaces = spaces.len();  // usize 类型(遮蔽允许类型改变)

let mut spaces = "   ";
// spaces = spaces.len();   // ❌ 编译错误:类型不匹配

1.3 常量的声明

常量用 const 声明,必须标注类型,且值必须在编译期确定:

rust 复制代码
const MAX_POINTS: u32 = 100_000;

常量 vs 不可变变量的区别:

常量必须在编译期求值,不可变变量可以在运行时求值。

常量可以声明在全局作用域。

常量命名惯例是 SCREAMING_SNAKE_CASE。

二、基本数据类型

Rust 是静态类型语言,所有类型在编译期确定。但编译器有强大的类型推断能力,通常不需要显式标注。

2.1 整数类型

Rust 提供了多种整数类型,区别在于位数和有无符号:

默认类型是 i32------除非有特殊需求,否则通常使用 i32。

rust 复制代码
let a = 42;              // i32(默认)
let b: u64 = 100;        // 显式标注
let c = 1_000_000;       // 下划线分隔,便于阅读
let d = 0xff;            // 十六进制
let e = 0o77;            // 八进制
let f = 0b1010_1010;     // 二进制
let g = b'A';            // 字节(仅限 u8)

⚠️ 整数溢出:debug 模式下溢出会 panic,release 模式下会"环绕"(wrap around)。如果需要显式处理溢出,使用 checked_add、wrapping_add 等方法。

2.2 浮点数类型

rust 复制代码
let x = 2.0;        // f64(默认)
let y: f32 = 3.0;   // 显式 f32

Rust 的浮点数遵循 IEEE-754 标准。默认 f64,因为现代 CPU 对双精度的处理速度与单精度相当,且精度更高。

2.3 布尔类型

rust 复制代码
let t = true;
let f: bool = false;

2.4 字符类型

Rust 的 char 是 4 字节的 Unicode 标量值,可以表示任何 Unicode 字符:

rust 复制代码
let c = 'z';
let z = 'ℤ';
let heart = '❤';
let emoji = '😀';

💡 注意:char 用单引号,字符串 &str 用双引号。这是 Rust 中容易混淆的地方。

三、复合类型

3.1 元组(Tuple)

元组将多个不同类型的值组合在一起,长度固定:

rust 复制代码
let tup: (i32, f64, char) = (500, 6.4, 'x');

// 解构(destructuring)
let (a, b, c) = tup;
println!("a = {}, b = {}, c = {}", a, b, c);

// 通过索引访问
println!("第一个元素: {}", tup.0);
println!("第二个元素: {}", tup.1);

单元类型 () 是一个特殊的空元组,表示"无返回值",函数无返回值时隐式返回 ()。

3.2 数组(Array)

数组的长度是类型的一部分,编译期确定:

rust 复制代码
let a = [1, 2, 3, 4, 5];
let b: [i32; 5] = [1, 2, 3, 4, 5];
let c = [3; 5];              // 5 个元素,值都是 3

println!("{}", a[0]);        // 通过索引访问

数组越界:Rust 在运行时检查索引,越界会 panic,而不是像 C 一样产生未定义行为。

💡 什么时候用数组 vs Vec? 长度固定、在栈上分配用数组;长度可变、在堆上分配用 Vec。数组适合小规模固定数据,Vec 适合动态数据。

四、函数:Rust 代码的基本单元

4.1 函数定义

rust 复制代码
fn add(a: i32, b: i32) -> i32 {
    a + b   // 表达式,作为返回值(注意没有分号)
}

关键规则:

每个参数必须标注类型。

返回类型用 -> 标注,无返回值时省略。

最后一个表达式的值就是返回值,不需要 return(但可以用)。

4.2 表达式 vs 语句

这是 Rust 的核心概念之一:

语句(Statement) :执行操作但不返回值,末尾有分号。

表达式(Expression) :计算并返回一个值,末尾无分号。

rust 复制代码
fn main() {
    let x = 5;           // 语句(let 是语句)
    let y = {
        let z = 3;
        z + 1            // 表达式,值为 4
    };                   // 整个块作为表达式赋值给 y

    println!("y = {}", y);  // 输出 4
}

函数返回值的常见错误:

rust 复制代码
// ❌ 错误:加了分号,变成了语句,返回 ()
fn add_wrong(a: i32, b: i32) -> i32 {
    a + b;
}

// ✅ 正确:表达式作为返回值
fn add_right(a: i32, b: i32) -> i32 {
    a + b
}

4.3 提前返回

使用 return 可以提前结束函数:

rust 复制代码
fn check_positive(n: i32) -> bool {
    if n < 0 {
        return false;   // 提前返回
    }
    true                // 最后表达式作为返回值
}

五、控制流

5.1 if / else

Rust 的 if 条件是表达式,可以用在 let 语句中:

rust 复制代码
let condition = true;
let number = if condition { 5 } else { 6 };

⚠️ 注意:if 和 else 分支的返回类型必须相同。

5.2 循环:loop、while、for

loop:无限循环,可以返回值:

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

while:带条件的循环:

rust 复制代码
let mut n = 3;
while n != 0 {
    println!("{}!", n);
    n -= 1;
}
for:遍历集合,最推荐的循环方式:

rust
let a = [10, 20, 30, 40, 50];
for element in a.iter() {
    println!("值: {}", element);
}

// 范围遍历
for n in 1..=5 {     // 1 到 5(包含 5)
    println!("{}", n);
}

for n in (1..5).rev() {  // 4, 3, 2, 1
    println!("{}", n);
}

💡 实践建议:Rust 中优先使用 for 循环,而非 while 或 loop。for 循环天然避免了数组越界和手动索引的复杂性。

5.3 标签循环与嵌套控制

rust 复制代码
'outer: for i in 1..=3 {
    for j in 1..=3 {
        if i == 2 && j == 2 {
            break 'outer;   // 跳出外层循环
        }
        println!("i = {}, j = {}", i, j);
    }
}

六、实战:计算斐波那契数列

将本文所学知识整合起来,编写一个计算斐波那契数列的程序:

rust 复制代码
use std::io;

/// 递归版:简洁但效率低(指数级时间复杂度)
fn fib_recursive(n: u32) -> u64 {
    if n <= 1 {
        return n as u64;
    }
    fib_recursive(n - 1) + fib_recursive(n - 2)
}

/// 迭代版:效率高(O(n))
fn fib_iterative(n: u32) -> u64 {
    if n <= 1 {
        return n as u64;
    }
    let mut a = 0u64;
    let mut b = 1u64;
    for _ in 2..=n {
        let temp = a + b;
        a = b;
        b = temp;
    }
    b
}

fn main() {
    println!("请输入斐波那契数列的项数(0-90):");

    let mut input = String::new();
    io::stdin()
        .read_line(&mut input)
        .expect("读取输入失败");

    let n: u32 = match input.trim().parse() {
        Ok(num) => num,
        Err(_) => {
            println!("请输入有效的数字!");
            return;
        }
    };

    if n > 90 {
        println!("数字太大,u64 会溢出!");
        return;
    }

    println!("迭代版:fib({}) = {}", n, fib_iterative(n));
    if n <= 40 {
        println!("递归版:fib({}) = {}", n, fib_recursive(n));
    } else {
        println!("递归版:n > 40 时耗时过长,跳过");
    }
}

代码要点:

使用 std::io 读取用户输入。

使用 parse() 将字符串转换为数字,match 处理解析错误。

递归版展示函数递归,迭代版展示 for 循环和变量可变性。

使用 u64 避免 i32 在 n > 46 时溢出。

七、小结

变量默认不可变:let 声明不可变变量,let mut 声明可变变量。

遮蔽(Shadowing) :用同名新变量覆盖旧变量,可以改变类型。

基本类型:整数(默认 i32)、浮点数(默认 f64)、布尔、字符(4 字节 Unicode)。

复合类型:元组(不同类型组合)、数组(同类型固定长度)。

函数:参数必须标注类型,最后表达式作为返回值(不加分号)。

表达式 vs 语句:表达式有返回值,语句无返回值;这是 Rust 的核心概念。

控制流:if 是表达式;loop 可以返回值;for 是最推荐的循环方式。

相关推荐
5335ld1 小时前
app版本更新(vue3+unibest+静默更新+强制更新)
开发语言·javascript·ecmascript
江华森1 小时前
02 Ubuntu 24.04 手把手部署 K8s 集群(K3s 选型 + 真实踩坑记录)
后端
袁因1 小时前
多租户隔离该做到哪一层:从连接池到 ORM 的取舍
后端
chushiyunen1 小时前
mockito笔记
java·开发语言·笔记
悟天特斯1 小时前
AI驱动的楼宇节能:从粗放管控到精准降碳的实践路径
开发语言·人工智能·python·物联网
传奇开心果编程1 小时前
【xilem0.4基础语法学与练】第22课:on_click / on_change 与手势系统的实战
学习·rust·前端框架
名字还没想好☜1 小时前
Python 用 tempfile 安全创建临时文件:NamedTemporaryFile、TemporaryDirectory 与别自己拼 /tmp 的坑
开发语言·后端·python·安全·编程语言
lvshuocool1 小时前
golang 多版本管理工具 -- g
开发语言·后端·golang