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

image00848

用 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
    }
}

解释:

  1. main 函数中定义了两个字符串 string1 和 string2。
  2. 调用 longest 函数,并传入这两个字符串的切片。
  3. longest 函数比较这两个字符串切片的长度,并返回较长的那个切片。
  4. 最后打印返回的较长字符串。

由于返回的引用的生命周期是 '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
}

解析:

  1. RefCell:Rust 提供了 RefCell 类型来实现运行时的内部可变性。RefCell 包裹的值在不可变借用的情况下仍然可以被修改。而在编译时,RefCell 的借用规则也是通过运行时检查来保证,即只能有一个可变引用或多个不可变引用。
  2. Borrow 和 BorrowMut
• borrow() 用来获取不可变引用。
• borrow_mut() 用来获取可变引用。
  1. 运行时报错:如果借用了多个可变引用或混用了可变与不可变引用,会在运行时导致程序崩溃。

实用场景:

Rust 中的内部可变性与其他语言中的用途类似,主要用于维护和更新内部状态而保持外部接口的简洁和安全。这在许多应用场景下都具有实际的意义,特别是在需要保证线程安全和严格借用规则的情况下。