Nugine 的个人博客关于

Rust 有趣片段(四):sealed trait

sealed trait 字面意思为“密封特质”,在 Rust 中表示一类无法被下游代码实现的 trait.

与 Scala 不同,Rust 中的 sealed trait 不是语言内建功能,而是从访问控制功能导出的特殊设计模式。

假设我们的库中有一个非常重要的 trait。

mod important {
    pub trait Important {
        fn do_sth(&self);
    }

    impl Important for String {
        fn do_sth(&self) {
            dbg!(self);
        }
    }
}

fn main() {
    use important::Important;
    let s: String = "hello".into();
    s.do_sth();
}

使用方想折腾一下,增加了一个实现。

struct Foo;

impl important::Important for Foo {
    fn do_sth(&self) {
        dbg!();
    }
}

fn main() {
    use important::Important;

    let s: String = "hello".into();
    s.do_sth();

    let foo = Foo;
    foo.do_sth();
}

这时为了新需求,修改了 Important trait.

pub trait Important {
    fn do_sth(&self);
    fn do_another_thing(&self);
}

由于不匹配,使用方的实现立即报错。

...
23 | impl Important for Foo {
   | ^^^^^^^^^^^^^^^^^^^^^^ missing `do_another_thing` in implementation

Important trait 仅用于下游代码与上游库的交互,但此时,上游的内部修改破坏了下游的代码。

下游使用上游库,必须访问 Important trait,但上游为了未来的修改考虑,不想让下游实现这个 trait.

方法很简单:依赖一个下游无法访问的 trait。

mod important {
    mod private {
        pub trait Sealed {}

        impl Sealed for String {}
    }

    pub trait Important: private::Sealed {
        fn do_sth(&self);
    }

    impl Important for String {
        fn do_sth(&self) {
            dbg!(self);
        }
    }
}

Important trait 依赖一个 Sealed trait,要实现前者,必须先实现后者。下游代码无法访问未公开的 Sealed trait,也就无法实现 Important trait,但仍然能使用它的功能。

在访问泄露检查器看来,Important trait 没有泄露私有元素,只是依赖了其他模块的一个公开 trait,但对使用者来说,这个“其他模块”是不可见的。

此外,通过控制 Sealed trait 的可见范围,能够将扩展权限开放到整个 crate,也能只开放给同一模块。

这种设计模式在标准库和很多第三方库中出现。当上游为了兼容性或逻辑正确性,想禁止下游实现上游 trait 时,就可以用 sealed trait 来消除这种可能。

在 struct 中混入未公开字段也有类似的效果。

mod user {
    pub struct User {
        pub name: String,
        pub age: u32,
        _priv: (),
    }

    pub fn get_user() -> User {
        User {
            name: "asd".into(),
            age: 18,
            _priv: (),
        }
    }
}

模块提供 struct 定义与指定的构造器,外部代码可以访问公开字段,但不能自行构造 struct,必须使用模块提供的构造方法。

这就允许模块在未来任意添加字段,而不会破坏已有代码。

pub struct User {
    pub name: String,
    pub age: u32,
    pub job: Option<String>,
    _priv: (),
}

例如,添加 job 字段并修改对应构造方法,不会对下游造成影响。只要公开 API 没有破坏性改动,上游就能自由演进。

利用 Rust 类型系统与语言功能,能写出一些较为特殊的设计模式。目前来看,Rust 生态已经形成了特有的 API 风格与设计,有待总结常见模式。

发布于 2020-04-15地址: GitHub, 知乎