Rust的数据和变量

Rust由Mozilla于2006年发起,旨在提供内存安全、高性能的系统级编程语言。其核心优势在于无垃圾回收机制下保证内存安全,通过所有权系统避免数据竞争。适用于操作系统、浏览器组件、区块链和嵌入式开发等领域。连续多年获Stack Overflow最受欢迎语言,正成为C/C++的现代替代品,在系统编程和高性能应用领域前景广阔。


一、数据类型

Rust 的基本数据类型提供了丰富而精确的控制能力:

  1. 标量类型:表示单个值(整数、浮点数、布尔值、字符)

  2. 复合类型:组合多个值(元组、数组)

  3. 引用和指针:提供内存访问的不同方式

  4. 特殊类型:处理特殊情况(never 类型、单元类型)

这些基本类型是构建更复杂数据结构的基础,Rust 的强类型系统和所有权规则确保了内存安全和代码可靠性。理解这些基本类型对于编写高效、安全的 Rust 代码至关重要。

(一)基本类型

1、整数

类型有符号无符号大小范围(有符号)范围(无符号)
整型i8u88位-128 到 1270 到 255
整型i16u1616位-32,768 到 32,7670 到 65,535
整型i32u3232位-2³¹ 到 2³¹-10 到 2³²-1
整型i64u6464位-2⁶³ 到 2⁶³-10 到 2⁶⁴-1
整型i128u128128位-2¹²⁷ 到 2¹²⁷-10 到 2¹²⁸-1
指针大小isizeusize架构相关取决于目标平台(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、浮点数

类型大小精度范围近似值
f3232位单精度±1.2×10⁻³⁸ 到 ±3.4×10³⁸
f6464位双精度(默认)±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;                // 引用在栈,指向堆数据

(七)最佳实践

  1. 优先使用栈变量:对于小型、固定大小的数据

  2. 合理使用堆变量:对于大型或动态大小的数据

  3. 注意所有权转移:堆数据移动时只复制指针,不复制数据

  4. 利用作用域:让编译器自动管理内存释放

理解堆栈变量的区别是编写高效、安全 Rust 代码的基础。通过合理选择数据存储位置,可以优化程序性能并避免常见的内存错误。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值