1.6 Struct 和 Trait 实战:面向对象编程的 Rust 式思考,构建可复用代码

1.6 Struct 和 Trait 实战:面向对象编程的 Rust 式思考,构建可复用代码

引言:Rust 的面向对象哲学

Rust 不是传统的面向对象语言,它没有类(class)和继承(inheritance)。但 Rust 通过结构体(struct)和 trait 提供了强大的代码组织和复用机制,这种方式在某些方面甚至比传统的面向对象更加灵活和强大。

本章将深入讲解 Rust 中的结构体和 trait,通过大量实战案例,让你理解 Rust 的"组合优于继承"的设计哲学,并掌握如何构建可复用、可扩展的代码。

结构体(Struct)基础

定义结构体

结构体是 Rust 中自定义数据类型的主要方式:

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

创建结构体实例

fn main() {
    let user1 = User {
        email: String::from("someone@example.com"),
        username: String::from("someusername123"),
        active: true,
        sign_in_count: 1,
    };
    
    // 访问字段
    println!("用户名: {}", user1.username);
    
    // 修改字段(需要 mut)
    let mut user2 = User {
        email: String::from("another@example.com"),
        username: String::from("anotherusername456"),
        active: true,
        sign_in_count: 1,
    };
    user2.sign_in_count += 1;
}

字段初始化简写

当字段名和变量名相同时,可以简写:

fn build_user(email: String, username: String) -> User {
    User {
        email,      // 等价于 email: email
        username,   // 等价于 username: username
        active: true,
        sign_in_count: 1,
    }
}

结构体更新语法

使用现有实例创建新实例:

fn main() {
    let user1 = User {
        email: String::from("someone@example.com"),
        username: String::from("someusername123"),
        active: true,
        sign_in_count: 1,
    };
    
    let user2 = User {
        email: String::from("another@example.com"),
        username: String::from("anotherusername456"),
        ..user1  // 其余字段从 user1 复制
    };
}

元组结构体

没有字段名的结构体:

struct Color(i32, i32, i32);
struct Point(i32, i32, i32);

fn main() {
    let black = Color(0, 0, 0);
    let origin = Point(0, 0, 0);
}

单元结构体

没有任何字段的结构体:

struct AlwaysEqual;

fn main() {
    let subject = AlwaysEqual;
}

方法(Methods)

定义方法

使用 impl 块为结构体定义方法:

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

impl Rectangle {
    // 关联函数(类似静态方法)
    fn new(width: u32, height: u32) -> Rectangle {
        Rectangle { width, height }
    }
    
    // 方法(第一个参数是 &self)
    fn area(&self) -> u32 {
        self.width * self.height
    }
    
    // 可变方法
    fn set_width(&mut self, width: u32) {
        self.width = width;
    }
    
    // 获取所有权的方法
    fn take_ownership(self) -> u32 {
        self.width
    }
    
    // 方法可以调用其他方法
    fn can_hold(&self, other: &Rectangle) -> bool {
        self.width > other.width && self.height > other.height
    }
}

fn main() {
    let rect1 = Rectangle::new(30, 50);
    println!("面积: {}", rect1.area());
    
    let rect2 = Rectangle::new(10, 40);
    println!("rect1 能容纳 rect2: {}", rect1.can_hold(&rect2));
}

多个 impl 块

可以为同一个结构体定义多个 impl 块:

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

impl Rectangle {
    fn perimeter(&self) -> u32 {
        2 * (self.width + self.height)
    }
}

枚举(Enum)

定义枚举

枚举允许你定义一个类型,它可以是多个变体之一:

enum IpAddrKind {
    V4,
    V6,
}

// 枚举可以包含数据
enum IpAddr {
    V4(String),
    V6(String),
}

// 每个变体可以有不同的数据类型
enum IpAddrAdvanced {
    V4(u8, u8, u8, u8),
    V6(String),
}

// 枚举变体可以是结构体
enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
    ChangeColor(i32, i32, i32),
}

使用枚举

fn main() {
    let four = IpAddrKind::V4;
    let six = IpAddrKind::V6;
    
    route(four);
    route(six);
    
    let home = IpAddr::V4(String::from("127.0.0.1"));
    let loopback = IpAddr::V6(String::from("::1"));
}

fn route(ip_kind: IpAddrKind) {
    // ...
}

Option 枚举

Rust 没有 null,而是使用 Option<T> 枚举:

enum Option<T> {
    Some(T),
    None,
}

fn main() {
    let some_number = Some(5);
    let some_string = Some("a string");
    let absent_number: Option<i32> = None;
    
    // 使用 match 处理
    match some_number {
        Some(value) => println!("值是: {}", value),
        None => println!("没有值"),
    }
}

Trait:定义共享行为

定义 Trait

Trait 定义了一组方法签名,任何实现了这个 trait 的类型都必须提供这些方法:

pub trait Summary {
    fn summarize(&self) -> String;
    
    // 默认实现
    fn summarize_author(&self) -> String {
        String::from("(Read more...)")
    }
}

实现 Trait

pub struct NewsArticle {
    pub headline: String,
    pub location: String,
    pub author: String,
    pub content: String,
}

impl Summary for NewsArticle {
    fn summarize(&self) -> String {
        format!("{}, by {} ({})", self.headline, self.author, self.location)
    }
}

pub struct Tweet {
    pub username: String,
    pub content: String,
    pub reply: bool,
    pub retweet: bool,
}

impl Summary for Tweet {
    fn summarize(&self) -> String {
        format!("{}: {}", self.username, self.content)
    }
    
    // 覆盖默认实现
    fn summarize_author(&self) -> String {
        format!("@{}", self.username)
    }
}

Trait 作为参数

// 方法 1: 使用 impl Trait(语法糖)
pub fn notify(item: impl Summary) {
    println!("突发新闻! {}", item.summarize());
}

// 方法 2: Trait Bound(更灵活)
pub fn notify<T: Summary>(item: T) {
    println!("突发新闻! {}", item.summarize());
}

// 多个 Trait Bound
pub fn notify(item: impl Summary + Display) {
    // ...
}

// 使用 where 子句(更清晰)
fn some_function<T, U>(t: T, u: U) -> i32
where
    T: Display + Clone,
    U: Clone + Debug,
{
    // ...
}

Trait 作为返回值

fn returns_summarizable() -> impl Summary {
    Tweet {
        username: String::from("horse_ebooks"),
        content: String::from("of course, as you probably already know, people"),
        reply: false,
        retweet: false,
    }
}

条件实现

可以根据 trait bound 条件实现方法:

use std::fmt::Display;

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

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

impl<T: Display + PartialOrd> Pair<T> {
    fn cmp_display(&self) {
        if self.x >= self.y {
            println!("最大的成员是 x = {}", self.x);
        } else {
            println!("最大的成员是 y = {}", self.y);
        }
    }
}

实战案例:构建一个博客系统

让我们构建一个完整的博客系统来实践结构体和 trait:

use std::fmt;

// Trait 定义
pub trait Publishable {
    fn publish(&self) -> String;
    fn preview(&self) -> String {
        String::from("(预览不可用)")
    }
}

pub trait Editable {
    fn edit(&mut self, content: String);
    fn get_content(&self) -> &str;
}

// 文章结构体
pub struct Article {
    title: String,
    content: String,
    author: String,
    published: bool,
}

impl Article {
    pub fn new(title: String, author: String) -> Self {
        Article {
            title,
            content: String::new(),
            author,
            published: false,
        }
    }
    
    pub fn get_title(&self) -> &str {
        &self.title
    }
    
    pub fn is_published(&self) -> bool {
        self.published
    }
    
    pub fn set_published(&mut self, published: bool) {
        self.published = published;
    }
}

impl Publishable for Article {
    fn publish(&self) -> String {
        if self.published {
            format!("文章《{}》已发布", self.title)
        } else {
            format!("文章《{}》未发布", self.title)
        }
    }
    
    fn preview(&self) -> String {
        let preview_len = 100;
        if self.content.len() > preview_len {
            format!("{}...", &self.content[..preview_len])
        } else {
            self.content.clone()
        }
    }
}

impl Editable for Article {
    fn edit(&mut self, content: String) {
        self.content = content;
    }
    
    fn get_content(&self) -> &str {
        &self.content
    }
}

impl fmt::Display for Article {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(
            f,
            "标题: {}\n作者: {}\n内容: {}\n发布状态: {}",
            self.title,
            self.author,
            self.content,
            if self.published { "已发布" } else { "未发布" }
        )
    }
}

// 评论结构体
pub struct Comment {
    author: String,
    content: String,
    timestamp: u64,
}

impl Comment {
    pub fn new(author: String, content: String) -> Self {
        Comment {
            author,
            content,
            timestamp: std::time::SystemTime::now()
                .duration_since(std::time::UNIX_EPOCH)
                .unwrap()
                .as_secs(),
        }
    }
}

impl Publishable for Comment {
    fn publish(&self) -> String {
        format!("评论已发布: {}", self.content)
    }
}

// 博客结构体
pub struct Blog {
    articles: Vec<Article>,
    comments: Vec<Comment>,
}

impl Blog {
    pub fn new() -> Self {
        Blog {
            articles: Vec::new(),
            comments: Vec::new(),
        }
    }
    
    pub fn add_article(&mut self, article: Article) {
        self.articles.push(article);
    }
    
    pub fn add_comment(&mut self, comment: Comment) {
        self.comments.push(comment);
    }
    
    pub fn publish_all(&self) {
        for article in &self.articles {
            println!("{}", article.publish());
        }
        for comment in &self.comments {
            println!("{}", comment.publish());
        }
    }
    
    pub fn get_articles(&self) -> &Vec<Article> {
        &self.articles
    }
}

fn main() {
    let mut blog = Blog::new();
    
    // 创建文章
    let mut article = Article::new(
        String::from("Rust 入门指南"),
        String::from("张三"),
    );
    article.edit(String::from("这是文章内容..."));
    article.set_published(true);
    
    blog.add_article(article);
    
    // 创建评论
    let comment = Comment::new(
        String::from("李四"),
        String::from("很好的文章!"),
    );
    blog.add_comment(comment);
    
    // 发布所有内容
    blog.publish_all();
    
    // 显示文章预览
    for article in blog.get_articles() {
        println!("预览: {}", article.preview());
    }
}

高级 Trait 特性

关联类型

关联类型允许在 trait 定义中声明类型占位符:

pub trait Iterator {
    type Item;  // 关联类型
    
    fn next(&mut self) -> Option<Self::Item>;
}

struct Counter {
    count: u32,
}

impl Iterator for Counter {
    type Item = u32;
    
    fn next(&mut self) -> Option<Self::Item> {
        self.count += 1;
        if self.count < 6 {
            Some(self.count)
        } else {
            None
        }
    }
}

默认泛型类型参数

use std::ops::Add;

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

impl Add for Point {
    type Output = Point;
    
    fn add(self, other: Point) -> Point {
        Point {
            x: self.x + other.x,
            y: self.y + other.y,
        }
    }
}

// 可以指定不同的输出类型
impl Add<i32> for Point {
    type Output = Point;
    
    fn add(self, other: i32) -> Point {
        Point {
            x: self.x + other,
            y: self.y + other,
        }
    }
}

完全限定语法

当有多个同名方法时,使用完全限定语法:

trait Pilot {
    fn fly(&self);
}

trait Wizard {
    fn fly(&self);
}

struct Human;

impl Pilot for Human {
    fn fly(&self) {
        println!("这是机长在说话。");
    }
}

impl Wizard for Human {
    fn fly(&self) {
        println!("起来!");
    }
}

impl Human {
    fn fly(&self) {
        println!("*挥舞手臂*");
    }
}

fn main() {
    let person = Human;
    person.fly();                    // 调用 Human 的方法
    Pilot::fly(&person);            // 调用 Pilot 的方法
    Wizard::fly(&person);           // 调用 Wizard 的方法
}

Supertrait

一个 trait 可以依赖于另一个 trait:

use std::fmt;

trait OutlinePrint: fmt::Display {
    fn outline_print(&self) {
        let output = self.to_string();
        let len = output.len();
        println!("{}", "*".repeat(len + 4));
        println!("*{}*", " ".repeat(len + 2));
        println!("* {} *", output);
        println!("*{}*", " ".repeat(len + 2));
        println!("{}", "*".repeat(len + 4));
    }
}

impl OutlinePrint for Point {}

impl fmt::Display for Point {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "({}, {})", self.x, self.y)
    }
}

总结

本章我们深入学习了 Rust 的结构体和 trait:

  1. 结构体:自定义数据类型,支持方法和关联函数
  2. 枚举:类型安全的联合类型
  3. Trait:定义共享行为,实现多态
  4. 方法:为类型添加行为
  5. 高级特性:关联类型、Supertrait 等

Rust 通过组合(composition)而不是继承(inheritance)来实现代码复用,这种方式更加灵活和安全。在接下来的章节中,我们将学习泛型编程,进一步扩展这些概念。

思考题

  1. Rust 为什么选择组合而不是继承?这有什么优势?
  2. Trait 和接口(interface)有什么区别?
  3. 什么时候应该使用关联类型而不是泛型参数?
  4. 默认实现的作用是什么?什么时候使用?
  5. 如何设计一个好的 trait?应该遵循什么原则?

实践练习

  1. 实现一个图形库,使用 trait 定义可绘制(Drawable)的对象
  2. 创建一个事件系统,使用枚举表示不同类型的事件
  3. 实现一个简单的 ORM,使用 trait 定义数据库操作
  4. 构建一个插件系统,使用 trait 定义插件接口
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

少林码僧

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值