bigywater.tech
Rust · 7

Tuple、Struct 与 Enum

元组、结构体和枚举是 Rust 组织数据的三种基本形状。这篇不重复语法,而是用真实输出回答:它们各占多少内存、编译器会不会偷偷重排字段、枚举的「标签」要付出多少空间,以及为什么不能像访问字段一样访问枚举里的数据。

8 vs 12
{u8,u32,u8} 默认布局与 repr(C) 的字节数(字段之和 6)
24 字节
enum Shape(最大变体 16 字节 + 标签)
0 字节
单元类型 () 与单元结构体

Tuple

实测 · 本机执行输出
  源码:
      1 | use std::mem::size_of;
      2 | fn main() {
      3 |     let t = (500, 6.4, 'z');
      4 |     let (a, b, c) = t;                       // 解构
      5 |     println!("t.0 = {}, t.1 = {}, t.2 = {}", t.0, t.1, t.2);
      6 |     println!("解构:a = {a}, b = {b}, c = {c}");
      7 |     println!("(i32, f64, char) 占 {} 字节,字段字节数之和 = {}", size_of::<(i32, f64, char)>(), size_of::<i32>() + size_of::<f64>() + size_of::<char>());
      8 |     let unit = ();
      9 |     println!("单元类型 () = {:?},占 {} 字节", unit, size_of::<()>());
     10 |     let one = (1,);                          // 单元素元组要带逗号
     11 |     println!("(1,) = {:?},(1) 是普通括号表达式 = {}", one, (1));
     12 | }
  编译:成功
  运行(退出码 0):
    t.0 = 500, t.1 = 6.4, t.2 = z
    解构:a = 500, b = 6.4, c = z
    (i32, f64, char) 占 16 字节,字段字节数之和 = 16
    单元类型 () = (),占 0 字节
    (1,) = (1,),(1) 是普通括号表达式 = 1

Struct

实测 · 本机执行输出
  源码:
      1 | use std::mem::size_of;
      2 | #[derive(Debug, Clone)] struct User { name: String, age: u8, active: bool }
      3 | struct Point(f64, f64);              // 元组结构体
      4 | struct Marker;                       // 单元结构体
      5 | struct Loose { a: u8, b: u32, c: u8 }
      6 | #[repr(C)] struct LooseC { a: u8, b: u32, c: u8 }
      7 | fn main() {
      8 |     let u1 = User { name: "ann".into(), age: 30, active: true };
      9 |     let u2 = User { name: "bob".into(), ..u1.clone() };   // 结构体更新语法
     10 |     println!("{:?}\n{:?}", u1, u2);
     11 |     let p = Point(1.0, 2.5); println!("Point.0 = {}, Point.1 = {}", p.0, p.1);
     12 |     println!("Marker 占 {} 字节", size_of::<Marker>());
     13 |     println!("Loose{{u8,u32,u8}} 默认布局:{} 字节;#[repr(C)]:{} 字节;字段之和 = 6", size_of::<Loose>(), size_of::<LooseC>());
     14 | }
  编译:成功
  运行(退出码 0):
    User { name: "ann", age: 30, active: true }
    User { name: "bob", age: 30, active: true }
    Point.0 = 1, Point.1 = 2.5
    Marker 占 0 字节
    Loose{u8,u32,u8} 默认布局:8 字节;#[repr(C)]:12 字节;字段之和 = 6

字段重排的规则与 #[repr(C)] 的区别见Struct 对齐与 Padding。

Enum

实测 · 本机执行输出
  源码:
      1 | use std::mem::size_of;
      2 | #[derive(Debug)]
      3 | enum Shape { Circle(f64), Rect { w: f64, h: f64 }, Empty }
      4 | #[derive(Debug, Clone, Copy)] enum Color { Red, Green = 10, Blue }
      5 | fn area(s: &Shape) -> f64 { match s { Shape::Circle(r) => 3.14 * r * r, Shape::Rect { w, h } => w * h, Shape::Empty => 0.0 } }
      6 | fn main() {
      7 |     for s in [Shape::Circle(2.0), Shape::Rect { w: 3.0, h: 4.0 }, Shape::Empty] { println!("{:?} 面积 = {}", s, area(&s)); }
      8 |     println!("size_of::<Shape>() = {} 字节(最大变体 Rect = 16 字节 + 标签)", size_of::<Shape>());
      9 |     println!("Color 是无数据枚举:size_of = {} 字节;Red = {},Green = {},Blue = {}", size_of::<Color>(), Color::Red as i32, Color::Green as i32, Color::Blue as i32);
     10 |     println!("size_of::<Option<u8>>() = {};size_of::<Option<u32>>() = {};size_of::<Option<f64>>() = {}", size_of::<Option<u8>>(), size_of::<Option<u32>>(), size_of::<Option<f64>>());
     11 | }
  编译:成功
  运行(退出码 0):
    Circle(2.0) 面积 = 12.56
    Rect { w: 3.0, h: 4.0 } 面积 = 12
    Empty 面积 = 0
    size_of::<Shape>() = 24 字节(最大变体 Rect = 16 字节 + 标签)
    Color 是无数据枚举:size_of = 1 字节;Red = 0,Green = 10,Blue = 11
    size_of::<Option<u8>>() = 2;size_of::<Option<u32>>() = 8;size_of::<Option<f64>>() = 16

枚举的内存布局(标签放在哪、niche 优化)见Enum 的内存布局与Option<&T> 的空指针优化。

枚举字段的访问

实测 · 本机执行输出
  源码:
      1 | enum Shape { Circle(f64), Empty }
      2 | fn main() {
      3 |     let s = Shape::Circle(1.0);
      4 |     println!("{}", s.0);
      5 | }
  编译:失败
    error[E0609]: no field `0` on type `Shape`
     --> <实验目录>/src/u7d.rs:4:22
      |
    4 |     println!("{}", s.0);
      |                      ^ unknown field
    
    error: aborting due to 1 previous error
    
    For more information about this error, try `rustc --explain E0609`.

取出枚举里的数据靠match 的穷尽匹配。

分步演示

Tuple、Struct、Enum
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / rustc 1.98.1(brew)/ cargo 1.98.1;单个文件用 rustc 直接编译,需要依赖时用 cargo(经代理从 crates.io 下载);计时用 std::time::Instant,数字每次运行会有小幅抖动。大小为 64 位目标、rustc 1.98.1 的默认布局结果;默认布局不是语言规范保证的,可能随编译器版本变化
    • 没有验证:#[repr(u8)] 枚举、带泛型参数的枚举大小、联合体 union、结构体字段重排在不同编译器版本间是否一致
    京ICP备2026067502号-1京公网安备11010502063487号