结构(Struct)

Rust 和 C# 中的结构体有一些相同点:

  • 它们都通过 struct 关键字定义,但是 Rust 中,struct 只能定义数据/字段。函数和方法所表示的行为都单独定义在一个 impl块 中。

  • Rust 中结构可以实现多个 trait,就像 C# 中的可以实现多个接口。

  • 它们无法派生类。

  • 它们默认分配在栈上,除非:

    • .NET 中,装箱或转换为接口类型。
    • Rust 中,包裹在 BoxRc/Arc 等智能指针内。

在 C# 中,struct 是建立 .NET 值类型,的办法,它们通常是一些领域特定的原始值,或是需要值等价性语义的复合类型。在 Rust 中,struct 是主要的建立数据类型的方式。(另一个是 enum)。

C# 中的 struct (或 record struct)默认拥有按值复制和值等价性语义。但在 Rust 中,这需要额外的一步,使用 #derive 属性 并列出要实现的 trait。

#[derive(Clone, Copy, PartialEq, Eq, Hash)]
struct Point {
    x: i32,
    y: i32,
}

C#/.NET 中的值类型通常被开发者设计为不可变的。这被认为是符合语义的最佳实践,不过语言本身并不阻止设计一个具有破坏性和原地修改行为的 struct。Rust 中也一样。类型必须被开发者刻意地设计为可变的。

由于 Rust 没有类,因此也没有通过派生类实现的类型架构,共享行为通过 trait,泛型,以及通过使用 trait objects 动态分发实现的多态。

假设下面这个 C# struct 表示一个矩形:

struct Rectangle
{
    public Rectangle(int x1, int y1, int x2, int y2) =>
        (X1, Y1, X2, Y2) = (x1, y1, x2, y2);

    public int X1 { get; }
    public int Y1 { get; }
    public int X2 { get; }
    public int Y2 { get; }

    public int Length => Y2 - Y1;
    public int Width => X2 - X1;

    public (int, int) TopLeft => (X1, Y1);
    public (int, int) BottomRight => (X2, Y2);

    public int Area => Length * Width;
    public bool IsSquare => Width == Length;

    public override string ToString() => $"({X1}, {Y1}), ({X2}, {Y2})";
}

Rust 的等价实现:

#![allow(dead_code)]

struct Rectangle {
    x1: i32, y1: i32,
    x2: i32, y2: i32,
}

impl Rectangle {
    pub fn new(x1: i32, y1: i32, x2: i32, y2: i32) -> Self {
        Self { x1, y1, x2, y2 }
    }

    pub fn x1(&self) -> i32 { self.x1 }
    pub fn y1(&self) -> i32 { self.y1 }
    pub fn x2(&self) -> i32 { self.x2 }
    pub fn y2(&self) -> i32 { self.y2 }

    pub fn length(&self) -> i32 {
        self.y2 - self.y1
    }

    pub fn width(&self)  -> i32 {
        self.x2 - self.x1
    }

    pub fn top_left(&self) -> (i32, i32) {
        (self.x1, self.y1)
    }

    pub fn bottom_right(&self) -> (i32, i32) {
        (self.x2, self.y2)
    }

    pub fn area(&self)  -> i32 {
        self.length() * self.width()
    }

    pub fn is_square(&self)  -> bool {
        self.width() == self.length()
    }
}

use std::fmt::*;

impl Display for Rectangle {
    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
        write!(f, "({}, {}), ({}, {})", self.x1, self.y2, self.x2, self.y2)
    }
}

需要注意 C# 的 struct 继承了来自 ObjectToString 方法,因此,它可以通过 重写 基类实现来提供自定义的字符串表示。由于 Rust 中没有继承,类型对一些 格式化 表示的支持是通过实现 Display trait。这样该结构的实例就可以参与格式化,例如被 println! 打印。如下:

fn main() {
    let rect = Rectangle::new(12, 34, 56, 78);
    println!("Rectangle = {rect}");
}