news 2026/10/14 3:30:14

《Rust程序设计》学习笔记八:结构体

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
《Rust程序设计》学习笔记八:结构体

===^^===
下面是介绍《榴芒客服系统》的博客,它是我们工作室开发的在线客服系统,欢迎下载试用:
https://blog.csdn.net/look4liming/article/details/164755808https://blog.csdn.net/look4liming/article/details/164755808

结构体将多个不同类型的值组合成单一的值,以便把它们作为一个单元来处理。

Rust有3种结构体类型:具名字段型结构体、元组型结构体和单元型结构体。

具名字段型结构体的定义如下:

// 由8位灰度像素组成的矩形:

struct CrayscaleMap {

pixels: Vec<u8>,

size: (usize, usize)

}

Rust中约定,所有类型(包括结构体)的名称都将每个单词的第一个字母大写,这称为大驼峰格式(CamelCase或PascalCase)。字段和方法是小写的,单词之间用下划线分割,这称为蛇形格式(snake case)。

结构体表达式一类型名称开头,后跟一对花括号,其中列出了每个字段的名称和值:

fn new_map(size: (usize, usize), pixels: Vec<u8>) -> GrayscaleMap {

GrayscaleMap { pixels, size } // 这就是结构体表达式

}

访问结构体的字段,使用.运算符。

结构体默认情况下是私有的,仅在声明它们的模块及其子模块中可见,可以通过在结构体的定义前加上pub来使结构体在其模块外部可见。

结构体中的每个字段默认情况下也是私有的。

即使结构体声明为pub,它的字段也可以是私有的。

在结构体表达式中,如果具名字段后面跟着..EXPR,则任何未提及的字段都会从EXPR中获取它们的值。

struct Broom {

name: String,

height: u32,

health: u32,

position: (f32, f32, f32),

intent: BroomIntent

}

#[derive(Copy, Clone)]

enum BroomIntent {FetchWater, DumpWater}

fn chop(b: Broom) -> (Broom, Broom) {

let mut broom1 = Broom {height: b.height/2, ..b};

let mut broom2 = Broom {name: broom1.name().clone(), ..broom1};

(broom1, broom2)

}

元组型结构体,类似于元组:

struct Bounds(usize, usize);

构造此类型的值与构造元组非常相似,只是必须包含结构体名称:

let image_bounds = Bounds(1024, 768);

元组型结构体保存的值称为元素,就像元组的值一样:

assert_eq!(image_bounds.0, image_bounds.1);

元组型结构体的单个元素可以使公共的,也可以不是:

pub struct Bounds(pub usize, pub usize);

单元型结构体是没有元素的结构体类型:

struct Onesuch;

这种类型的值不占用内存,很像单元类型()。仅通过值的类型它就能知道关于值的所有信息。

在内存中,具名字段型结构体和元组型结构体是一样的:值的集合以特定的方式在内存中布局。

Rust没有具体承诺它将如何在内存中对结构体的字段或元素进行排序,然而,Rust承诺会将字段的值直接存储在结构体本身的内存块中。

可以使用#[repr(C)]属性要求Rust以兼容C和C++的方式对结构体进行布局。

Rust方在单独的impl块中。

impl块是fn定义的集合,每个定义都会成为块顶部命名的结构体类型上的方法:

pub struct Queue {

older: Vec<char>,

younger: Vec<char>

}

impl Queue {

pub fn push(&mut self, c: char) {

Self.younger.push(c);

}

}

在impl块中定义的函数称为关联函数,因为它们是与特定类型相关联的。与关联函数相对的是自由函数,它们是未定义在impl块中的语法项。

Rust会将调用关联函数的结构体值作为第一个参数传给方法,该参数必须具有特殊名称self。

允许省略类型,self、&slef或&mut self是self:Queue、self:&Queue或self:&mut Queue的简写形式。

也就可以使用完整形式,但几乎所有的Rust代码都会使用简写形式。

Rust方法中必须显式使用self来引用调用此方法的结构体值。

由于push需要修改Queue,因此它们都接受&mut self参数。然而,当调用方法时,不需要自己借用可变引用,常规的方法调用语法就已经隐式处理了这一点:

let mut q = Queue {older: Vec::new(), younger: Vec::new()};

q.push('0');

q.push('1');

只需要写q.push(...)就可以借入对q的可变引用。

如果方法不需要修改self,那么可以将其定义为接受共享引用:

impl Queue {

pub fn is_empty(&self) -> bool {

self.older.is_empty() && self.younger.is_empty();

}

}

同样,方法调用表达式知道要借用哪种引用:

assert!(q.is_empty());

或者,如果方法需要获取self的所有权,可以通过值来获取self:

impl Queue {

pub fn split(self) -> (Vec<char>, Vec<char>) {

(self.older, self.younger)

}

}

调用这个split方法看上去和调用其他方法是一样的:

let mut q = Queue {older: Vec::new(), younger: Vec::new()};

q.push('0');

q.push('1');

let (older, younger) = q.split();

方法的self参数也可以是Box<Self>类型、Rc<Self>类型或Arc<Self>类型。这种方法只能在给定的指针类型值上调用。调用该方法会将指针的所有权传给它。

如果方法期望通过引用接受self,那它在任何指针类型上调用时都可以正常工作:

let mut bq = Box::new(Queue::new());

// Queue::push需要&mut Queue,

// 但bq是Box<Queue>,

// Rust在调用期间从Box借入了&mut Queue:

bq.push('1');

impl块还可以定义根本不以self为参数的函数。这些函数仍然是关联函数,因为它们在impl块中,但它们不是方法,因为它们不接受self参数。为了与方法区分开来,称其为类型关联函数。

类型关联函数通常用于提供构造函数:

impl Queue {

pub fn new() -> Queue {

Queue { older: Vec::new(), younger: Vec::new() };

}

}

构造函数通常被惯例命名为new。

但是new这个名字并没有什么特别之处,它不是关键字。

类型通常还有其它关联函数作为构造函数,比如Vec::wwith_capacity。

类型可以有许多独立的impl块,但它们必须都在定义该类型的同一个crate中。不过,Rust允许将自己的方法附加到其它类型中。

将类型的方法与其定义分开有几个优点:

找出类型的数据成员变得更容易;

将方法提到impl块中可以让所有者3中结构体使用同一套语法;

impl语法可以巧妙地用于实现特型。

有些值是与类型而不是该类型的特定实例关联起来的,这些值叫作关联常量。

关联常量是常量值,它们通常用于表示指定类型下的常用值:

pub struct Vector2 {

x: f32,

y: f32

}

impl Vector2 {

const ZERO: Vector2 = Vector2 {x : 0.0, y: 0.0};

const UNIT: Vector2 = Vector2 {x : 1.0, y: 0.0};

}

这些值是和类型本身相关联的,可以在不必引用Vector2的任一实例的情况下使用它们。

这与关联函数非常类似,使用的名字是与其关联的类型名,后面跟着它们自己的名字:

let scaled = Vector2::UNIT.scaled_by(2.0);

Rust结构体可以是泛型的:

pub struct Queue<T> {

older: Vec<T>,

younger: Vec<T>

}

在泛型结构体定义中,尖括号(<>)中的类型名称叫作类型参数。

泛型结构体的impl块:

impl<T> Queue<T> {

pub fn new() -> Queue<T> {

Queue { older: Vec::new(), younger: Vec::new() };

}

pub fn push(&mut self, t: T) {

self.younger.push(t);

}

...

}

每个impl块,无论是不是泛型,都会将特殊类型的参数Self定义为我们要为其添加方法的任意类型。所以我们可以进一步缩写Queue::new的定义:

pub fn new() -> Self {

Queue { older: Vec::new(), younger: Vec::new() };

}

在new的函数体中,不需要在构造表达式中写入类型参数,这是Rust的类型推断在起作用:由于只有一种类型适用于该函数的返回值(Queue<T>),因此Rust为我们不起了该类型参数。但是,始终都要在函数签名和类型定义中提供类型参数。

在调用关联函数时,可以使用::<>表示法显式地提供类型参数:

let mut q = Queue::<char>::new();

不仅结构体可以是泛型的,枚举同样可以接受类型参数。

如果结构体类型包含引用,则必须为这些引用的生命周期命名:

struct Extrema<'elt> {

greatest: &'elt i32,

least: &'elt i32

}

上面代码的意义是:给定任意生命周期'elt,都可以创建Extrema<'elt>来持有对该生命周期的引用。

fn find_extrema<'s>(slice: &'s [i32]) -> Extrema<'s> {

let mut greatest = &slice[0];

let mut least = &slice[0];

...

Extrema { greatest, least }

}

因为返回类型的生命周期与参数的生命周期相同是很常见的情况,所以如果有一个显而易见的候选者,那么Rust就允许我们省略生命周期。因此也可以把find_extrema的签名写成如下形式:

fn find_extrema(slice: &[i32]) -> Extrema {

...

}

泛型结构体也可以接受常量值作为参数:

struct Polynomial<const N: usise> {

coefficients: [f64; N]

}

也可以在该类型的关联函数中使用参数N:

impl<const N: usize> Polynomial<N> {

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

Polynomial { coefficients }

}

...

}

与类型参数和生命周期参数一样,Rust通常也能为常量参数推断出正确的值:

let sine_poly = Polynomial::new(0.0, 1.0, 0.0, 1.0/120.0);

由于想Polynomial::new传递了一个包含4个元素的数组,因此Rust指导必须构造出一个Polynomial<4>。

常量泛型参数可以使任意整数类型、char或bool,不允许使用浮点数、枚举和其他类型。如果结构体还接受其它种类的泛型参数,则生命周期参数必须排在第一位,然后是类型,接下来是任何const值:

struct LumpOfReferences<'a, T, const N: usize> {

the_lump: [&'a T; N]

...

}

不支持表达式:

struct Polynomial<const N: usize> {

coefficients: [f64; N + 1] // 错误!

}

Copy、Clone、Debug和PartialEq,被称为特型。Rust可以自动实现它们,只需将#[derive]添加到结构体上即可:

#[derive(Copy, Clone, Debug, PartialEq)]

struct Point { x: f64, y: f64 }

这些特型都可以为结构体自动实现特型。前提是结构体的每个字段都实现了该特型。

只要实现某个特型就会自动让它成为公共特型,一次可复制性、可克隆性等都会成为该结构体的公共API的一部分。

内部可变性!

Rc代表引用计数(reference counting),Rc指向的值始终是共享的,一次将始终不可变。

不可变值中的一丁点可变数据,这种情况称为内部可变性。Rust提供了多种可选方案,两种最直观的类型是Cell<T>和RefCell<T>。

Cell<T>是包含类型T的单个私有值的结构体。特殊之处在于,即使对Cell本身没有mut访问权限,也可以获取和设置这个私有值字段。

// 创建新的Cell,将给定的value移动进去:

Cell::new(value)

// 返回cell中值的“副本”:

cell.get()

// 将给定的value存储在cell中,丢弃先前的值:

cell.set(value)

上面的set方法,接受不可变引用型的self:

fn set(&self, value: T)

迄今为止,Rust告诉我们如果想更改数据,就需要mut型访问。但出于同样的原因,这个不同寻常的细节正是Cell的全部意义所在。Cell只是改变不变性规则的一种安全方式。

Cell不允许在共享值上调用mut方法。.get()方法会返回Cell中值的副本,因为它仅在T实现了Copy特型时才有效。

RefCell<T>也是一种泛型类型,它包含类型T的单个值。RefCell支持借用对其T的引用。

// 创建新的RefCell,将Value移动进去:

RefCell::new(value)

// 返回Ref<T>,它本质上只是对存储在ref_cell中的值的共享引用。

// 如果该值已经被以可变的方式借出,会panic

ref_cell.borrow()

// 返回RefMut<T>,它本质上只是对存储在ref_cell中的值的可变引用。

// 如果该值已经被借出,会panic

ref_cell.borrow_mut()

// 行为与borrow()和borrow_mut()一样,但是返回值是Result。

// 如果该值已被以可变的方式借出,那么不会panic,而是返回Rrr值。

ref_cell.try_borrow()

Ref_cell.try_borrow_mut()

通常情况下,借用变量的引用时,Rust会在编译期进行检查,以确保在安全地使用该引用。如果检查失败,则会出现编译错误。RefCell会使用运行期检查强制执行相同的规则。如果违反了规则,就会收到panic。

Cell以及包含它的任意类型都不是线程安全的。因此Rust不允许多个线程同时访问它们。

===^^===
下面是介绍《榴芒客服系统》的博客,它是我们工作室开发的在线客服系统,欢迎下载试用:
https://blog.csdn.net/look4liming/article/details/164755808https://blog.csdn.net/look4liming/article/details/164755808

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/14 3:29:55

Rust GPUI 桌面应用开发入门:Menu 菜单

菜单往往是我们创建一个桌面应用时第一个需要编写的组件,本文我们系统地介绍一下如何在 GPUI 中创建菜单。特别说明:本文探讨的是 Zed 项目内而非 crates.io 上的 GPUI ,鉴于 Zed 项目迭代迅速且不保证 GPUI 模块向前兼容,验证和测试时要特别注意版本。本文基于的是 Zed 当…

作者头像 李华
网站建设 2026/10/14 3:29:55

工业控制系统深度解析:12 PLC扫描周期与实时性

第12章 PLC扫描周期与实时性 12.1 为什么必须理解PLC扫描周期? 上一章我们建立了一个重要概念: PLC的核心不是梯形图,而是把工业逻辑数字化。 但程序写出来以后,并不是立即产生动作。PLC内部还有一个关键过程: 扫描执行机制。 例如一个按钮控制电机: 按钮 → PLC程…

作者头像 李华
网站建设 2026/10/14 3:29:48

JDBC批量插入性能优化:rewriteBatchedStatements=true实战详解

先给个结论&#xff1a;rewriteBatchedStatementstrue是我目前在数据库批量写入优化里&#xff0c;效果最直观、改动成本最低的一个 JDBC 连接参数。很多项目把批量插入性能问题归结为网络差、数据库慢&#xff0c;其实很多时候只是 JDBC 驱动默认在“假装批处理”。这个参数是…

作者头像 李华
网站建设 2026/10/14 3:29:20

专有云DataWorks V3.12.0:本地化部署、数据集成与调度实践

简介&#xff1a;阿里云专有云企业版V3.12.0 DataWorks用户指南&#xff08;20200706版&#xff09;是一份面向企业数据工程师、架构师及平台管理员的大数据开发与管理参考手册&#xff0c;覆盖从登录控制台、创建工作空间到数据开发、数据治理、数据服务与数据安全的完整链路。…

作者头像 李华
网站建设 2026/10/14 3:29:13

Halcon 拟合生成算法详解与算子说明

1-4 Halcon 拟合生成算法详解与算子说明 本文档严格对照 Halcon 17.12 本地官方文档 doc/html/reference/operators/ 编写,全面覆盖拟合与生成相关的全部 28 个官方算子(矩阵数据拟合 9 个 + XLD 轮廓拟合 4 个 + 最小包络拟合 6 个 + 区域基元生成 5 个 + XLD 轮廓生成 4 个…

作者头像 李华
网站建设 2026/10/14 3:28:57

深入理解Servlet:生命周期、请求链路与会话管理实战解析

最近在帮一个转行的朋友补Java Web基础&#xff0c;他上来就问我&#xff1a;Servlet这东西还有必要学吗&#xff1f;Spring Boot一把梭不就行了&#xff1f;我想这也是很多同学的共同困惑。不夸张地说&#xff0c;在现在的技术社区里&#xff0c;Servlet已经被"框架原教旨…

作者头像 李华