首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >02-Rust 教程 - 变量与基本类型

02-Rust 教程 - 变量与基本类型

作者头像
LarryLan
发布2026-03-30 18:07:31
发布2026-03-30 18:07:31
970
举报

Rust 教程 - 变量与基本类型

从"let 开始"到"类型推断",Rust 的变量比你想象的更有原则


🎬 引入

还记得你第一次学编程时写的"Hello World"吗?我猜大概率是这样的:

代码语言:javascript
复制
name = "Larry"
print(f"Hello, {name}!")

简单粗暴,对吧?变量就是个盒子,想装啥装啥,想改就改。

但到了 Rust 这里……事情变得有点"复杂"了。

代码语言:javascript
复制
let name = "Larry";
// name = "Bob";  // ❌ 编译器:我不允许!

我当时就想:这编译器是不是跟我过不去? 为啥不让我改变量?

别急,今天咱们就来聊聊 Rust 的变量和基本类型。你会发现,Rust 的"死板"背后,其实是一套精心设计的哲学。


📌 核心概念

变量:不是盒子,是标签

在其他语言里,变量像个盒子

代码语言:javascript
复制
┌─────────────┐
│   name      │  ← 盒子标签
│  "Larry"    │  ← 盒子里的东西
└─────────────┘

你想换东西?把旧的拿出来,塞个新的进去,完事。

但在 Rust 里,变量更像是个标签

代码语言:javascript
复制
  name
   ↓
┌─────────────┐
│  "Larry"    │  ← 数据在内存里
└─────────────┘

let name = "Larry"; 这行代码的意思是:给"Larry"这个数据贴上个叫 name 的标签

默认情况下,这个标签只能贴一次。你想重新贴?编译器会问你:"你确定吗?"

const vs static:常量的两种人格

Rust 有两种常量,就像人有两种性格:

类型

特点

使用场景

const

编译期确定,内联展开

配置值、魔法数字

static

运行时存在,有固定地址

全局状态、缓存

生活化类比:

  • const 像是印在 T 恤上的图案——编译时就固定了,每件 T 恤都一样
  • static 像是公告栏上的通知——有个固定位置,大家都去看同一个

类型系统:Rust 的"强迫症"

Rust 是强类型语言,而且大部分时候是静态类型。这意味着:

  1. 每个值都有类型,编译器知道
  2. 类型在编译期确定,运行时不变
  3. 类型不匹配?编译器直接报错,不跟你商量

但这不代表你要写一堆类型注解!Rust 有类型推断,就像个聪明的助手:

代码语言:javascript
复制
let age = ;        // 编译器:这是 i32
let price = 9.99;    // 编译器:这是 f64
let name = "Larry";  // 编译器:这是 &str

你不用告诉编译器类型,它自己能猜出来。但猜错了怎么办?咱们后面说。


💻 代码示例

基础示例:let 声明变量

代码语言:javascript
复制
fn main() {
    // 声明一个不可变变量
    let x = ;
    println!("x 的值是:{}", x);
    
    // 尝试修改?编译器不允许!
    // x = 6;  // ❌ 错误:cannot assign twice to immutable variable
    
    // 但可以用 shadowing(影子)"覆盖"
    let x = ;  // ✅ 这是新的 x,不是修改旧的
    println!("x 的新值是:{}", x);
    
    // 甚至可以改变类型!
    let x = "现在是字符串了";
    println!("x 变成:{}", x);
}

输出:

代码语言:javascript
复制
x 的值是:5
x 的新值是:6
x 变成:现在是字符串了

逐行解释:

  1. let x = 5; - 声明不可变变量 x,绑定到值 5
  2. x = 6; - ❌ 被注释掉,因为这会编译失败
  3. let x = 6; - ✅ 这是shadowing,新的 x 遮蔽了旧的 x
  4. let x = "..." - shadowing 甚至可以改变类型!(但实际开发中不建议这么玩)

错误示例:不可变变量的"尊严"

代码语言:javascript
复制
fn main() {
    let mut count = ;
    count = ;  // ✅ 可以修改,因为加了 mut
    
    let name = "Larry";
    // name = "Bob";  // ❌ 编译错误!
}

编译器错误信息:

代码语言:javascript
复制
error[E0384]: cannot assign twice to immutable variable `name`
 --> src/main.rs:6:5
  |
5 |     let name = "Larry";
  |         ---- first assignment to `name`
6 |     name = "Bob";
  |     ^^^^ cannot assign twice to immutable variable
  |
help: consider making this binding mutable
  |
5 |     let mut name = "Larry";
  |         +++

编译器在说什么人话:

"嘿,你这个 name 变量是不可变的(immutable),就像刻在石头上的字,不能改!" "如果你想改,当初声明时就加上 mut 关键字啊!"

修复方法:

代码语言:javascript
复制
let mut name = "Larry";  // 加上 mut
name = "Bob";  // ✅ 现在可以改了

const 常量示例

代码语言:javascript
复制
// 常量必须标注类型,且用大写字母命名
const MAX_POINTS: u32 = 100_000;
const PI: f64 = 3.14159265359;
const GREETING: &str = "你好,世界!";

fn main() {
    println!("最大分数:{}", MAX_POINTS);
    println!("圆周率:{}", PI);
    println!("{}", GREETING);
    
    // 常量可以在编译期计算
    const SECONDS_PER_DAY: u32 =  *  * ;
    println!("一天有 {} 秒", SECONDS_PER_DAY);
}

输出:

代码语言:javascript
复制
最大分数:100000
圆周率:3.14159265359
你好,世界!
一天有 86400 秒

关键点:

  1. const 必须标注类型(: u32
  2. 命名 convention 是全大写 + 下划线
  3. 值必须是编译期常量(不能是函数调用结果)
  4. 常量是内联展开的,每次使用都相当于复制一份值

static 静态变量示例

代码语言:javascript
复制
// static 变量有固定的内存地址
static LANGUAGE: &str = "Rust";
static mut COUNTER: u32 = ;  // ⚠️ 可变 static 需要 unsafe

fn main() {
    println!("语言:{}", LANGUAGE);
    
    // 访问可变 static 需要 unsafe 块
    unsafe {
        COUNTER += ;
        println!("计数器:{}", COUNTER);
    }
}

⚠️ 警告: 可变 static mut不安全的,因为多线程访问会导致数据竞争。实际开发中应该用 MutexAtomic 类型。

const vs static 对比:

代码语言:javascript
复制
const MAX: u32 = ;      // 编译期内联,每次使用都是新副本
static MIN: u32 = ;       // 运行时固定地址,全局唯一

fn main() {
    let a = MAX;  // 相当于 let a = 100;
    let b = MAX;  // 又是 let b = 100;
    
    let c = &MIN;  // 取 MIN 的地址
    let d = &MIN;  // 同一个地址!
    
    println!("{:p}", c);  // 打印地址
    println!("{:p}", d);  // 和上面一样!
}

标量类型:Rust 的基本款

整数类型
代码语言:javascript
复制
fn main() {
    // 有符号整数(可正可负)
    let a: i8 = -;      // 8 位,范围 -128 ~ 127
    let b: i16 = -;   // 16 位
    let c: i32 = -;  // 32 位,默认类型
    let d: i64 = -;  // 64 位
    let e: i128 = -;  // 128 位
    
    // 无符号整数(只能非负)
    let f: u8 = ;       // 8 位,范围 0 ~ 255
    let g: u32 = ;  // 32 位
    
    // 机器字长类型(取决于平台)
    let h: isize = ;    // 32 位或 64 位系统不同
    let i: usize = ;    // 常用于数组索引
    
    println!("i32 是默认类型:{}", c);
    println!("数组索引用 usize: {}", i);
}

新手坑点:整数溢出

代码语言:javascript
复制
fn main() {
    let x: u8 = ;
    // let y = x + 1;  // ❌ 编译期能检测到的溢出会报错
    
    // 运行时的溢出行为取决于编译模式:
    // - debug 模式:panic!
    // - release 模式:回绕(255 + 1 = 0)
    
    // 安全做法:用 checked_add
    match x.checked_add() {
        Some(result) => println!("结果是:{}", result),
        None => println!("溢出了!"),  // ✅ 会输出这个
    }
}
浮点数
代码语言:javascript
复制
fn main() {
    let pi: f32 = 3.14159;   // 32 位浮点数
    let e: f64 = 2.71828;    // 64 位浮点数,默认类型
    
    // 浮点数有科学计数法
    let avogadro: f64 = 6.022e23;  // 阿伏伽德罗常数
    
    println!("圆周率:{}", pi);
    println!("自然常数:{}", e);
    println!("阿伏伽德罗常数:{}", avogadro);
    
    // ⚠️ 浮点数比较要小心!
    let a = 0.1 + 0.2;
    let b = 0.3;
    println!("0.1 + 0.2 == 0.3 ? {}", a == b);  // 输出 false!
    
    // 正确做法:比较差值是否足够小
    let epsilon = 1e-10;
    println!("差值小于 epsilon ? {}", (a - b).abs() < epsilon);  // true
}

输出:

代码语言:javascript
复制
圆周率:3.14159
自然常数:2.71828
阿伏伽德罗常数:602200000000000000000000
0.1 + 0.2 == 0.3 ? false
差值小于 epsilon ? true

吐槽时间: 是的,0.1 + 0.2 != 0.3,这不是 Rust 的 bug,是所有浮点数的"特性"。因为二进制无法精确表示某些十进制小数。

布尔类型
代码语言:javascript
复制
fn main() {
    let is_rust_fun: bool = true;
    let is_monday: bool = false;
    
    // 布尔运算
    println!("Rust 有趣吗?{}", is_rust_fun);  // true
    println!("今天是周一吗?{}", is_monday);    // false
    println!("取反:{}", !is_monday);           // true
    
    // 逻辑运算
    let can_code = true;
    let has_coffee = false;
    
    println!("能写代码且有咖啡?{}", can_code && has_coffee);  // false
    println!("能写代码或有咖啡?{}", can_code || has_coffee);  // true
}
字符类型
代码语言:javascript
复制
fn main() {
    // char 是 Unicode 标量值,占 4 字节
    let letter: char = 'A';
    let chinese: char = '中';
    let emoji: char = '🦀';  // Rust 的吉祥物 crab
    
    // 转义字符
    let newline: char = '\n';
    let tab: char = '\t';
    let quote: char = '\'';
    
    println!("字母:{}", letter);
    println!("中文:{}", chinese);
    println!("emoji: {}", emoji);
    println!("Rust  crab: {}", '🦀');
    
    // ⚠️ 注意:char 是单个字符,不是字符串
    // let word: char = "Hello";  // ❌ 错误!这是 &str
}

输出:

代码语言:javascript
复制
字母:A
中文:中
emoji: 🦀
Rust  crab: 🦀

复合类型:多个值的组合

元组(Tuple)
代码语言:javascript
复制
fn main() {
    // 元组:固定长度,可以包含不同类型
    let point: (i32, i32) = (, );
    let person: (&str, i32, bool) = ("Larry", , true);
    
    // 访问元组元素:用索引
    let x = point.;  // 第一个元素
    let y = point.;  // 第二个元素
    println!("坐标:({}, {})", x, y);
    
    // 解构:更优雅的访问方式
    let (name, age, is_active) = person;
    println!("{} 今年 {} 岁", name, age);
    
    // 元组可以嵌套
    let nested: ((i32, i32), i32) = ((, ), );
    let ((a, b), c) = nested;
    println!("解构嵌套:{}, {}, {}", a, b, c);
    
    // 空元组叫"unit type",类似 ()
    let nothing: () = ();
}

输出:

代码语言:javascript
复制
坐标:(3, 4)
Larry 今年 25 岁
解构嵌套:1, 2, 3

实战场景:函数返回多个值

代码语言:javascript
复制
fn get_user_info() -> (&'static str, i32) {
    ("Larry", )
}

fn main() {
    let (name, age) = get_user_info();
    println!("{} 今年 {} 岁", name, age);
}
数组(Array)
代码语言:javascript
复制
fn main() {
    // 数组:固定长度,所有元素类型相同
    let numbers: [i32; ] = [, , , , ];
    
    // 简化写法:重复某个值
    let zeros: [i32; ] = [; ];  // [0, 0, 0, 0, 0]
    
    // 访问元素:用索引
    let first = numbers[];
    let third = numbers[];
    println!("第一个数:{}", first);
    println!("第三个数:{}", third);
    
    // 数组长度
    println!("数组长度:{}", numbers.len());
    
    // ⚠️ 越界访问会 panic!
    // let out_of_bounds = numbers[10];  // ❌ 运行时 panic
    
    // 安全访问:用 get 方法
    match numbers.get() {
        Some(value) => println!("值是:{}", value),
        None => println!("索引越界了!"),  // ✅ 会输出这个
    }
}

输出:

代码语言:javascript
复制
第一个数:1
第三个数:3
数组长度:5
索引越界了!

数组 vs Vec:

特性

数组 [T; N]

Vec Vec<T>

长度

编译期固定

运行时可变

位置

栈上

堆上

性能

更快

稍慢

使用场景

固定大小数据

动态集合

类型推断:Rust 的"读心术"

代码语言:javascript
复制
fn main() {
    // 编译器能推断出类型
    let x = ;           // i32
    let y = 3.14;         // f64
    let z = "hello";      // &str
    let is_true = true;   // bool
    
    // 但有些情况需要显式标注
    let a = "42".parse::<i32>().unwrap();  // 需要指定解析成什么类型
    
    // 或者用类型注解
    let b: i32 = "42".parse().unwrap();
    
    // 函数返回值也能推断
    fn add(x: i32, y: i32) -> i32 {
        x + y  // 返回类型可以推断,但建议显式标注
    }
    
    println!("x + y = {}", add(x, ));
}

什么时候需要显式标注类型?

  1. 编译器无法推断时(如 parse()
  2. 有多种可能类型时
  3. 想让代码更清晰时
  4. 定义常量时(const 必须标注)

🐛 常见坑点

坑点 1:忘记加 mut

错误代码:

代码语言:javascript
复制
let count = ;
count = ;  // ❌ 编译错误

错误信息:

代码语言:javascript
复制
error[E0384]: cannot assign twice to immutable variable `count`

解决方案:

代码语言:javascript
复制
let mut count = ;  // 加上 mut
count = ;  // ✅

我的心理活动: "我就想改个变量,咋这么多规矩?" 编译器的回答: "规矩是为了保护你!"

坑点 2:整数类型不匹配

错误代码:

代码语言:javascript
复制
fn main() {
    let a: i32 = ;
    let b: i64 = ;
    // let c = a + b;  // ❌ 类型不匹配
}

错误信息:

代码语言:javascript
复制
error[E0271]: type mismatch resolving `i32 as Add<i64>`

人话翻译: "i32 和 i64 不能直接相加,你得先转换!"

解决方案:

代码语言:javascript
复制
let c = a as i64 + b;  // 显式转换
// 或者
let c = a + b as i32;  // 但可能溢出!

坑点 3:数组越界

错误代码:

代码语言:javascript
复制
fn main() {
    let arr = [, , ];
    // 编译期能检测到的越界会报错
    // let x = arr[5];  // ❌
    
    // 但运行时的越界会 panic
    let index = ;
    let x = arr[index];  // ⚠️ 运行时 panic
}

运行时错误:

代码语言:javascript
复制
thread 'main' panicked at 'index out of bounds: the len is 3 but the index is 5'

最佳实践:

代码语言:javascript
复制
// 用 get 方法安全访问
match arr.get() {
    Some(value) => println!("{}", value),
    None => println!("越界了!"),
}

坑点 4:浮点数比较

错误代码:

代码语言:javascript
复制
fn main() {
    let a = 0.1 + 0.2;
    let b = 0.3;
    // if a == b {  // ⚠️ 可能永远不相等!
    //     println!("相等");
    // }
}

正确做法:

代码语言:javascript
复制
let epsilon = 1e-10;
if (a - b).abs() < epsilon {
    println!("在误差范围内相等");
}

坑点 5:const 和 let 混淆

错误代码:

代码语言:javascript
复制
// const MAX = 100;  // ❌ const 必须标注类型
// MAX = 200;        // ❌ const 不能修改

fn main() {
    let x = ;
    // const Y = 200;  // ❌ const 不能在函数内定义(实际可以,但少见)
}

正确做法:

代码语言:javascript
复制
const MAX: i32 = ;  // ✅ 标注类型

fn main() {
    let mut x = ;   // ✅ 可变变量
    x = ;
}

🎯 实战案例

案例 1:温度转换器

代码语言:javascript
复制
fn main() {
    // 摄氏温度转华氏温度
    let celsius: f64 = 25.0;
    let fahrenheit = celsius_to_fahrenheit(celsius);
    println!("{}°C = {}°F", celsius, fahrenheit);
    
    // 华氏温度转摄氏温度
    let fahrenheit: f64 = 77.0;
    let celsius = fahrenheit_to_celsius(fahrenheit);
    println!("{}°F = {}°C", fahrenheit, celsius);
}

fn celsius_to_fahrenheit(c: f64) -> f64 {
    c * 9.0 / 5.0 + 32.0
}

fn fahrenheit_to_celsius(f: f64) -> f64 {
    (f - 32.0) * 5.0 / 9.0
}

输出:

代码语言:javascript
复制
25°C = 77°F
77°F = 25°C

知识点: 浮点数运算、函数定义、类型推断

案例 2:学生成绩管理

代码语言:javascript
复制
fn main() {
    // 用元组存储学生信息
    let student1: (&str, i32, f64) = ("张三", , 95.5);
    let student2: (&str, i32, f64) = ("李四", , 87.3);
    
    // 解构获取信息
    let (name, age, score) = student1;
    println!("{} 今年 {} 岁,成绩:{:.1}", name, age, score);
    
    // 用数组存储多个成绩
    let scores: [f64; ] = [95.5, 87.3, 92.1, 88.9, 91.0];
    
    // 计算平均分
    let sum: f64 = scores.iter().sum();
    let average = sum / scores.len() as f64;
    println!("平均分:{:.2}", average);
    
    // 找最高分
    let max_score = scores.iter().max_by(|a, b| a.partial_cmp(b).unwrap()).unwrap();
    println!("最高分:{}", max_score);
}

输出:

代码语言:javascript
复制
张三 今年 18 岁,成绩:95.5
平均分:90.96
最高分:95.5

知识点: 元组、数组、迭代器、类型转换

案例 3:常量配置

代码语言:javascript
复制
// 用 const 定义配置常量
const MAX_CONNECTIONS: u32 = ;
const TIMEOUT_SECONDS: u64 = ;
const APP_NAME: &str = "我的应用";
const VERSION: &str = "1.0.0";

fn main() {
    println!("应用:{} v{}", APP_NAME, VERSION);
    println!("最大连接数:{}", MAX_CONNECTIONS);
    println!("超时时间:{} 秒", TIMEOUT_SECONDS);
    
    // 编译期计算
    const TIMEOUT_MS: u64 = TIMEOUT_SECONDS * ;
    println!("超时时间:{} 毫秒", TIMEOUT_MS);
}

输出:

代码语言:javascript
复制
应用:我的应用 v1.0.0
最大连接数:100
超时时间:30 秒
超时时间:30000 毫秒

最佳实践: 配置值用 const,方便统一管理和修改


🧠 思维导图

02-变量与基本类型
02-变量与基本类型

📝 小结

核心要点回顾:

  1. 变量默认不可变——想改就加 mut,这是 Rust 的安全哲学
  2. const vs static——编译期常量用 const,全局状态用 static
  3. 类型推断很智能——但 const 必须标注,parse() 需要指定
  4. 数组越界会 panic——用 get() 方法安全访问
  5. 浮点数比较要小心——别直接用 ==,用误差范围判断

金句时间:

"Rust 的变量不是盒子,是标签——标签贴上去,默认就不能撕。" "编译器不是跟你过不去,是在保护你免受自己的伤害。" "类型推断是 Rust 给你的温柔,类型安全是 Rust 给你的原则。"

下篇预告:

下一篇咱们聊聊流程控制——if/elseloopwhilefor。你以为这些很简单?Rust 的 if 可是能返回值的!敬请期待~

互动问题:

你在其他语言里遇到过哪些变量相关的坑?欢迎在评论区分享!


🔗 参考资料

  • Rust 官方文档 - 变量与可变性
  • Rust 官方文档 - 数据类型
  • Rust By Example - 变量
  • Rust 参考手册 - 常量与静态项
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2026-03-20,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Larry的Hub 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Rust 教程 - 变量与基本类型
    • 🎬 引入
    • 📌 核心概念
      • 变量:不是盒子,是标签
      • const vs static:常量的两种人格
      • 类型系统:Rust 的"强迫症"
    • 💻 代码示例
      • 基础示例:let 声明变量
      • 错误示例:不可变变量的"尊严"
      • const 常量示例
      • static 静态变量示例
      • 标量类型:Rust 的基本款
      • 复合类型:多个值的组合
      • 类型推断:Rust 的"读心术"
    • 🐛 常见坑点
      • 坑点 1:忘记加 mut
      • 坑点 2:整数类型不匹配
      • 坑点 3:数组越界
      • 坑点 4:浮点数比较
      • 坑点 5:const 和 let 混淆
    • 🎯 实战案例
      • 案例 1:温度转换器
      • 案例 2:学生成绩管理
      • 案例 3:常量配置
    • 🧠 思维导图
    • 📝 小结
    • 🔗 参考资料
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档