Trait 系统

Trait 是 Fleet 语言的核心特性之一,提供了强大的抽象和多态能力。本章将详细介绍如何定义和使用 trait。

🎯 什么是 Trait

Trait 定义了一组方法签名,类似于其他语言中的接口。任何类型都可以实现 trait,从而获得这些方法的能力。

// 定义一个 trait
trait Display {
    fn show(self) -> str;
    fn format(self, prefix: str) -> str;
}

📝 定义 Trait

基本 Trait 定义

trait Drawable {
    fn draw(self);
    fn area(self) -> f64;
}

trait Comparable {
    fn compare(self, other: Self) -> int;
}

带返回类型的方法

trait Calculator {
    fn add(self, other: int) -> int;
    fn multiply(self, factor: f64) -> f64;
    fn to_string(self) -> str;
}

🏗️ 实现 Trait

使用 impl 关键字为类型实现 trait:

为结构体实现 Trait

struct Point {
    x: int,
    y: int,
}

struct Circle {
    radius: f64,
}

impl Display for Point {
    fn show(self) -> str {
        return "Point";
    }

    fn format(self, prefix: str) -> str {
        return prefix + "Point(" + self.x + ", " + self.y + ")";
    }
}

impl Display for Circle {
    fn show(self) -> str {
        return "Circle";
    }

    fn format(self, prefix: str) -> str {
        return prefix + "Circle(radius: " + self.radius + ")";
    }
}

Self 参数

self 参数代表当前实例,支持字段访问:

struct Rectangle {
    width: f64,
    height: f64,
}

impl Drawable for Rectangle {
    fn draw(self) {
        print("Drawing rectangle: " + self.width + "x" + self.height);
    }

    fn area(self) -> f64 {
        return self.width * self.height;
    }
}

fn main() {
    let rect = Rectangle { width: 10.0, height: 5.0 };
    rect.draw();
    let area = rect.area();
    print("Area: " + area);
}

🔄 多 Trait 实现

一个类型可以实现多个 trait:

trait Debug {
    fn debug(self) -> str;
}

trait Clone {
    fn clone(self) -> Self;
}

struct Person {
    name: str,
    age: int,
}

impl Display for Person {
    fn show(self) -> str {
        return self.name;
    }

    fn format(self, prefix: str) -> str {
        return prefix + self.name + " (" + self.age + " years old)";
    }
}

impl Debug for Person {
    fn debug(self) -> str {
        return "Person { name: \"" + self.name + "\", age: " + self.age + " }";
    }
}

fn main() {
    let person = Person { name: "Alice", age: 30 };

    // 使用不同 trait 的方法
    print(person.show());
    print(person.format(">>> "));
    print(person.debug());
}

🎨 实际应用示例

形状系统

trait Shape {
    fn area(self) -> f64;
    fn perimeter(self) -> f64;
    fn describe(self) -> str;
}

struct Rectangle {
    width: f64,
    height: f64,
}

struct Circle {
    radius: f64,
}

impl Shape for Rectangle {
    fn area(self) -> f64 {
        return self.width * self.height;
    }

    fn perimeter(self) -> f64 {
        return 2.0 * (self.width + self.height);
    }

    fn describe(self) -> str {
        return "Rectangle: " + self.width + "×" + self.height;
    }
}

impl Shape for Circle {
    fn area(self) -> f64 {
        return 3.14159 * self.radius * self.radius;
    }

    fn perimeter(self) -> f64 {
        return 2.0 * 3.14159 * self.radius;
    }

    fn describe(self) -> str {
        return "Circle: radius " + self.radius;
    }
}

fn print_shape_info(shape: impl Shape) {
    print(shape.describe());
    print("Area: " + shape.area());
    print("Perimeter: " + shape.perimeter());
}

fn main() {
    let rect = Rectangle { width: 5.0, height: 3.0 };
    let circle = Circle { radius: 2.0 };

    print_shape_info(rect);
    print("---");
    print_shape_info(circle);
}

序列化系统

trait Serializable {
    fn serialize(self) -> str;
    fn content_type(self) -> str;
}

struct User {
    id: int,
    name: str,
    email: str,
}

struct Product {
    sku: str,
    name: str,
    price: f64,
}

impl Serializable for User {
    fn serialize(self) -> str {
        return "{\"id\":" + self.id + ",\"name\":\"" + self.name + "\",\"email\":\"" + self.email + "\"}";
    }

    fn content_type(self) -> str {
        return "application/json";
    }
}

impl Serializable for Product {
    fn serialize(self) -> str {
        return "{\"sku\":\"" + self.sku + "\",\"name\":\"" + self.name + "\",\"price\":" + self.price + "}";
    }

    fn content_type(self) -> str {
        return "application/json";
    }
}

fn export_data(item: impl Serializable) {
    print("Content-Type: " + item.content_type());
    print("Data: " + item.serialize());
}

fn main() {
    let user = User { id: 1, name: "Alice", email: "alice@example.com" };
    let product = Product { sku: "ABC123", name: "Laptop", price: 999.99 };

    export_data(user);
    print("---");
    export_data(product);
}

🔍 Trait 方法调用

直接调用

fn main() {
    let rect = Rectangle { width: 4.0, height: 3.0 };

    // 直接调用 trait 方法
    let area = rect.area();
    let desc = rect.describe();

    print(desc + " has area " + area);
}

方法链调用

trait Chainable {
    fn step1(self) -> Self;
    fn step2(self) -> Self;
    fn finalize(self) -> str;
}

struct Builder {
    value: str,
}

impl Chainable for Builder {
    fn step1(self) -> Self {
        return Builder { value: self.value + "->step1" };
    }

    fn step2(self) -> Self {
        return Builder { value: self.value + "->step2" };
    }

    fn finalize(self) -> str {
        return self.value + "->done";
    }
}

fn main() {
    let builder = Builder { value: "start" };
    let result = builder.step1().step2().finalize();
    print(result);  // 输出: start->step1->step2->done
}

⚠️ 错误检测

Fleet 的类型检查器会验证 trait 实现的正确性:

缺少方法

trait Complete {
    fn method1(self) -> str;
    fn method2(self) -> int;
}

struct Incomplete {
    value: str,
}

// 错误:缺少 method2
impl Complete for Incomplete {
    fn method1(self) -> str {
        return self.value;
    }
    // 编译错误:impl Complete for Incomplete 缺少必需方法: method2
}

方法签名不匹配

trait Signature {
    fn process(self, input: str) -> int;
}

struct Wrong {
    data: str,
}

// 错误:方法签名不匹配
impl Signature for Wrong {
    fn process(self, input: int) -> str {  // 参数和返回类型都错误
        return "wrong";
    }
    // 编译错误:方法签名不匹配
}

🎯 最佳实践

1. 单一职责原则

// 好的设计:每个 trait 职责单一
trait Readable {
    fn read(self) -> str;
}

trait Writable {
    fn write(self, data: str);
}

// 而不是混合职责
trait BadFileHandler {
    fn read(self) -> str;
    fn write(self, data: str);
    fn compress(self);
    fn encrypt(self);
}

2. 合理的方法命名

trait WellNamed {
    fn calculate_area(self) -> f64;      // 清晰的动词+名词
    fn is_valid(self) -> bool;           // 布尔方法用 is_ 前缀
    fn to_string(self) -> str;           // 转换方法用 to_ 前缀
}

3. 适当的抽象级别

// 好的抽象:通用且有用
trait Drawable {
    fn draw(self);
    fn bounds(self) -> Rectangle;
}

// 避免过度具体的抽象
trait TooSpecific {
    fn draw_with_red_pen_on_white_paper(self);  // 太具体
}

🎯 练习

尝试实现以下 trait 系统:

  1. 媒体播放器:定义 Playable trait,为 AudioVideo 结构体实现
  2. 数据存储:定义 Storable trait,为不同的存储后端实现
  3. 数学运算:定义 Numeric trait,为数字类型实现基本运算

示例解答

// 1. 媒体播放器
trait Playable {
    fn play(self);
    fn pause(self);
    fn duration(self) -> int;
}

struct Audio {
    filename: str,
    length: int,
}

struct Video {
    filename: str,
    length: int,
    resolution: str,
}

impl Playable for Audio {
    fn play(self) {
        print("Playing audio: " + self.filename);
    }

    fn pause(self) {
        print("Pausing audio: " + self.filename);
    }

    fn duration(self) -> int {
        return self.length;
    }
}

impl Playable for Video {
    fn play(self) {
        print("Playing video: " + self.filename + " (" + self.resolution + ")");
    }

    fn pause(self) {
        print("Pausing video: " + self.filename);
    }

    fn duration(self) -> int {
        return self.length;
    }
}

fn main() {
    let song = Audio { filename: "song.mp3", length: 180 };
    let movie = Video { filename: "movie.mp4", length: 7200, resolution: "1080p" };

    song.play();
    movie.play();

    print("Song duration: " + song.duration() + " seconds");
    print("Movie duration: " + movie.duration() + " seconds");
}

📖 下一步

恭喜你掌握了 Fleet 的 trait 系统!这是 Fleet 最强大的特性之一。接下来,让我们学习 模式匹配,了解如何优雅地处理不同的数据情况。