当前位置: 首页 > article >正文

【遵守孤儿规则的External trait pattern】

遵守孤儿规则的External trait pattern

  • Rust中的孤儿规则
  • trait约定俗成的impl路径
    • 内部类型实现外部trait(impl External Trait for Internal Type)
    • 外部类型实现内部trait(impl Internal Trait for External Type)


Rust中的孤儿规则

孤儿规则,Rust限制了开发者,在实现trait时,至少类型和trait有一个在当前实现的crate内,方才可以实现trait,否则将会报错,既在本crate中用为external type 实现了external trait,为什么要这么限制呢,显然的,如果放开这种限制,开发者很有可能为某一种类型如Vector又一次实现了Debug Trait,那么在编译时编译器将不知道如何选择,将其限制在编译阶段可以避免很多不必要的时间浪费和问题排查。


trait约定俗成的impl路径

一般的,开发者在使用rust为类型实现trait时,通常有两种路径,一种是为内部类型实现外部trait(impl External Trait for Internal Type),一种是为外部类型实现内部trait(impl Internal Trait for External Type),不同的实现路径依赖于具体的需要。

内部类型实现外部trait(impl External Trait for Internal Type)

显然,内部类型实现外部trait,既类型定义在本Crate,trait定义在外部,实现时,impl trait 与 type 同Crate。
代码示例:

//-> src/user_info.rs
pub struct UserInfo {
    name: String,
    age: u32,
    email: String,
    phone: String,
}

impl UserInfo {
    pub fn new() -> Self {
        UserInfo {
            name: String::from("James"),
            age: 0,
            email: String::from("xxxx@126.com"),
            phone: String::from("000-000-0000"),
        }
    }
}
// --> src/main.rs 
mod user_info;
fn main() {
    let user = user_info::UserInfo::new();
    println!("{:?}", user); //--> goes wrong
}

如果我们需要在此打印UserInfo,那么我们就需要为UserInfo实现Debug,如果我们在main.rs中尝试实现impl Debug for UserInfo,如下:

mod user_info;
use std::fmt::Debug;

fn main() {
    let user = user_info::UserInfo::new();
    println!("{:?}", user);
}
impl Debug for UserInfo {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "UserInfo {{ name: {}, age: {}, email: {}, phone: {} }}",
            self.name, self.age, self.email, self.phone
        )
    }
}

报错如下:

cannot find type `UserInfo` in this scope

因为Debug是external Trait,Rustc会自然的在current scope寻找类型UserInfo,而上述实现恰好违反了孤儿规则。正确的方式:

  1. 在UserInfo mod中实现Debug:
use std::fmt::Debug;

pub struct UserInfo {
    name: String,
    age: u32,
    email: String,
    phone: String,
}

impl UserInfo {
    pub fn new() -> Self {
        UserInfo {
            name: String::from("James"),
            age: 0,
            email: String::from("xxxx@126.com"),
            phone: String::from("000-000-0000"),
        }
    }
}
impl Debug for UserInfo {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "UserInfo {{ name: {}, age: {}, email: {}, phone: {} }}",
            self.name, self.age, self.email, self.phone
        )
    }
}
  1. 使用macro derive Debug
use std::fmt::Debug;
#[derive(Debug)]
pub struct UserInfo {
    name: String,
    age: u32,
    email: String,
    phone: String,
}

impl UserInfo {
    pub fn new() -> Self {
        UserInfo {
            name: String::from("James"),
            age: 0,
            email: String::from("xxxx@126.com"),
            phone: String::from("000-000-0000"),
        }
    }
}

ps: 如果开发者一定想要将一个外部类型添加外部trait,可以先将外部类型转换为内部类型(如用结构体元组包裹),但这通常不提倡,这样做会为后续的开发留下隐患,如可读性等。

外部类型实现内部trait(impl Internal Trait for External Type)

相对拗口的题目,实际上就是为外部类型实现扩展trait,这在日常使用中十分常见,也是常被提到的扩展trait 模式。还记得我们在actix web 译文中讨论的actix 框架吗?下面通过为Actix web框架的Request实现扩展trait让开发者有个更加直观的印象。
代码示例:

use actix_web::HttpRequest;

trait HttpRequestExt {
    fn is_windows_user(&self) -> bool;
}

impl HttpRequestExt for HttpRequest {
    fn is_windows_user(&self) -> bool {
        self.headers()
            .get("User-Agent")
            .and_then(|value| value.to_str().ok())
            .map(|ua| ua.contains("Windows"))
            .unwrap_or(false)
    }
}
fn main() {
    //something to do
}

上述代码开发者可以通过is_windows_user对请求方做进一步的判断,如windows用户如何重定向,linux用户重定向到哪里。

上述代码从业务逻辑的角度出发讲解了External trait pattern,结合Iterator,开发者也可以为Iterator实现扩展,如过滤重复Item等等。Rust
类型驱动开发一文中,我们通过对进度条的实现讨论了类型驱动的合理性和优点,通过借助Rust对类型的check来让问题更加明确。在其代码示例中有如下代码:

impl<Iter, Bound> Iterator for Progress<Iter, Bound>
where
    Iter: Iterator,
    Bound: ProgressDisplay,
{
    type Item = Iter::Item;

    fn next(&mut self) -> Option<Self::Item> {
        self.bound.display(&self);
        self.i += 1;
        self.iter.next()
    }
}
//为进度条实现迭代器,方可迭代,实现关键步骤:Item & next 方法
trait ProgressIteratorExt: Sized {
    fn progress(self) -> Progress<Self, Unbounded>;
}

impl<Iter> ProgressIteratorExt for Iter
where
    Iter: Iterator,
{
    fn progress(self) -> Progress<Self, Unbounded> {
        Progress::new(self)
    }
}

此进度条便是为Rust的迭代器实现了External trait pattern,使得在对容器进行遍历时可以像进度条一样展示。


“直面复杂性,反复练习和思考可以解决绝大部分的难题”


http://www.kler.cn/a/406550.html

相关文章:

  • 2024 APMCM亚太数学建模C题 - 宠物行业及相关产业的发展分析和策略 完整参考论文(2)
  • MacOS下的Opencv3.4.16的编译
  • 【Redis】基于Redis实现秒杀功能
  • Go-RPC关键指标分析与企业实践
  • 使用 PyTorch-BigGraph 构建和部署大规模图嵌入的完整教程
  • 《Vue零基础入门教程》第四课: 应用实例
  • Python 爬虫 (1)基础 | 基础操作
  • python语言基础-5 进阶语法-5.5 上下文管理协议(with语句)
  • 第31次CCF计算机软件能力认证
  • 相机触发模式
  • Appium常用的使用方法(一)
  • 上生产时连接mysql数据库总是被拒绝
  • HarmonyOs鸿蒙开发实战(20)=>一文学会基础使用组件导航Navigation
  • 网络安全-web架构-nginx配置
  • node.js fluent-ffmpeg 桌面推流
  • JS中的正则表达式简要梳理
  • Spring Boot图书馆管理系统:疫情时代的管理工具
  • kubepi管理k8s集群,演示如何连接阿里云k8s容器
  • AR智能眼镜|AR眼镜定制开发|工业AR眼镜方案
  • 从0开始学习Linux——Shell编程详解【03】
  • windows C#-异步返回类型(下)
  • Javaweb web前端标签样式正文
  • 【AI赋能电商】数据分析和训练精准导向
  • Web前端演示:管道与支架检测
  • 香豆烤馍:传统美食中的烟火记忆
  • 深入理解Go语言并发编程:从基础到实践