🦀 第06课:结构体与方法

结构体(struct)是Rust中创建自定义类型的主要方式。它让你将相关的数据组合在一起,并通过方法(method)为其附加行为。本课将从基础到高级,全面掌握结构体的使用。

核心特性 第6/25课

学习目标:掌握三种结构体语法、方法与关联函数、派生特征、结构体设计模式

📐 三种结构体

1. 命名字段结构体(最常用)

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

fn main() {
    // 创建实例(字段必须全部初始化)
    let user1 = User {
        username: String::from("alice"),
        email: String::from("alice@example.com"),
        sign_in_count: 1,
        active: true,
    };
    
    // 访问字段
    println!("用户名: {}", user1.username);
    println!("邮箱: {}", user1.email);
    
    // 可变结构体:整个实例可变,不能只标记部分字段
    let mut user2 = User {
        username: String::from("bob"),
        email: String::from("bob@example.com"),
        sign_in_count: 0,
        active: true,
    };
    user2.sign_in_count += 1;  // 修改字段
    
    // 结构体更新语法(从其他实例继承字段)
    let user3 = User {
        username: String::from("charlie"),
        email: String::from("charlie@example.com"),
        ..user1  // 其余字段从user1复制
    };
    // ⚠️ user1的username和email已被移动(String非Copy)
    // println!("{}", user1.username);  // ❌ 已移动
    // 但sign_in_count和active是Copy类型,user1仍可用
    println!("user1仍可用: active={}, count={}", user1.active, user1.sign_in_count);
}

2. 元组结构体

// 有名字的元组
struct Color(u8, u8, u8);
struct Point(f64, f64, f64);

fn main() {
    let red = Color(255, 0, 0);
    let origin = Point(0.0, 0.0, 0.0);
    
    println!("红色: ({}, {}, {})", red.0, red.1, red.2);
    println!("原点: ({}, {}, {})", origin.0, origin.1, origin.2);
    
    // 类型不同,不能混用
    // let c: Color = origin;  // ❌ 类型不匹配
}

3. 单元结构体

// 无字段,用于标记类型或实现trait
struct AlwaysEqual;

fn main() {
    let _subject = AlwaysEqual;
    println!("单元结构体大小: {} 字节", std::mem::size_of::());
}
单元结构体大小: 0 字节

✅ 验证通过

⚙️ 方法(impl块)

方法是在impl块中定义的函数,第一个参数是self,表示调用该方法的结构体实例。

#[derive(Debug)]
struct Rectangle {
    width: f64,
    height: f64,
}

impl Rectangle {
    // 关联函数(不接收self)——类似"构造函数"
    fn new(width: f64, height: f64) -> Self {
        Rectangle { width, height }
    }
    
    fn square(size: f64) -> Self {
        Rectangle { width: size, height: size }
    }
    
    // 方法:借用self(&self = &Rectangle)
    fn area(&self) -> f64 {
        self.width * self.height
    }
    
    fn perimeter(&self) -> f64 {
        2.0 * (self.width + self.height)
    }
    
    fn can_hold(&self, other: &Rectangle) -> bool {
        self.width > other.width && self.height > other.height
    }
    
    // 方法:可变借用
    fn scale(&mut self, factor: f64) {
        self.width *= factor;
        self.height *= factor;
    }
    
    // 方法:获取所有权
    fn into_components(self) -> (f64, f64) {
        (self.width, self.height)
    }
    
    // 链式调用
    fn set_width(&mut self, width: f64) -> &mut Self {
        self.width = width;
        self
    }
    
    fn set_height(&mut self, height: f64) -> &mut Self {
        self.height = height;
        self
    }
}

fn main() {
    let rect = Rectangle::new(10.0, 20.0);
    println!("矩形: {:?}", rect);
    println!("面积: {}", rect.area());
    println!("周长: {}", rect.perimeter());
    
    let square = Rectangle::square(5.0);
    println!("正方形面积: {}", square.area());
    println!("能容纳正方形? {}", rect.can_hold(&square));
    
    // 链式调用
    let mut r = Rectangle::new(1.0, 1.0);
    r.set_width(30.0).set_height(40.0);
    println!("链式设置后: {:?}", r);
    
    // 获取所有权
    let (w, h) = r.into_components();
    println!("分解: width={}, height={}", w, h);
    // r.area();  // ❌ r已被消耗
}
矩形: Rectangle { width: 10.0, height: 20.0 } 面积: 200 周长: 60 正方形面积: 25 能容纳正方形? true 链式设置后: Rectangle { width: 30.0, height: 40.0 } 分解: width=30, height=40

✅ 验证通过

💡 方法 vs 关联函数:
- &self方法:通过实例调用,如rect.area()
- 关联函数:通过类型名调用,如Rectangle::new(),类似其他语言的静态方法

🏷️ 派生特征(Derive)

Rust可以通过#[derive]属性自动实现常用特征:

// Debug: 格式化输出 {:?} 和 {:#?}
// Clone: 允许深拷贝 .clone()
// Copy: 允许值复制(需要Clone)
// PartialEq, Eq: 允许 == 和 != 比较
// PartialOrd, Ord: 允许 > < >= <= 排序
// Hash: 允许作为HashMap的键

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

impl Point {
    fn new(x: f64, y: f64) -> Self { Point { x, y } }
    fn distance_from_origin(&self) -> f64 {
        (self.x * self.x + self.y * self.y).sqrt()
    }
}

fn main() {
    let p1 = Point::new(3.0, 4.0);
    let p2 = p1.clone();  // Clone
    
    println!("{:?}", p1);            // Debug
    println!("{:#?}", p1);           // 漂亮Debug
    println!("相等? {}", p1 == p2);  // PartialEq
    
    // 排序
    let mut points = vec![
        Point::new(3.0, 4.0),   // 距离5
        Point::new(1.0, 1.0),   // 距离√2
        Point::new(0.0, 5.0),   // 距离5
    ];
    
    points.sort_by(|a, b| {
        a.distance_from_origin()
            .partial_cmp(&b.distance_from_origin())
            .unwrap()
    });
    
    for p in &points {
        println!("距离: {:.2}", p.distance_from_origin());
    }
}
Point { x: 3.0, y: 4.0 } Point { x: 3.0, y: 4.0, } 相等? true 距离: 1.41 距离: 5.00 距离: 5.00

✅ 验证通过

🏗️ 综合实战:图书馆系统

use std::fmt;

#[derive(Debug, Clone, PartialEq)]
enum BookStatus {
    Available,
    Borrowed { borrower: String, due_days: u32 },
    Reserved(Vec<String>),  // 等待列表
}

#[derive(Debug, Clone)]
struct Book {
    isbn: String,
    title: String,
    author: String,
    year: u32,
    status: BookStatus,
}

impl Book {
    fn new(isbn: &str, title: &str, author: &str, year: u32) -> Self {
        Book {
            isbn: isbn.to_string(),
            title: title.to_string(),
            author: author.to_string(),
            year,
            status: BookStatus::Available,
        }
    }
    
    fn borrow(&mut self, borrower: &str, days: u32) -> Result<(), &str> {
        match &self.status {
            BookStatus::Available => {
                self.status = BookStatus::Borrowed {
                    borrower: borrower.to_string(),
                    due_days: days,
                };
                Ok(())
            }
            BookStatus::Borrowed { .. } => Err("书已被借出"),
            BookStatus::Reserved(_) => Err("书已被预约"),
        }
    }
    
    fn return_book(&mut self) -> Result<(), &str> {
        match &self.status {
            BookStatus::Borrowed { .. } => {
                self.status = BookStatus::Available;
                Ok(())
            }
            _ => Err("书未被借出"),
        }
    }
    
    fn is_available(&self) -> bool {
        matches!(self.status, BookStatus::Available)
    }
}

impl fmt::Display for Book {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "[{}] {} by {} ({})", self.isbn, self.title, self.author, self.year)
    }
}

struct Library {
    name: String,
    books: Vec<Book>,
}

impl Library {
    fn new(name: &str) -> Self {
        Library { name: name.to_string(), books: Vec::new() }
    }
    
    fn add_book(&mut self, book: Book) {
        self.books.push(book);
    }
    
    fn find_by_title(&self, keyword: &str) -> Vec<&Book> {
        self.books.iter()
            .filter(|b| b.title.to_lowercase().contains(&keyword.to_lowercase()))
            .collect()
    }
    
    fn available_books(&self) -> Vec<&Book> {
        self.books.iter().filter(|b| b.is_available()).collect()
    }
    
    fn borrow_book(&mut self, isbn: &str, borrower: &str, days: u32) -> Result<(), &str> {
        let book = self.books.iter_mut()
            .find(|b| b.isbn == isbn)
            .ok_or("未找到此书")?;
        book.borrow(borrower, days)
    }
    
    fn return_book(&mut self, isbn: &str) -> Result<(), &str> {
        let book = self.books.iter_mut()
            .find(|b| b.isbn == isbn)
            .ok_or("未找到此书")?;
        book.return_book()
    }
    
    fn stats(&self) -> (usize, usize) {
        let total = self.books.len();
        let available = self.available_books().len();
        (total, available)
    }
}

impl fmt::Display for Library {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "📚 {} ({}本藏书, {}本可借)", 
               self.name, self.books.len(), self.available_books().len())
    }
}

fn main() {
    let mut lib = Library::new("Rust图书馆");
    
    lib.add_book(Book::new("978-1", "The Rust Programming Language", "Steve Klabnik", 2023));
    lib.add_book(Book::new("978-2", "Rust in Action", "Tim McNamara", 2022));
    lib.add_book(Book::new("978-3", "Programming Rust", "Jim Blandy", 2024));
    
    println!("{}", lib);
    
    // 借书
    match lib.borrow_book("978-1", "Alice", 14) {
        Ok(()) => println!("✅ Alice借书成功"),
        Err(e) => println!("❌ {}", e),
    }
    
    // 重复借
    match lib.borrow_book("978-1", "Bob", 7) {
        Ok(()) => println!("✅ Bob借书成功"),
        Err(e) => println!("❌ {}", e),
    }
    
    // 还书
    lib.return_book("978-1").unwrap();
    println!("📖 归还后: {}", lib);
    
    // 搜索
    let found = lib.find_by_title("rust");
    println!("\n搜索'rust'的结果:");
    for book in found {
        println!("  {}", book);
    }
}
📚 Rust图书馆 (3本藏书, 3本可借) ✅ Alice借书成功 ❌ 书已被借出 📖 归还后: 📚 Rust图书馆 (3本藏书, 3本可借) 搜索'rust'的结果: [978-1] The Rust Programming Language by Steve Klabnik (2023) [978-2] Rust in Action by Tim McNamara (2022) [978-3] Programming Rust by Jim Blandy (2024)

✅ 验证通过

📝 练习

练习1:2D向量

实现Vec2结构体,包含x和y字段,实现加法、减法、点积、长度等方法。

练习2:温度类型

创建CelsiusFahrenheit元组结构体,实现相互转换的方法。

练习3:简易栈

实现泛型栈Stack<T>,支持push、pop、peek、is_empty、len方法。

🏆 本课成就

🔒 下一课解锁:枚举与模式匹配 —— Rust中最优雅的控制流

🔧 结构体设计模式

use std::fmt;

// 新类型模式(Newtype)
struct Meters(f64);
struct Kilometers(f64);

impl Meters {
    fn new(v: f64) -> Self { Meters(v) }
    fn to_km(self) -> Kilometers { Kilometers(self.0 / 1000.0) }
}

impl Kilometers {
    fn new(v: f64) -> Self { Kilometers(v) }
    fn to_m(self) -> Meters { Meters(self.0 * 1000.0) }
}

impl fmt::Display for Meters {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}m", self.0) }
}

impl fmt::Display for Kilometers {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}km", self.0) }
}

// Builder模式
#[derive(Debug)]
struct HttpRequest {
    url: String,
    method: String,
    headers: Vec<(String, String)>,
    body: Option,
    timeout: u64,
}

struct HttpRequestBuilder {
    url: Option,
    method: Option,
    headers: Vec<(String, String)>,
    body: Option,
    timeout: u64,
}

impl HttpRequestBuilder {
    fn new() -> Self {
        HttpRequestBuilder {
            url: None, method: None, headers: Vec::new(),
            body: None, timeout: 30,
        }
    }
    fn url(mut self, url: &str) -> Self { self.url = Some(url.to_string()); self }
    fn method(mut self, method: &str) -> Self { self.method = Some(method.to_string()); self }
    fn header(mut self, key: &str, value: &str) -> Self { self.headers.push((key.into(), value.into())); self }
    fn body(mut self, body: &str) -> Self { self.body = Some(body.to_string()); self }
    fn timeout(mut self, seconds: u64) -> Self { self.timeout = seconds; self }
    fn build(self) -> Result {
        Ok(HttpRequest {
            url: self.url.ok_or("URL is required")?,
            method: self.method.unwrap_or_else(|| "GET".to_string()),
            headers: self.headers,
            body: self.body,
            timeout: self.timeout,
        })
    }
}

fn main() {
    // 新类型
    let distance = Meters::new(5000.0);
    println!("{} = {}", distance, distance.to_km());
    
    // Builder
    let request = HttpRequestBuilder::new()
        .url("https://api.example.com/data")
        .method("POST")
        .header("Content-Type", "application/json")
        .header("Authorization", "Bearer token123")
        .body(r#"{"key": "value"}"#)
        .timeout(60)
        .build()
        .unwrap();
    println!("请求: {:?}", request);
}
5000m = 5km 请求: HttpRequest { url: "https://api.example.com/data", method: "POST", headers: [("Content-Type", "application/json"), ("Authorization", "Bearer token123")], body: Some("{"key": "value"}"), timeout: 60 }

✅ 验证通过