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:
- 结构体:自定义数据类型,支持方法和关联函数
- 枚举:类型安全的联合类型
- Trait:定义共享行为,实现多态
- 方法:为类型添加行为
- 高级特性:关联类型、Supertrait 等
Rust 通过组合(composition)而不是继承(inheritance)来实现代码复用,这种方式更加灵活和安全。在接下来的章节中,我们将学习泛型编程,进一步扩展这些概念。
思考题
- Rust 为什么选择组合而不是继承?这有什么优势?
- Trait 和接口(interface)有什么区别?
- 什么时候应该使用关联类型而不是泛型参数?
- 默认实现的作用是什么?什么时候使用?
- 如何设计一个好的 trait?应该遵循什么原则?
实践练习
- 实现一个图形库,使用 trait 定义可绘制(Drawable)的对象
- 创建一个事件系统,使用枚举表示不同类型的事件
- 实现一个简单的 ORM,使用 trait 定义数据库操作
- 构建一个插件系统,使用 trait 定义插件接口
5万+

被折叠的 条评论
为什么被折叠?



