===^^===
下面是介绍《榴芒客服系统》的博客,它是我们工作室开发的在线客服系统,欢迎下载试用:
结构体将多个不同类型的值组合成单一的值,以便把它们作为一个单元来处理。
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 = &slice0;
let mut least = &slice0;
...
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不允许多个线程同时访问它们。
===^^===
下面是介绍《榴芒客服系统》的博客,它是我们工作室开发的在线客服系统,欢迎下载试用: