Rust由Mozilla于2006年发起,旨在提供内存安全、高性能的系统级编程语言。其核心优势在于无垃圾回收机制下保证内存安全,通过所有权系统避免数据竞争。适用于操作系统、浏览器组件、区块链和嵌入式开发等领域。连续多年获Stack Overflow最受欢迎语言,正成为C/C++的现代替代品,在系统编程和高性能应用领域前景广阔。
一、数据类型
Rust 的基本数据类型提供了丰富而精确的控制能力:
-
标量类型:表示单个值(整数、浮点数、布尔值、字符)
-
复合类型:组合多个值(元组、数组)
-
引用和指针:提供内存访问的不同方式
-
特殊类型:处理特殊情况(never 类型、单元类型)
这些基本类型是构建更复杂数据结构的基础,Rust 的强类型系统和所有权规则确保了内存安全和代码可靠性。理解这些基本类型对于编写高效、安全的 Rust 代码至关重要。
(一)基本类型
1、整数
类型 | 有符号 | 无符号 | 大小 | 范围(有符号) | 范围(无符号) |
---|---|---|---|---|---|
整型 | i8 | u8 | 8位 | -128 到 127 | 0 到 255 |
整型 | i16 | u16 | 16位 | -32,768 到 32,767 | 0 到 65,535 |
整型 | i32 | u32 | 32位 | -2³¹ 到 2³¹-1 | 0 到 2³²-1 |
整型 | i64 | u64 | 64位 | -2⁶³ 到 2⁶³-1 | 0 到 2⁶⁴-1 |
整型 | i128 | u128 | 128位 | -2¹²⁷ 到 2¹²⁷-1 | 0 到 2¹²⁸-1 |
指针大小 | isize | usize | 架构相关 | 取决于目标平台(32/64位) | 取决于目标平台 |
// 整数类型示例
let decimal = 98_222; // 十进制
let hex = 0xff; // 十六进制
let octal = 0o77; // 八进制
let binary = 0b1111_0000; // 二进制
let byte = b'A'; // 字节 (u8)
let x: i32 = 42; // 类型注解
let y = 57u8; // 后缀类型注解
2、浮点数
类型 | 大小 | 精度 | 范围近似值 |
---|---|---|---|
f32 | 32位 | 单精度 | ±1.2×10⁻³⁸ 到 ±3.4×10³⁸ |
f64 | 64位 | 双精度(默认) | ±2.2×10⁻³⁰⁸ 到 ±1.8×10³⁰⁸ |
// 浮点类型示例
let x = 2.0; // 默认 f64
let y: f32 = 3.0; // 显式 f32
let z = 1.5e3; // 科学计数法 (1500.0)
let w = 2.5_f32; // 后缀类型注解
3、布尔类型
// 布尔类型示例
let t = true;
let f: bool = false;
let result = 5 > 3; // 比较表达式返回布尔值
4、字符
字符类型是 Unicode 标量值,占用 4 字节,可以表示比 ASCII 更丰富的字符集。
// 字符类型示例
let c = 'z';
let z = 'ℤ';
let heart_eyed_cat = '😻';
let ch: char = '中'; // 中文字符
(二)复合类型
复合类型可以将多个值组合成一个类型。
1. 元组(Tuple)
元组是将多个不同类型的值组合成一个复合类型的方式。
// 元组示例
let tup: (i32, f64, u8) = (500, 6.4, 1);
// 解构元组
let (x, y, z) = tup;
println!("y的值是: {}", y); // 输出: 6.4
// 通过索引访问
let five_hundred = tup.0;
let six_point_four = tup.1;
let one = tup.2;
// 空元组(单元类型)
let empty = ();
2. 数组(Array)
数组是包含多个相同类型元素的固定长度集合。
// 数组示例
let a = [1, 2, 3, 4, 5]; // 类型推断
let b: [i32; 5] = [1, 2, 3, 4, 5]; // 显式类型注解
let c = [3; 5]; // 等同于 [3, 3, 3, 3, 3]
// 访问数组元素
let first = a[0];
let second = a[1];
// 数组越界(编译时检查)
// let invalid = a[10]; // 编译错误
// 运行时检查(可能导致 panic)
let index = 10;
// let element = a[index]; // 运行时 panic
(三)引用和指针类型
1. 引用(References)
// 不可变引用
let x = 5;
let r = &x; // 创建不可变引用
println!("{}", *r); // 解引用
// 可变引用
let mut y = 10;
let rm = &mut y; // 创建可变引用
*rm += 1; // 通过引用修改值
2. 裸指针(Raw Pointers)
// 裸指针(通常在 unsafe 代码中使用)
let mut num = 5;
let r1 = &num as *const i32; // 不可变裸指针
let r2 = &mut num as *mut i32; // 可变裸指针
// unsafe {
// println!("r1 指向: {}", *r1);
// *r2 = 10;
// }
(四)特殊类型
1. Never 类型 (!
)
// Never 类型表示永远不会返回的计算
fn forever() -> ! {
loop {
// 无限循环,永不返回
}
}
// panic! 宏也返回 never 类型
fn panic_example() -> ! {
panic!("This function never returns");
}
2. 单元类型 (()
)
// 单元类型表示"无返回值"
fn no_return() -> () {
// 没有返回值的函数
println!("This function returns nothing");
}
// 表达式也可以有单元类型
let unit = ();
(五)类型推断和注解
// 类型推断
let x = 5; // 推断为 i32
let y = 2.5; // 推断为 f64
let z = true; // 推断为 bool
// 显式类型注解
let a: u8 = 255;
let b: f32 = 3.14;
let c: char = 'A';
let d: [i32; 3] = [1, 2, 3];
(六)类型转换
1. as
关键字
let x: i32 = 5;
let y: u32 = x as u32; // 显式转换
let f = 3.7_f32;
let i = f as i32; // 浮点到整数转换 (截断)
2. 类型强制转换(Coercion)
// &String 到 &str
let s = String::from("hello");
let slice: &str = &s; // 自动解引用强制转换
// 函数参数中的类型强制转换
fn takes_str(s: &str) {
println!("{}", s);
}
takes_str(&s); // &String 自动转换为 &str
(七)类型别名
// 创建类型别名
type Kilometers = i32;
type Thunk = Box<dyn Fn() + Send + 'static>;
let distance: Kilometers = 5;
let f: Thunk = Box::new(|| println!("hi"));
(八)常量与不可变变量
// 不可变变量(可以在运行时计算)
let x = 5;
// x = 6; // 错误:不可变变量不能修改
// 常量(必须在编译时已知,命名规范使用全大写)
const MAX_POINTS: u32 = 100_000;
const PI: f64 = 3.141592653589793;
二、堆变量与栈变量
理解 Rust 中哪些变量存储在堆上,哪些存储在栈上,是掌握内存管理的关键。
(一)栈变量(Stack Variables)
栈变量是存储在栈内存中的变量,具有固定大小且在编译时已知。
1. 基本数据类型(标量类型)
let x: i32 = 42; // 4字节整数 - 栈
let y: f64 = 3.14; // 8字节浮点数 - 栈
let is_valid: bool = true; // 1字节布尔值 - 栈
let character: char = 'R'; // 4字节Unicode字符 - 栈
2. 固定大小的数组
let arr: [i32; 3] = [1, 2, 3]; // 12字节数组 - 栈
let byte_array: [u8; 8] = [0; 8]; // 8字节数组 - 栈
3. 元组(当所有元素都是栈类型时)
let tuple: (i32, f64, bool) = (10, 2.5, false); // 栈
4. 实现了 Copy trait 的结构体
#[derive(Copy, Clone)]
struct Point {
x: i32,
y: i32,
}
let point = Point { x: 10, y: 20 }; // 8字节结构体 - 栈
5. 函数局部变量和参数
fn calculate(a: i32, b: i32) -> i32 { // a, b 和返回值都在栈上
let result = a + b; // result 在栈上
result
}
(二)堆变量(Heap Variables)
堆变量是存储在堆内存中的变量,通常大小可变或在编译时未知。
1、String 类型
let s = String::from("Hello, World!"); // 堆分配
// 栈上存储: 指针、长度、容量
// 堆上存储: 实际字符数据
2、Vec<T> 向量
let mut numbers = Vec::new(); // 堆分配
numbers.push(1);
numbers.push(2);
numbers.push(3);
// 栈上存储: 指针、长度、容量
// 堆上存储: 实际元素数组
3、Box<T> 智能指针
let boxed_int = Box::new(5); // 整数在堆上分配
let boxed_array = Box::new([0; 1000]); // 大数组在堆上分配
// 栈上存储: 指向堆数据的指针
4、其他集合类型
use std::collections::{HashMap, HashSet};
let mut scores = HashMap::new(); // 堆分配
scores.insert("Blue", 10);
let mut set = HashSet::new(); // 堆分配
set.insert(1);
set.insert(2);
5、动态大小的类型(DST)
// 特质对象
trait Drawable {
fn draw(&self);
}
let drawables: Vec<Box<dyn Drawable>> = vec![/*...*/]; // 堆分配
// 切片(引用堆数据)
let s = String::from("hello");
let slice: &str = &s[0..3]; // 栈上的切片引用堆上的数据
6. 包含堆数据的结构体
struct Person {
name: String, // 堆分配
age: i32, // 栈
hobbies: Vec<String>, // 堆分配
}
let person = Person {
name: String::from("Alice"), // 堆
age: 30, // 栈
hobbies: vec![String::from("reading")], // 堆
};
// 整个结构体实例在栈上,但包含指向堆数据的指针
(三)变量的混合存储
1. 结构体部分在堆,部分在栈
struct MixedData {
id: i32, // 栈
metadata: String, // 堆
values: Vec<f64>, // 堆
timestamp: [u8; 8], // 栈
}
let data = MixedData {
id: 1,
metadata: String::from("some data"),
values: vec![1.0, 2.0, 3.0],
timestamp: [0, 1, 2, 3, 4, 5, 6, 7],
};
// data实例在栈上,但包含指向堆数据的指针
2. 枚举变体可能包含堆数据
enum Message {
Quit, // 无数据
Move { x: i32, y: i32 }, // 栈数据
Write(String), // 堆数据
ChangeColor(i32, i32, i32), // 栈数据
}
let msg1 = Message::Quit; // 栈
let msg2 = Message::Write(String::from("hello")); // 栈上的枚举,包含指向堆的指针
(四)堆栈变量对比
特性 | 栈变量 | 堆变量 |
---|---|---|
存储位置 | 栈内存 | 堆内存 |
大小 | 固定,编译时已知 | 可变,运行时确定 |
分配速度 | 极快(移动指针) | 较慢(搜索可用内存) |
访问速度 | 极快(直接访问) | 较慢(指针间接访问) |
生命周期 | 与作用域同步 | 由所有权系统管理 |
内存管理 | 自动(栈指针) | 手动(通过所有权) |
示例 | 基本类型、数组 | String、Vec、Box |
(五)内存布局示例
fn main() {
// 栈变量
let x = 10; // 栈
let arr = [1, 2, 3]; // 栈
// 堆变量
let s = String::from("hello"); // 栈上有指针,数据在堆
let v = vec![1, 2, 3]; // 栈上有指针,数据在堆
// 包含堆数据的结构体
struct Data {
id: i32,
content: String,
}
let data = Data {
id: 1,
content: String::from("example"),
}; // data在栈上,content数据在堆
}
(六)如何判断变量在堆还是栈
1.基本规则:
-
固定大小的类型通常在栈上
-
动态大小的类型通常在堆上
-
实现了
Copy
trait 的类型在赋值时会被复制(栈行为)
2.使用 std::mem::size_of
use std::mem;
println!("i32 size: {}", mem::size_of::<i32>()); // 4字节 - 栈
println!("String size: {}", mem::size_of::<String>()); // 24字节 - 栈上的胖指针
3.注意引用和指针
let s = String::from("hello"); // 数据在堆,指针在栈
let s_ref = &s; // 引用在栈,指向堆数据
(七)最佳实践
-
优先使用栈变量:对于小型、固定大小的数据
-
合理使用堆变量:对于大型或动态大小的数据
-
注意所有权转移:堆数据移动时只复制指针,不复制数据
-
利用作用域:让编译器自动管理内存释放
理解堆栈变量的区别是编写高效、安全 Rust 代码的基础。通过合理选择数据存储位置,可以优化程序性能并避免常见的内存错误。