Rust 结构体
Rust 有 3 种结构体类型:具名字段型结构体、元组型结构体和单元型结构体。
具名字段型结构体
/// 由8位灰度像素组成的矩形
struct GrayscaleMap {
pixels: Vec<u8>,
size: (usize, usize)
}
Rust 中的约定是,所有类型(包括结构体)的名称都将每个单词的第一个字母大写(如 GrayscaleMap),这称为大驼峰格式(CamelCase 或 PascalCase)。字段和方法是小写的,单词之间用下划线分隔,这称为蛇形格式(snake_case)。
元组型结构体
struct Bounds(usize, usize);
//构造此类型的值与构造元组非常相似,只是必须包含结构体名称:
let image_bounds = Bounds(1024, 768);
//使用元组
assert_eq!(image_bounds.0 * image_bounds.1, 786432);
//元组可以是公共的
pub struct Bounds(pub usize, pub usize);
单元型结构体
这种结构体有点儿晦涩难懂,因为它声明了一个根本没有元素的结构体类型.
struct Onesuch;
结构体布局
在内存中,具名字段型结构体和元组型结构体是一样的:值(可能是混合类型)的集合以特定方式在内存中布局。
struct GrayscaleMap {
pixels: Vec<u8>,
size: (usize, usize)
}
用 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);
}
/// 从队列的前面弹出一个字符。如果确实有要弹出的字符,
/// 就返回`Some(c)`;如果队列为空,则返回`None`
pub fn pop(&mut self) -> Option<char> {
if self.older.is_empty() {
if self.younger.is_empty() {
return None;
}
// 将younger中的元素移到older中,并按照所承诺的顺序排列它们
use std::mem::swap;
swap(&mut self.older, &mut self.younger);
self.older.reverse();
}
// 现在older能保证有值了。Vec的pop方法已经
// 返回一个Option,所以可以放心使用了
self.older.pop()
}
}
在 impl 块中定义的函数称为关联函数,因为它们是与特定类型相关联的。与关联函数相对的是自由函数,它是未定义在 impl 块中的语法项。
impl Queue {
pub fn is_empty(&self) -> bool {
self.older.is_empty() && self.younger.is_empty()
}
}
以 Box、Rc 或 Arc 形式传入 self
关联常量
Rust 在其类型系统中的另一个特性也采用了类似于 C# 和 Java 的思想,有些值是与类型而不是该类型的特定实例关联起来的。
impl Engine {
pub const MAX_POWER: u8 = 10;
pub const FERRARI: Engine = Engine {
name: "V12".to_string(),
power: 12,
};
}
//使用
Engine::FERRARI.start();
泛型
例子
pub struct Engine<T> {
//! name of the engine
name: String,
//! power of the engine
power: u8,
//! 机油
oil: T,
}
//使用
impl<T> Engine<T> {
const MAX_POWER: u8 = 10;
pub const FERRARI: Engine<T> = Engine {
name: "V12".to_string(),
power: 12,
oil: Oil {
name: "Ferrari".to_string(),
weight: 10,
},
};
pub fn new(name: String, power: u8) -> Engine<T> {
Engine {
name,
power,
oil: "Hello",
}
}
pub fn start(&self) {
println!("Engine {} started power is {}", self.name, self.power);
}
pub fn stop(&self) {
println!("Engine {} stopped {} ", self.name, self.power);
}
}
带生命周期参数的泛型结构体
来确保返回的引用的生命周期与输入的引用一致。
struct Extrema<'elt> { greatest: &'elt i32, least: &'elt i32}
fn main() {
let string1 = String::from("abcd");
let string2 = "xyz";
let result = longest(string1.as_str(), string2);
println!("The longest string is {}", result);
}
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
解释:
- main 函数中定义了两个字符串 string1 和 string2。
- 调用 longest 函数,并传入这两个字符串的切片。
- longest 函数比较这两个字符串切片的长度,并返回较长的那个切片。
- 最后打印返回的较长字符串。
由于返回的引用的生命周期是 'a,它与输入的两个引用的生命周期一致,这样就保证了返回的引用在输入的引用生命周期内是有效的。
带常量参数的泛型结构体
/// N - 1 次多项式
struct Polynomial<const N: usize> {
/// 多项式的系数
///
/// 对于多项式 a + bx + cx<sup>2</sup> + ... + zx<sup>n-1</sup>,其第`i`个元素是 x<sup>i</sup>的系数
coefficients: [f64; N]
}
其实这块就可理解成 java 中 arrayList(3) 这样的逻辑
impl<const N: usize> Polynomial<N> {
fn new(coefficients: [f64; N]) -> Polynomial<N> {
Polynomial { coefficients }
}
/// 计算`x`处的多项式的值
fn eval(&self, x: f64) -> f64 {
// 秦九韶算法在数值计算上稳定、高效且简单:
// c<sub>0</sub> + x(c<sub>1</sub> + x(c<sub>2</sub> + x(c<sub>3</sub> + ... x(c[n-1] + x c[n]))))
let mut sum = 0.0;
for i in (0..N).rev() {
sum = self.coefficients[i] + x * sum;
}
sum
}
}
让结构体类型派生自某些公共特型
struct Point {
x: f64,
y: f64
}
但是,如果你要开始使用这种 Point 类型,很快就会发现它有点儿难用。像这样写的话,Point 不可复制或克隆,不能用 println!("{:?}", point); 打印,而且不支持 == 运算符和 != 运算符。
这些特性中的每一个在 Rust 中都有名称——Copy、Clone、Debug 和 PartialEq,它们被称为特型。第 11 章会展示如何为自己的结构体手动实现特型。但是对于这些标准特型和其他一些特型,无须手动实现,除非你想要某种自定义行为。Rust 可以自动为你实现它们,而且结果准确无误。只需将 #[derive] 属性添加到结构体上即可:
#[derive(Copy, Clone, Debug, PartialEq)]
struct Point {
x: f64,
y: f64
}
内部可变性
以下是一个使用 RefCell 来实现内部可变性的例子。在这个例子中,我们有一个 Temperature 结构体,它包含温度的值,并通过 RefCell 让这个值在不可变借用的情况下可以被修改。
use std::cell::RefCell;
struct Temperature {
celsius: RefCell<f64>,
}
impl Temperature {
fn new(celsius: f64) -> Self {
Temperature {
celsius: RefCell::new(celsius),
}
}
fn get_celsius(&self) -> f64 {
*self.celsius.borrow()
}
fn get_fahrenheit(&self) -> f64 {
*self.celsius.borrow() * 9.0 / 5.0 + 32.0
}
fn increase_temperature(&self, increment: f64) {
*self.celsius.borrow_mut() += increment;
}
}
fn main() {
let temp = Temperature::new(25.0);
println!("Temperature in Celsius: {}", temp.get_celsius()); // Temperature in Celsius: 25
println!("Temperature in Fahrenheit: {}", temp.get_fahrenheit()); // Temperature in Fahrenheit: 77.0
// Increase the temperature
temp.increase_temperature(5.0);
println!("Updated Temperature in Celsius: {}", temp.get_celsius()); // Updated Temperature in Celsius: 30
println!("Updated Temperature in Fahrenheit: {}", temp.get_fahrenheit()); // Updated Temperature in Fahrenheit: 86.0
}
解析:
- RefCell:Rust 提供了 RefCell 类型来实现运行时的内部可变性。RefCell 包裹的值在不可变借用的情况下仍然可以被修改。而在编译时,RefCell 的借用规则也是通过运行时检查来保证,即只能有一个可变引用或多个不可变引用。
- Borrow 和 BorrowMut:
• borrow() 用来获取不可变引用。
• borrow_mut() 用来获取可变引用。
- 运行时报错:如果借用了多个可变引用或混用了可变与不可变引用,会在运行时导致程序崩溃。
实用场景:
Rust 中的内部可变性与其他语言中的用途类似,主要用于维护和更新内部状态而保持外部接口的简洁和安全。这在许多应用场景下都具有实际的意义,特别是在需要保证线程安全和严格借用规则的情况下。
评论