跳到主要内容Rust 核心基础数据类型与变量系统详解 | 极客日志Rust
Rust 核心基础数据类型与变量系统详解
介绍 Rust 语言的基础数据类型与变量系统。涵盖标量类型(整数、浮点数、布尔值、字符)及复合类型(元组、数组、切片、字符串)。详细讲解变量声明、可变性控制、作用域规则及 Shadowing 机制。同时阐述类型转换方法(as 关键字、From/Into Traits),并通过几何计算、成绩处理、CSV 解析三个实战案例演示应用。最后总结常见问题及解决方案,为系统级编程打下基础。
樱花落尽20 浏览 Rust 核心基础数据类型与变量系统详解

一、学习目标与重点
1.1 学习目标
- 掌握基础数据类型:理解 Rust 所有标量类型(整数、浮点数、布尔值、字符)的定义、内存布局、范围限制与字面量写法
- 精通复合类型:熟练运用元组(Tuple)、数组(Array)、切片(Slice)处理复杂数据结构,理解其固定/动态特性
- 理解变量系统:深入掌握变量的声明、可变性控制、作用域规则,以及 Shadowing(变量隐藏)机制的应用场景
- 熟练类型转换:熟悉 Rust 严格的类型安全规则,掌握隐式转换(极少)与显式转换(as 关键字、From/Into Traits)的方法
- 实战应用:能结合真实场景运用基础数据类型编写简单但实用的代码,解决常见问题
1.2 学习重点
💡 三大核心难点:
- 整数类型的无符号/有符号区分与溢出处理机制
- 切片(Slice)的引用本质与生命周期依赖(简单引入,后续章节深入)
- Shadowing(变量隐藏)与
mut(可变性)的本质区别
⚠️ 三大高频错误点:
- 浮点数精度问题导致的逻辑错误
- 数组越界访问引发的编译/运行时崩溃
- 错误使用类型转换导致的未定义行为
二、Rust 的基础数据类型详解
Rust 的数据类型分为标量类型(单个值)和复合类型(多个值的组合),所有变量在编译期必须明确类型(强静态类型语言)。
2.1 标量类型
2.1.1 整数类型
Rust 提供了10 种整数类型,分为无符号整数(以 u 开头,只能表示正数和 0)和有符号整数(以 i 开头,能表示正负整数),每种类型的位数从 8 位到 128 位不等。
📊 整数类型表:
| 长度(位) | 无符号类型 | 有符号类型 | 最小值 | 最大值 |
|---|
| 8 | u8 | i8 | 0 | 255 |
| 16 | u16 | i16 | -32768 | 32767 |
| 32 | u32 | i32 | -2³¹ | 2³¹-1 |
| 64 | u64 | i64 |
| 平台相关 | usize | isize | 0 | 取决于 CPU 架构(x86 为 2³²,x86_64 为 2⁶⁴) |
let a = 10;
let b: u32 = 20;
let c = 0xff;
let d: u8 = 0x1A;
let e = 0o77;
let f = 0b1010;
let g = b'A';
let h = 1_000_000;
⚠️ 整数溢出问题:
Rust 在Debug 模式(默认)下会检查整数溢出,若发生溢出会直接崩溃(panic!);在 Release 模式下会默认启用'两补数环绕'(Wrapping)行为,但这是未定义行为(UB),建议显式处理溢出。
let x: u8 = 255;
match x.checked_add(1) {
Some(y) => println!("255 + 1 = {}", y),
None => println!("255 + 1 发生溢出"),
}
let y: u8 = 255;
let z = y.saturating_add(1);
let w: u8 = 255;
let v = w.wrapping_add(1);
2.1.2 浮点数类型
f32:32 位单精度浮点数(IEEE-754 标准),精度约 6-7 位小数
f64:64 位双精度浮点数(IEEE-754 标准),精度约 15-17 位小数(默认类型)
let a = 3.14;
let b: f32 = 2.718;
let c = 1e5;
let d: f32 = 2.5e-3;
⚠️ 浮点数精度问题:
浮点数无法精确表示所有十进制小数,会导致逻辑错误。
let x = 0.1 + 0.2;
println!("0.1 + 0.2 = {}", x);
println!("x == 0.3? {}", x == 0.3);
fn float_equals(a: f64, b: f64) -> bool {
(a - b).abs() < 1e-9
}
println!("x == 0.3? {}", float_equals(x, 0.3));
2.1.3 布尔类型
Rust 的布尔类型只有bool一种,值只能是true或false,占用1 字节内存(确保内存对齐)。
let is_true = true;
let is_false: bool = false;
if is_true {
println!("这是真的");
} else {
println!("这是假的");
}
let true_as_u8 = is_true as u8;
let false_as_u8 = is_false as u8;
2.1.4 字符类型
Rust 的字符类型是char,占用4 字节内存,支持Unicode 标量值(包括中文、日文、韩文、表情符号等,范围是 U+0000 到 U+10FFFF)。
let a = 'A';
let b: char = '中';
let c = '😀';
let a_as_u32 = a as u32;
println!("'A'的 Unicode 值是 U+{:X}", a_as_u32);
2.2 复合类型
2.2.1 元组类型
元组是固定长度、异质数据类型的组合,长度在声明时必须明确,且后续无法修改。
let t1: (i32, f64, bool) = (10, 3.14, true);
let t2 = ("hello", 'R', 2024);
println!("t1 的第 0 个元素:{}", t1.0);
println!("t1 的第 1 个元素:{}", t1.1);
println!("t1 的第 2 个元素:{}", t1.2);
let (x, y, z) = t2;
println!("x = {}, y = {}, z = {}", x, y, z);
let t3 = (5,);
let t4 = (5);
let t5 = ();
fn calculate_rectangle(width: u32, height: u32) -> (u32, u32) {
let area = width * height;
let perimeter = (width + height) * 2;
(area, perimeter)
}
fn main() {
let (area, perimeter) = calculate_rectangle(10, 5);
println!("面积:{},周长:{}", area, perimeter);
}
2.2.2 数组类型
数组是固定长度、同质数据类型的组合,存储在栈内存上,长度在声明时必须明确,且后续无法修改。
let a: [i32; 3] = [1, 2, 3];
let b = [10, 20, 30];
let c = [5; 4];
println!("a 的第 0 个元素:{}", a[0]);
println!("a 的第 2 个元素:{}", a[2]);
println!("c 的第 1 个元素:{}", c[1]);
println!("a 的长度:{}", a.len());
for element in a.iter() {
println!("{}", element);
}
let mut d = [1, 2, 3];
d[0] = 10;
println!("d 的第 0 个元素:{}", d[0]);
⚠️ 数组越界访问问题:
Rust 在编译期无法检查所有越界访问,但在运行期会检查,若发生越界会直接崩溃(panic!)。
let a = [1, 2, 3];
println!("a 的第 3 个元素:{}", a[3]);
2.2.3 切片类型
切片是数组或 Vec(动态数组)的动态视图,存储在栈内存上,包含两个部分:
- 指向数组/Vec 的指针(*const T 或 *mut T)
- 切片的长度(usize)
- 切片本身不存储数据,它只是一个引用
- 切片的长度在运行期可以动态变化,但不能超过原数组/Vec 的长度
- 切片的类型是
&[T](不可变切片)或&mut [T](可变切片)
let a = [1, 2, 3, 4, 5];
let slice1 = &a[1..3];
let slice2 = &a[..2];
let slice3 = &a[3..];
let slice4 = &a[..];
println!("slice1 的第 0 个元素:{}", slice1[0]);
println!("slice1 的长度:{}", slice1.len());
let mut b = [1, 2, 3, 4, 5];
let mut_slice = &mut b[1..3];
mut_slice[0] = 20;
println!("原数组 b:{:?}", b);
let mut vec = vec![10, 20, 30, 40, 50];
let vec_slice = &vec[2..4];
println!("Vec 切片:{:?}", vec_slice);
2.3 字符串类型
- &str:不可变字符串切片,存储在静态内存(如字符串字面量)或栈内存(指向其他字符串的切片)上,类型是
&str
- String:可变性字符串,存储在堆内存上,类型是
String
let s1: &str = "Hello, Rust!";
let s2 = "这是中文";
let s3 = String::new();
let s4 = String::from(s1);
let s5 = String::from("动态字符串");
let s6 = s4 + " " + s5.as_str();
println!("s6:{}", s6);
let s7 = format!("{} {} {}", s1, s5, 2024);
println!("s7:{}", s7);
let s8 = "Rust 语言开发";
println!("s8 的第 0-3 字节:{}", &s8[0..3]);
for c in s8.chars() {
println!("{}", c);
}
2.4 变量系统
2.4.1 变量声明
Rust 的变量声明必须使用let关键字,变量默认是不可变的(immutable)。
let x = 10;
let y = "hello";
let z: u8 = 255;
let w: String = String::from("Rust");
2.4.2 变量的可变性
如果需要修改一个变量,必须在声明时添加mut关键字(mutable)。
let mut x = 10;
x = 20;
println!("x:{}", x);
let mut s = String::from("hello");
s.push_str(", Rust!");
println!("s:{}", s);
2.4.3 变量的作用域
变量的作用域是从声明位置到所在代码块({})的结束位置。
fn main() {
let x = 10;
{
let y = 20;
println!("x + y = {}", x + y);
}
println!("x = {}", x);
}
2.4.4 Shadowing(变量隐藏)
Shadowing 是指在同一作用域或嵌套作用域内,用相同的变量名声明一个新变量,新变量会隐藏旧变量。
let x = 10;
let x = x + 5;
println!("x:{}", x);
let y = 20;
{
let y = "hello";
println!("内部 y:{}", y);
}
println!("外部 y:{}", y);
let z = "123";
let z = z.parse::<i32>().unwrap();
println!("z:{}", z);
| 特性 | Shadowing | mut |
|---|
| 变量名 | 可以相同 | 可以相同 |
| 变量类型 | 可以不同 | 必须相同 |
| 内存地址 | 可能不同 | 必须相同 |
| 作用域 | 同一或嵌套作用域 | 同一作用域 |
| 适用场景 | 需要类型转换或重新计算 | 需要修改值但类型不变 |
2.5 类型转换
Rust 是强静态类型语言,几乎不支持隐式类型转换,所有类型转换必须显式声明。
2.5.1 as 关键字转换
as关键字是 Rust 中最常用的类型转换方法,适用于基础数据类型之间的转换。
let a: i32 = 100;
let b: u8 = a as u8;
let c: i8 = 255 as i8;
let d: i32 = 5;
let e: f64 = d as f64;
let f: f64 = 3.14;
let g: i32 = f as i32;
let h: char = 'A';
let i: u32 = h as u32;
- 不能在非基础数据类型之间转换(如 String 和&str 不能用 as 转换)
- 浮点数转整数会截断小数部分,可能导致精度损失
- 大整数转小整数会发生两补数环绕,可能导致未定义行为
2.5.2 From/Into Traits 转换
From/Into Traits 是 Rust 中更安全、更通用的类型转换方法,适用于所有实现了这些 Traits 的类型。
- From Trait:用于将一个类型转换为另一个类型,定义方法是
from()
- Into Trait:是 From Trait 的逆操作,自动实现(只要实现了 From Trait,就会自动实现 Into Trait),定义方法是
into()
⌨️ From/Into Traits 转换示例:
let s1: &str = "hello";
let s2: String = String::from(s1);
let s3: String = s1.into();
use std::convert::TryFrom;
use std::convert::TryInto;
let a: i32 = 100;
let b: Result<u8, std::num::TryFromIntError> = u8::try_from(a);
match b {
Ok(x) => println!("a 转换为 u8:{}", x),
Err(e) => println!("转换失败:{}", e),
}
let c: Result<u8, std::num::TryFromIntError> = a.try_into();
struct Person {
name: String,
age: u32,
}
struct PersonInfo {
name: &'static str,
age: u32,
}
impl From<PersonInfo> for Person {
fn from(info: PersonInfo) -> Self {
Person {
name: String::from(info.name),
age: info.age,
}
}
}
let info = PersonInfo { name: "张三", age: 25 };
let person: Person = info.into();
println!("姓名:{},年龄:{}", person.name, person.age);
三、真实案例应用
3.1 案例 1:计算多种几何图形的面积
💡 场景分析:需要编写一个函数,根据不同的几何图形(圆形、矩形、三角形)计算面积,输入参数类型不同,但返回值都是浮点数。
#[derive(Debug)]
enum Shape {
Circle(f64),
Rectangle(f64, f64),
Triangle(f64, f64),
}
impl Shape {
fn area(&self) -> f64 {
match self {
Shape::Circle(radius) => std::f64::consts::PI * radius * radius,
Shape::Rectangle(width, height) => width * height,
Shape::Triangle(base, height) => base * height / 2.0,
}
}
}
fn main() {
let circle = Shape::Circle(5.0);
let rectangle = Shape::Rectangle(10.0, 5.0);
let triangle = Shape::Triangle(6.0, 4.0);
println!("圆形面积:{:.2}", circle.area());
println!("矩形面积:{:.2}", rectangle.area());
println!("三角形面积:{:.2}", triangle.area());
}
3.2 案例 2:处理用户输入的成绩数据
💡 场景分析:需要编写一个程序,读取用户输入的多个学生成绩(整数),计算平均分、最高分、最低分,并输出成绩的分布情况。
use std::io;
fn main() {
let mut scores = [0; 50];
let mut count = 0;
println!("请输入学生成绩(输入 -1 结束):");
loop {
let mut input = String::new();
io::stdin().read_line(&mut input).expect("读取输入失败");
let score: i32 = match input.trim().parse() {
Ok(num) => num,
Err(_) => {
println!("请输入有效的整数");
continue;
}
};
if score == -1 {
break;
}
if score < 0 || score > 100 {
println!("成绩必须在 0-100 之间");
continue;
}
if count >= 50 {
println!("最多只能输入 50 个成绩");
break;
}
scores[count] = score;
count += 1;
}
if count == 0 {
println!("没有输入成绩");
return;
}
let sum: i32 = scores[0..count].iter().sum();
let average = sum as f64 / count as f64;
let max_score = scores[0..count].iter().max().unwrap();
let min_score = scores[0..count].iter().min().unwrap();
let mut grade_counts = [0; 5];
for &score in scores[0..count].iter() {
match score {
0..=59 => grade_counts[0] += 1,
60..=69 => grade_counts[1] += 1,
70..=79 => grade_counts[2] += 1,
80..=89 => grade_counts[3] += 1,
90..=100 => grade_counts[4] += 1,
_ => (),
}
}
println!("成绩统计结果:");
println!("----------------------");
println!("学生人数:{}", count);
println!("平均分:{:.2}", average);
println!("最高分:{}", max_score);
println!("最低分:{}", min_score);
println!("----------------------");
println!("成绩分布:");
println!("0-59 分:{}人", grade_counts[0]);
println!("60-69 分:{}人", grade_counts[1]);
println!("70-79 分:{}人", grade_counts[2]);
println!("80-89 分:{}人", grade_counts[3]);
println!("90-100 分:{}人", grade_counts[4]);
}
3.3 案例 3:解析 CSV 格式的产品数据
💡 场景分析:需要编写一个程序,读取 CSV 格式的产品数据(包含产品名称、价格、库存),解析并存储在数组中,然后根据价格范围筛选产品。
#[derive(Debug)]
struct Product {
name: String,
price: f64,
stock: u32,
}
fn parse_product_csv(line: &str) -> Option<Product> {
let fields: Vec<&str> = line.split(',').collect();
if fields.len() != 3 {
return None;
}
let name = fields[0].trim().to_string();
let price: f64 = match fields[1].trim().parse() {
Ok(num) => num,
Err(_) => return None,
};
let stock: u32 = match fields[2].trim().parse() {
Ok(num) => num,
Err(_) => return None,
};
Some(Product { name, price, stock })
}
fn main() {
let csv_data = "苹果,5.99, 100\n香蕉,2.49, 200\n橙子,3.99, 150\n葡萄,9.99, 50\n西瓜,12.99, 30\n错误数据,abc, 10 ";
let mut products = Vec::new();
for line in csv_data.lines() {
let trimmed_line = line.trim();
if trimmed_line.is_empty() {
continue;
}
match parse_product_csv(trimmed_line) {
Some(product) => products.push(product),
None => println!("忽略无效行:{}", trimmed_line),
}
}
let filtered_products: Vec<&Product> = products
.iter()
.filter(|p| p.price >= 5.0 && p.price <= 10.0)
.collect();
println!("价格在 5-10 元之间的产品:");
println!("----------------------------------");
println!("产品名称\t价格\t库存");
println!("----------------------------------");
for product in filtered_products {
println!("{}\t{:.2}\t{}", product.name, product.price, product.stock);
}
}
四、常见问题与解决方案
4.1 整数溢出导致的崩溃
问题现象:在 Debug 模式下,整数溢出会导致程序崩溃(panic!)。
- 使用 checked_*系列方法(溢出时返回 None)
- 使用 saturating_*系列方法(溢出时取最大值/最小值)
- 使用 wrapping_*系列方法(强制两补数环绕)
4.2 浮点数精度导致的逻辑错误
问题现象:0.1+0.2 的结果不等于 0.3,比较浮点数相等时返回 false。
解决方案:比较两个浮点数的差值是否小于一个极小值(epsilon),如 1e-9。
4.3 数组越界访问导致的崩溃
- 在访问数组元素前,先检查索引是否在有效范围内
- 使用 get() 方法(返回 Option 类型),避免崩溃
let a = [1, 2, 3];
if let Some(element) = a.get(3) {
println!("{}", element);
} else {
println!("索引无效");
}
4.4 类型不匹配导致的编译错误
问题现象:函数接受的参数类型与传入的类型不一致,导致编译错误。
- 显式类型转换(使用 as 关键字或 From/Into Traits)
- 检查变量的类型注解是否正确
- 使用 dbg!() 宏打印变量类型(调试时)
let x = 10;
dbg!(x);
let y = x as u8;
dbg!(y);
五、总结与展望
5.1 总结
✅ 掌握了 Rust 所有标量类型的定义、内存布局、范围限制与字面量写法
✅ 熟练运用了元组、数组、切片处理复杂数据结构,理解了其固定/动态特性
✅ 深入理解了变量的声明、可变性控制、作用域规则,以及 Shadowing(变量隐藏)机制的应用场景
✅ 熟练掌握了 Rust 严格的类型转换方法,包括 as 关键字和 From/Into Traits
✅ 结合真实场景编写了三个实用的代码案例,解决了常见问题
5.2 展望
下一篇文章,我们将深入学习 Rust 的函数与流程控制,包括:
- 函数的定义、参数传递、返回值类型
- 条件判断(if/else、match)
- 循环(loop、while、for)
- 流程控制的高级应用(标签、break/continue、?运算符)
通过学习这些内容,我们将能够编写更复杂、逻辑更清晰的 Rust 程序。
相关免费在线工具
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
- Markdown转HTML
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
- HTML转Markdown
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
- JSON 压缩
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online
- JSON美化和格式化
将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online