前端都能看懂的Rust入门教程(四)——struct和trait

本节介绍Rust中的struct和trait。

这两者类似js或java中的class和interface,但概念上并不等同。可以用三句话概括Rust中的struct和trait设计理念:sturct主要关注数据的组织,trait主要关注行为的抽象,组合优于继承。

struct(结构体)

Struct 是 Rust 中定义自定义数据类型的主要方式,它允许你将多个相关的值组合在一起,形成一个有意义的组合。

1. 基本定义和使用

定义结构体

使用 struct 关键字后跟结构体名称,接着用花括号 {} 包裹字段声明。每个字段都需要指定类型。

rust 复制代码
// 定义一个结构体
struct User {
    username: String,
    email: String,
    sign_in_count: u64,
    active: bool,
}

创建实例和访问属性

使用结构体名称后跟带有初始化值的花括号 {} 来创建实例,每个字段必须被显式地赋予一个值。

使用 pub 关键字可以控制结构体及其字段的可见性。默认情况下,结构体和其字段是模块内部可见的。

rust 复制代码
fn main() {
    // 创建结构体实例
    let user1 = User {
        email: String::from("someone@example.com"),
        pub username: String::from("someusername123"),
        active: true,
        sign_in_count: 1,
    };
    
    // 访问字段
    println!("用户名: {}", user1.username);
    println!("邮箱: {}", user1.email);
    println!("活跃状态: {}", user1.active);
    println!("登录次数: {}", user1.sign_in_count);
    
    // 修改字段(需要 mut)
    let mut user2 = User {
        email: String::from("another@example.com"),
        username: String::from("anotheruser"),
        active: true,
        sign_in_count: 0,
    };
    
    user2.sign_in_count = 1;  // 可以修改
    user2.active = false;
}

2. 结构体更新语法

rust 复制代码
// 使用已有实例创建新实例
let user3 = User {
    email: String::from("third@example.com"),
    username: String::from("thirduser"),
    ..user1  // 使用 user1 的其他字段值
};

// 等价于:
let user4 = User {
    email: String::from("fourth@example.com"),
    username: String::from("fourthuser"),
    active: user1.active,
    sign_in_count: user1.sign_in_count,
};

3. 元组结构体

rust 复制代码
// 元组结构体:有名字的元组
struct Color(i32, i32, i32);
struct Point(i32, i32, i32);

fn main() {
    let black = Color(0, 0, 0);
    let origin = Point(0, 0, 0);
    
    // 通过索引访问
    println!("黑色的红色分量: {}", black.0);
    println!("原点的 x 坐标: {}", origin.0);
    
    // 即使字段类型相同,也是不同的类型
    // let color_point: Color = origin;  // 错误!类型不匹配
}

4. impl方法定义

上面的结构体更接近js中的interface,其本身只定义结构而无实现,因此也无法使用new来创建实例。

在Rust中要通过new创建实例,就需要对该结构体实现构造函数。

rust 复制代码
// 定义一个Person结构体
struct Person {
    name: String,
    age: u32,
}

// 为Person实现构造函数
impl Person {
    fn new(name: String, age: u32) -> Person {
        Person { name, age }
    }
}

fn main() {
    // 使用new方法创建一个Person实例
    let person = Person::new("Alice".to_string(), 30);
    
    // 访问结构体的字段
    println!("Name: {}, Age: {}", person.name, person.age);
}

使用 impl 块定义结构体静态方法和成员方法

通过impl块为结构体定义方法时,可以根据方法的第一个参数是否为&self&mut self来区分静态方法和成员方法。

rust 复制代码
struct Rectangle {
    width: u32,
    height: u32,
}

// 为 Rectangle 实现方法
impl Rectangle {
    // 关联函数(类似静态方法)
    fn square(size: u32) -> Rectangle {
        Rectangle {
            width: size,
            height: size,
        }
    }
    
    // 方法:第一个参数是 self
    fn area(&self) -> u32 {
        self.width * self.height
    }
    
    // 可变方法
    fn double(&mut self) {
        self.width *= 2;
        self.height *= 2;
    }
    
  
    fn can_hold(&self, other: &Rectangle) -> bool {
        self.width > other.width && self.height > other.height
    }
}

fn main() {
    let rect1 = Rectangle {
        width: 30,
        height: 50,
    };
    
    println!("面积: {}", rect1.area());
    
    let mut rect2 = Rectangle {
        width: 10,
        height: 40,
    };
    
    rect2.double();
    println!("加倍后的面积: {}", rect2.area());
    
    println!("rect1 能包含 rect2 吗? {}", rect1.can_hold(&rect2));
    
    // 调用关联函数
    let square = Rectangle::square(10);
    println!("正方形的面积: {}", square.area());
}

5. 多个 impl

在Rust中,struct可以有多个impl块,这带来了几个好处:

  1. 灵活性:可以在不同的地方、不同的模块中为同一个struct添加方法。例如,可以在定义struct的同一个文件中添加多个impl块,也可以在不同的文件中为struct实现方法(前提是struct和trait在同一个crate中,或者满足孤儿规则)。
  2. 组织代码:可以将相关的方法分组到不同的impl块中,提高代码的可读性。例如,可以将实现某个trait的方法放在一个impl块中,而将struct自身的方法放在另一个impl块中。
  3. 条件编译 :可以为不同的编译条件提供不同的实现。例如,可以使用#[cfg(target_os = "linux")]属性为Linux系统提供特定的实现,而将其他系统的实现放在另一个impl块中。
  4. 泛型特化:虽然Rust目前不支持完整的泛型特化,但通过多个impl块可以为不同的泛型参数提供不同的实现。例如,可以为某些特定的类型提供优化的实现。
  5. 扩展性:可以在不修改原有impl块的情况下,为struct添加新的方法。这在编写库代码时尤其有用,因为可以在后续的版本中添加新的方法而不会破坏现有的代码。
  6. 实现多个trait:每个trait的实现通常放在独立的impl块中,这样结构清晰,易于管理。
rust 复制代码
struct Counter {
    count: u32,
}

impl Counter {
    fn new() -> Counter {
        Counter { count: 0 }
    }
}

impl Counter {
    fn increment(&mut self) {
        self.count += 1;
    }
    
    fn current(&self) -> u32 {
        self.count
    }
}

注:多个impl块并不意味着同一个方法可以有不同的实现(即不允许重复定义相同签名的方法)。每个方法在一个struct中只能有一个实现。

6. 泛型结构体

rust 复制代码
// 泛型结构体
struct Point<T> {
    x: T,
    y: T,
}

// 为泛型结构体实现方法
impl<T> Point<T> {
    fn x(&self) -> &T {
        &self.x
    }
}

// 可以为特定类型实现特定方法
impl Point<f32> {
    fn distance_from_origin(&self) -> f32 {
        (self.x.powi(2) + self.y.powi(2)).sqrt()
    }
}

// 多个泛型参数
struct Pair<T, U> {
    first: T,
    second: U,
}

impl<T, U> Pair<T, U> {
    // 关联函数可以返回不同类型的 Pair
    fn mixup<V, W>(self, other: Pair<V, W>) -> Pair<T, W> {
        Pair {
            first: self.first,
            second: other.second,
        }
    }
}

fn main() {
    let integer_point = Point { x: 5, y: 10 };
    let float_point = Point { x: 1.0, y: 4.0 };
    
    println!("整数点的 x: {}", integer_point.x());
    println!("浮点点的距离: {}", float_point.distance_from_origin());
    
    let pair1 = Pair { first: 5, second: 10.4 };
    let pair2 = Pair { first: "Hello", second: 'c' };
    
    let mixed = pair1.mixup(pair2);
    println!("混合后: {}, {}", mixed.first, mixed.second); // 5, 'c'
}

trait(派生)

Rust中的trait是定义共享行为的一种方式。它类似ts中的interface,但更强大。trait定义了一组方法,这些方法可以被不同类型实现,从而允许不同类型共享相同的行为。

1. 基本概念

定义 Trait

rust 复制代码
// 定义一个简单的 trait
pub trait Summary {
    // 方法签名(没有默认实现)
    fn summarize(&self) -> String;
    
    // 方法签名(有默认实现)
    fn summarize_short(&self) -> String {
        String::from("(Read more...)")
    }
}

实现 Trait

rust 复制代码
// 为结构体实现 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_short(&self) -> String {
        format!("Tweet by @{}", self.username)
    }
}

使用 Trait

rust 复制代码
fn main() {
    let article = NewsArticle {
        headline: String::from("Penguins win the Stanley Cup Championship!"),
        location: String::from("Pittsburgh, PA, USA"),
        author: String::from("Iceburgh"),
        content: String::from("The Pittsburgh Penguins once again are the best hockey team in the NHL."),
    };
    
    let tweet = Tweet {
        username: String::from("horse_ebooks"),
        content: String::from("of course, as you probably already know, people"),
        reply: false,
        retweet: false,
    };
    
    println!("Article summary: {}", article.summarize());
    println!("Tweet summary: {}", tweet.summarize());
    println!("Tweet short: {}", tweet.summarize_short());
}

2. Trait 作为参数类型

impl Trait 语法

rust 复制代码
// 接受实现了 Summary trait 的类型
fn notify(item: &impl Summary) {
    println!("Breaking news! {}", item.summarize());
}

// 使用
notify(&article);
notify(&tweet);

Trait Bound 语法

rust 复制代码
// 更明确的写法
fn notify<T: Summary>(item: &T) {
    println!("Breaking news! {}", item.summarize());
}

// 多个 trait bound
fn notify_multiple<T: Summary + Display>(item: &T) {
    println!("{}", item);
    println!("Summary: {}", item.summarize());
}

3. Trait 作为返回值类型

返回实现了 Trait 的类型

rust 复制代码
// 返回实现了 Summary 的类型
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,
    }
}

组合优于继承

Rust 语言没有传统面向对象语言中的继承(inheritance)概念,而是推崇组合(composition)和 trait 对象(trait objects)来实现代码复用和多态。

1. 成员属性组合

在传统面向对象语言中,使用组合一般通过成员属性来实现,即has-a,这一点在Rust中也一样。

rust 复制代码
// 基本的组合:一个结构体包含另一个结构体
struct Engine {
    horsepower: u32,
    fuel_type: String,
}

impl Engine {
    fn start(&self) {
        println!("引擎启动,马力: {}", self.horsepower);
    }
}

// Car 包含 Engine,而不是继承自 Engine
struct Car {
    brand: String,
    model: String,
    engine: Engine,  // 组合:Car 有一个 Engine
}

2. trai实现多态

rust 复制代码
// 定义行为接口
trait Drawable {
    fn draw(&self);
    fn area(&self) -> f64;
}

// 多个类型实现相同的 trait
struct Circle {
    radius: f64,
}

impl Drawable for Circle {
    fn draw(&self) {
        println!("绘制圆形,半径: {}", self.radius);
    }
    
    fn area(&self) -> f64 {
        std::f64::consts::PI * self.radius * self.radius
    }
}

struct Square {
    side: f64,
}

impl Drawable for Square {
    fn draw(&self) {
        println!("绘制正方形,边长: {}", self.side);
    }
    
    fn area(&self) -> f64 {
        self.side * self.side
    }
}

// 使用 trait 对象实现多态
fn render_all(shapes: &[&dyn Drawable]) {
    for shape in shapes {
        shape.draw();
        println!("面积: {}", shape.area());
    }
}

注:dyn Drawable:这是一个 trait 对象,表示任何实现了 Drawable trait 的类型。它是一个动态类型,在编译时不知道具体类型,但知道它实现了 Drawable。`

3. 使用 derive 自动实现 trait

rust 复制代码
// 使用 derive 自动实现 trait
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
struct Point {
    x: i32,
    y: i32,
}

注:在Rust中,#[derive(...)]是一种属性(attribute),用于自动为结构体或枚举实现某些trait。这些trait通常是标准库中定义的一些常用trait,通过derive属性,编译器可以自动生成这些trait的实现代码,从而避免手动编写重复的代码。

相关推荐
mCell4 小时前
如何零成本搭建个人站点
前端·程序员·github
mCell5 小时前
为什么 Memo Code 先做 CLI:以及终端输入框到底有多难搞
前端·设计模式·agent
恋猫de小郭5 小时前
AI 在提高你工作效率的同时,也一直在增加你的疲惫和焦虑
前端·人工智能·ai编程
少云清6 小时前
【安全测试】2_客户端脚本安全测试 _XSS和CSRF
前端·xss·csrf
银烛木6 小时前
黑马程序员前端h5+css3
前端·css·css3
m0_607076606 小时前
CSS3 转换,快手前端面试经验,隔壁都馋哭了
前端·面试·css3
听海边涛声6 小时前
CSS3 图片模糊处理
前端·css·css3
IT、木易6 小时前
css3 backdrop-filter 在移动端 Safari 上导致渲染性能急剧下降的优化方案有哪些?
前端·css3·safari
0思必得06 小时前
[Web自动化] Selenium无头模式
前端·爬虫·selenium·自动化·web自动化
青云计划6 小时前
知光项目知文发布模块
java·后端·spring·mybatis