很久以前, 当牧羊人想要了解两个羊群是否相似时, 会挨个对它们进行比对. ---- John C. Baez, James Dolan

Rust中的结构体地位等于类, 但是具体实现与类有差异.

结构体类型:

  • 具名字段型结构体: 每个字段均有名称
    • 使用场景: 用户更多使用字段名访问
  • 元组型结构体: 通过索引访问字段
    • 使用场景: 更多的模式匹配查找元素
  • 单元型结构体: 无字段

9.1 具名字段型结构体

命名约定:

  • 类型名称约定: 单词首字母大写
  • 字段和方法约定: 小写并使用_分隔

结构体初始化: 使用:进行赋值, 类似于键值对

可见度:

  • 字段默认私有, 需要pub声明公开
  • 使用结构体表达式 (就是初始化赋值)时, 必须所有字段均为pub
  • 带私有字段的结构体初始化必须通过公共的类型关联函数 (比如: Vec::new())
  • 同类型初始化: 通过.. EXPR根据现有对象对其他字段进行初始化
  • let mut broom1 = Broom { height: b.height / 2, .. b };
#[derive(Copy, Clone)]
enum BroomIntent { FetchWater, DumpWater }

struct Broom {
    name: String,
    height: u32,
    health: u32,
    position: (f32, f32, f32),
    intent: BroomIntent
}

fn chop(b: Broom) -> (Broom, Broom) {
    // String没有实现Copy特型, 转移所有权被broom1, 其余字段与b相同
    let mut broom1 = Broom { height: b.height / 2, .. b };
    let mut broom2 = Broom { name: broom1.name.clone(), .. broom1 };

    broom1.name.push_str(" I");
    broom2.name.push_str(" II");

    (broom1, broom2)
}

fn main() {
    let hokey = Broom {
        name: "Hokey".to_string(),
        height: 60,
        health: 100,
        position: (100.0, 200.0, 0.0),
        intent: BroomIntent::FetchWater
    };

    let (hokey1, hokey2) = chop(hokey);

    assert_eq!(hokey1.name, "Hokey I");
    assert_eq!(hokey2.name, "Hokey II");

    assert_eq!(hokey1.height, 30);
    assert_eq!(hokey2.height, 30);
}

9.2 元组型结构体

类似于元组的结构体

注意:

  • struct属于语句(Statement), 不返回任何值, 所以后续无需分号
  • 元组型结构体
struct Bounds (usize, usize);

类似于

type Bounds = (usize, usize);

访问元素

assert_eq!(b.0, 123);

创建元组型结构体类似于函数调用, 且底层确实是如此实现的
定义元组型结构体时, 隐式定义了一个函数
fn Bounds(elem0: usize, elem1: usize) -> Bounds {
    ...
}

9.3 单元型结构体

声明一个根本没有元素的结构体

struct Onesuch;

单元型结构体不占用内存, 类似于(), 不存在任何值, 这也导致无值是单元型结构体的唯一值.

let o = Onesuch;

使用场景:

  • 范围运算符
  • 特型处理 (见chapter11)

9.4 结构体布局

特型:

  • Rust不保证在内存中字段的排列
  • Rust保证字段值存储在结构体自身的内存块中
  • 使用#[repr(C)]要求Rust以兼容C和C++的方式对结构体进行布局
struct GrayscaleMap {
    pixels: Vec<u8>,
    size: (usize, usize)
}

内存视图

875

9.5 用impl定义方法

特型:

  • 类型可以拥有多个impl类型, 但是必须定义在同一个crate中

impl相比于类定义优势:

  • 分离字段和函数可以更加清晰
  • 用于特型

9.5.0 impl基本使用

  • Rust不会在结构体中定义方法, 而是在外部使用impl块

关联函数:

  • 自由函数: 不属于任何结构体, 枚举, impl块的函数
  • 关联函数
    • 类型关联函数: 属于某个 struct、enum 或 trait,但不依赖self的函数
    • 方法: 必须有self参数
      • &self&mut self: 不会获取所有权
      • self: 会获取所有权, 使用后对象转变为未初始化状态
pub struct Queue {
    older: Vec<char>,
    younger: Vec<char>,
}

impl Queue {
    pub fn push(&mut self, c: char) {
        self.younger.push(c);
    }

    pub fn pop(&mut self) -> Option<char> {
        if self.older.is_empty() {
            if self.younger.is_empty() {
                return None;
            }

            use std::mem::swap;
            swap(&mut self.older, &mut self.younger);
            self.older.reverse();
        }

        self.older.pop()
    }
}

fn main() {

}

9.5.1 以Box, Rc或Arc形式传入self

self参数类型可以是: (注意: 大写Self表示类型)

  • &Self
  • &mut Self
  • Self
  • Box<Self>
  • Rc<Self>
  • Arc<Self>

针对某些方法确实需要获取指向Self的指针所有权

impl Node {
    fn append_to(self: Rc<Self>, paretn: &mut Node) {
        parent.children.push(self);
    }
}

9.5.2 类型关联函数

链接: [(Rust程序设计) Chapter9-结构体#9 5 0 impl基本使用]((Rust程序设计) Chapter9-结构体#9 5 0 impl基本使用 "wikilink")

常见的类型关联函数:

  • 构造函数 (一般使用new作为构造函数)
pub fn new() -> Queue {
    Queue { older: Vec::new(), younger: Vec::<char>::new() }
}

fn main() {
    let mut q = Queue::new();
}

9.6 关联常量

注意: 常量可以是当前类型

pub struct Vector2 {
    x: f32,
    y: f32,
}

impl Vector2 {
    const ZERO: Vector2 = Vector2 { x: 0.0, y: 0.0 }; // Vector2类型的常量
    const UNIT: Vector2 = Vector2 { x: 1.0, y: 0.0 }; // Vector2类型的常量
}

fn main() {
    println!("{}", Vector2::ZERO.x);
    println!("{}", Vector2::UNIT.y);
}

9.7 泛型结构体

构成:

  • <T>: 类型参数
  • impl<T> Queue<T>: 对于任意元素类型T, 在Queue<T>上实现函数.
    • 为什么需要impl<T>: 说明该块下的函数都是泛型的
    • impl Queue<f64>: 针对f64类型专门实现的函数
pub struct Queue<T> {
    older: Vec<T>,
    younger: Vec<T>,
}

impl<T> Queue<T> {
    pub fn new() -> Self { // 相当于Self: Queue<T>, Rust会自动推断类型
        Queue { older: Vec::new(), younger: Vec::new() }
    }

    pub fn push(&mut self, t: T) {
        self.younger.push(t);
    }

    pub fn is_empty(&self) -> bool {
        self.older.is_empty() && self.younger.is_empty()
    }
}

9.8 带生命周期参数的泛型结构体

在chapter5中学习到: 结构体中带有引用, 则结构体必须带有生命周期参数

struct Extrema<'a> {
    greatest: &'a i32,
    least: &'a i32,
}

fn find_extrema<'s>(slice: &'s [i32]) -> Extrema<'s> {
    let mut greatest = &slice[0];
    let mut least = &slice[0];

    for i in 1..slice.len() {
        if slice[i] < *least { least = &slice[i]; }
        if slice[i] > *greatest { greatest = &slice[i]; }
    }

    Extrema { greatest, least }
}

fn main() {

}

9.9 带常量参数的泛型结构体

常量参数的泛型结构体:

  • 常量N用于决定数组长度
  • impl<const N: usize> Polynomial<N>: 后续直接用<N>即可
  • 限制: 由于常量参数是较新的特型, 所以存在限制是: 使用时只能用N, 不能时N的表达式
struct Polynomial<const N: usize> {
    coefficients: [f64; N]
}

impl<const N: usize> Polynomial<N> {
    fn new(coefficients: [f64; N]) -> Polynomial<N> {
        Polynomial { coefficients }
    }

    fn eval(&self, x: f64) -> f64 {
        let mut sum = 0.0;

        for i in (0..N).rev() {
            sum = self.coefficients[i] + x * sum;
        }

        sum
    }
}

fn main() {

}

各类参数的排列顺序:

  • 生命周期参数
  • 类型参数
  • 常量参数
struct LumpOfReferences<'a , T, const N: usize> {
    the_lump: [&'a T; N]
}

9.10 让结构体类型派生自某些公共特型

你希望结构体具有某些特性, 或者说功能: 自动复制, 直接传入println!打印等

Rust可以自动为你实现他们, 而且结果精确无误, 只需要添加属性即可. (前提是: 结构体每个字段均实现了该特型)

#[derive(Copy, Clone, Debug, PartialEq)]
struct Point {
    x: f64,
    y: f64
}

9.11 内部可变型

蜘蛛机器人结构体

pub struct SpiderRobot {
    species: String,
    web_enabled: bool,
    leg_devices: [fd::FileDesc; 8],
}

蜘蛛机器人内部多个系统: 均需要指向SpiderRobot的指针

pub struct SpiderSenses {
    rotbot: Rc<SpiderRobot>,
    eyes: [Camera; 32],
    motion: Accelerometer,
}

问题: Rc指针相当于读者, 只读, 导致某些需要修改Rc指向的对象的操作无法进行.

解决方法:

  • Cell<T>
  • RefCell<T>
  • 注释: cell含义是隔离室, 表示在不可变整体下, 分离出一个可变单元 (也就是内部可变性)

Cell<T>

  • 定义: 包含单个私有值(类型T)的结构体
  • 功能: 即使对Cell<T>没有访问权限, 也可以通过Cell中的方法访问和设置其中的私有值
// 创建值
Cell::new(value)

// 访问
cell.get()

// 修改
cell.set(value)

实例

use std::cell::Cell;

pub struct SpiderRobot {
    counter: Cell<u32>
}

impl SpiderRobot {
    pub fn add_error(&self) {
        let n = self.counter.get(); // 获取值
        self.counter.set(n + 1); // 修改值
    }
}

RefCell<T>

  • 定义: 包含单个私有值(类型T)的结构体
  • 功能: 适用于value没有实现Copy特型的情况, 获取可变引用
  • 借用类型: Ref<T>RefMut<T>与正常引用使用没有区别
  • 特殊点:
    • 仅当打破"可变引用必须独占"的规则时: 触发panic
// 创建
RefCell::new(value)

// 借用共享引用
// 返回Ref<T>, 本质是共享引用
ref_cell.borrow()

// 借用共享引用
// 返回RefMut<T>, 本质是可变引用
ref_cell.borrow_mut()