很久以前, 当牧羊人想要了解两个羊群是否相似时, 会挨个对它们进行比对. ---- 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)
}
内存视图

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()