Skip to content

Rust 中的结构体 (Struct) 与特征 (Trait)

结构体 (Struct) 与其实现 (Impl)

在 Java 中,我们习惯于使用 class 来定义一个类,它包含了数据(字段)和行为(方法)。例如,我们可能这样定义一个 Rectangle 类来记录长宽并计算面积:

java
public class Rectangle {
    private double length;
    private double width;

    public Rectangle(double length, double width) {
        this.length = length;
        this.width = width;
    }

    public double getArea() {
        return length * width;
    }
}

在 Rust 中,我们使用 struct 关键字来定义一个结构体。它更像是 Java 类中只定义字段的部分,不包含方法:

rust
struct Rectangle {
    width: u32,
    height: u32,
}

当我们想给结构体添加方法时,我们需要使用 impl 关键字。你可以将 impl 块看作是对 Java 中指定某个类添加方法。例如,为 Rectangle 结构体添加一个计算面积的方法:

rust
impl Rectangle {
    fn area(&self) -> u32 {
        self.width * self.height
    }
}

这样就成功添加了一个 area 方法。传入的 &self 是一个指向当前结构体实例的引用,类似于 Java 中的 this。一个 struct 可以拥有多个 impl 块,这有助于将不同的功能分开实现,从而增加代码的可读性和可维护性。


结构体的字段更新语法

假设我们有一个 User 结构体:

rust
struct User {
    active: bool,
    username: String,
    email: String,
    sign_in_count: u64,
}

当我们想要创建一个新实例,但其中大部分字段与现有实例相同,只有少数字段不同时,可以使用结构体更新语法。这类似于 Java 中通过构造函数或 setter 方法部分复制对象,但在 Rust 中语法更简洁:

rust
let mut user1 = build_user(String::from("2494946808@qq.com"), String::from("huashen666"));
user1.email = String::from("huashen666@666.com");

let user2 = User {
    email: String::from("huashen@example.com"),
    ..user1 // 使用user1的其他字段
};

这里 ..user1 表示将 user1 中除了 email 字段以外的其他字段都复制到 user2 中。


特征 (Trait) - Rust 的接口与更多

trait 关键字是 Rust 中另一个非常重要的概念。它类似于 Java 中的接口,但功能更为强大。在 Java 中,有时会遇到一个接口设计得过于宽泛,导致某些实现者无需实现所有方法的情况,这时我们可能需要将接口拆分得更小。而 Rust 的 trait 提供了更强大的灵活性。

trait 是一种契约,它定义了类型可以拥有的共享行为。更重要的是,它可以提供默认实现

rust
pub trait Summary {
    fn summarize(&self) -> String {
        String::from("default summary")
    }
}

这个 Summary trait 定义了一个 summarize 方法,并提供了一个默认实现。这意味着,如果一个类型实现了 Summary trait 但没有覆盖 summarize 方法,它将直接使用这个默认实现。


为结构体实现特征 (Trait)

假设我们定义了一个 NewArticle 结构体:

rust
pub struct NewArticle {
    pub headline: String,
    pub location: String,
    pub author: String,
    pub content: String,
}

要为 NewArticle 实现 Summary trait,我们使用 impl Trait for Struct 的格式:

rust
impl Summary for NewArticle {
    fn summarize(&self) -> String {
        format!("{}:{}", self.headline, self.location)
    }
}

我们在这里覆盖了默认的 summarize 方法,提供了 NewArticle 特有的摘要实现。


特征约束与泛型

以下是作为 Java 开发者可能会感到“羡慕”的地方:Rust 允许你为实现了特定 trait 的泛型类型提供特定的 impl 实现。

考虑一个泛型结构体 Pair,它存储两个相同类型的泛型值 T

rust
struct Pair<T> {
    x: T,
    y: T,
}

我们可以像通常一样为它提供一个 new 方法:

rust
impl<T> Pair<T> {
    fn new(x: T, y: T) -> Self {
        Self {
            x,
            y,
        }
    }
}

现在,如果我们的需求是这个 Pair 只有在它的泛型类型 T 可以进行比较时才提供比较大小的功能。在 Java 中,这通常需要 T 继承 Comparable 接口,然后可能需要进行一些额外的类型处理。但在 Rust 中,你可以针对实现了特定 trait 的泛型特化提供 impl 实现

rust
impl<T: Display + PartialOrd> Pair<T> {
    fn cmp_display(&self) {
        if self.x >= self.y {
            println!("The largest member is x = {}", self.x);
        } else {
            println!("The largest member is y = {}", self.y);
        }
    }
}

这里:

  • Display 类似于 Java 中的 toString() 功能,允许类型被格式化输出。
  • PartialOrd 类似于 Java 中的 Comparable 接口,提供了部分排序能力(例如 ><>=<=)。

这意味着,如果你传入的类型 T 没有同时实现 DisplayPartialOrd 这两个 trait,那么这个 cmp_display 方法将不会被编译到 Pair 类型中。这给了我们极大的灵活性,不必让整个类都实现某个接口,而是可以特化地为某些满足条件的泛型类型提供特定的实现。


严格的类型与特征约束

Rust 的类型系统非常严格,这有助于提高代码的安全性。例如,如果我们想编写一个泛型函数 largest 来找出列表中最大的元素:

rust
fn largest<T>(list: &[T]) -> T {
    let mut largest = list[0];

    for &item in list.iter() {
        if item > largest { // 这里会报错!
            largest = item;
        }
    }

    largest
}

直接这样写会报错:

binary operation > cannot be applied to type T (rustc E0369)
hint: consider restricting type parameter `T` with trait `PartialOrd`: `: std::cmp::PartialOrd`

错误提示很明确:类型 T 没有实现 PartialOrd trait,因此不能进行 > 比较。我们加上 PartialOrd 约束:

rust
fn largest<T: PartialOrd>(list: &[T]) -> T {
    let mut largest = list[0]; // 这里仍然会报错!

    for &item in list.iter() {
        if item > largest {
            largest = item;
        }
    }

    largest
}

现在 list[0] 处会报新的错误:

cannot move out of type [T], a non-copy slice
cannot move out of here (rustc E0508)

这个错误的原因是,如果没有实现 Copy trait,list[0] 会发生所有权转移 (move)。list 是一个切片 (slice),是对数据集合的引用。当我们直接将 list[0] 赋值给 largest 时,如果 T 没有实现 Copy,那么所有权就会从 list[0] 转移到 largest,导致后续 list[0] 无法再被访问,这显然不是我们的本意。

所以,我们需要通过对泛型添加 CopyPartialOrd 约束来提供正确的实现,这样就可以顺利通过编译了:

rust
fn largest<T: PartialOrd + Copy>(list: &[T]) -> T {
    let mut largest = list[0];

    for &item in list.iter() {
        if item > largest {
            largest = item;
        }
    }

    largest
}

返回实现了特征的类型

类似于 Java 中方法可以返回一个接口类型,Rust 的方法也可以指定返回一个实现了特定 trait 的结构体。这通过 impl Trait 语法实现:

rust
fn return_summarizable() -> impl Summary {
    NewArticle {
        headline: String::from("Breaking News"),
        location: String::from("New York"),
        author: String::from("John Doe"),
        content: String::from("This is the content of the article."),
    }
}

这种语法表示函数会返回某个实现了 Summary trait 的类型,但调用者无需知道具体的底层类型是什么。