我有一个带有几个状态的 struct :

struct Select;
struct Insert;
struct Delete;
// ...more states

struct Query<T> {
    // ... some non-generic fields
    marker: PhantomData<T>
}

我有一些功能,我想为一些州实现,但不是所有州.我想它应该是这样的:

impl Query<T> for T: Select | Update | Delete {
    // implement only once
    fn foo() -> Query<T>;
}

这是可能的吗?如果可能,如何实现?

推荐答案

有两种主要的方法可以做到这一点.像查伊姆建议的那样,用trait 守卫,或者用宏观.让我们看看这些解决方案是如何工作的,以及它们的权衡之处.

特长卫士

这是一个非常简单的概念,但它有一个微妙的细微差别.我们希望定义某种类型的Guard特征,为某些类型实现它,然后利用泛型实现.例如:

pub trait Guard {}

impl Guard for Select {}
impl Guard for Update {}
impl Guard for Delete {}

impl<T: Guard> Query<T> {
    pub fn foo() -> Query<T> {
        todo!()
    }
}

然而,这有一个重要的缺点.由于Guard是一个公共特征,如果有人要为其他类型Other实现它,那么impl<T: Guard>也将适用于Other类型.这可能是不受欢迎的,因为根据您项目的需求,这可能会导致不变式损坏.

我们可以try 将Guard作为private个性状,但目前(rustc 1.70.0,2021 Edition)会导致警告,并将在future 成为错误.

warning: private trait `Guard` in public interface (error E0445)
  --> src/lib.rs:24:1
   |
24 | impl<T: Guard> Query<T> {
   | ^^^^^^^^^^^^^^^^^^^^^^^
   |
   = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
   = note: for more information, see issue #34537 <https://github.com/rust-lang/rust/issues/34537>
   = note: `#[warn(private_in_public)]` on by default

我们可以用sealed trait来解决这个问题:

mod sealed {
    pub trait Sealed {}
}

pub trait Guard: sealed::Sealed {}

impl sealed::Sealed for Select {}
impl sealed::Sealed for Update {}
impl sealed::Sealed for Delete {}

impl Guard for Select {}
impl Guard for Update {}
impl Guard for Delete {}

impl<T: Guard> Query<T> {
    pub fn foo() -> Query<T> {
        todo!()
    }
}

然而,这使我们必须编写的实现数量翻了一番,并导致了稍微难看的API(因为我们将私有密封"泄漏"到公共API).这还可能导致文档可读性降低,因为读者必须首先判断哪些类型实现了Guard.

或者,你也可以用declarative macro.这将导致与您所描述的语法非常相似的语法.

macro_rules! foo_impl {
    ($($state:ty),*) => {
        $(impl Query<$state> {
            pub fn foo() -> Query<$state> {
                todo!()
            }
        })*
    };
}

foo_impl!(Select, Update, Delete);

这有几个优点:

  • 您不必重复为您的类型实现Guard特征.
  • 您不必担心其他人会为其他类型实现守卫特性.
  • 您拥有一个更干净的API,并且(可能)具有更多的可读性文档.

另一方面,如果您想要更好的带有特征的解决方案,您仍然可以编写一个宏来自动实现Guard,并对您的类型进行封存.

macro_rules! guard_impl {
    ($($state:ty),*) => {
        $(
            impl sealed::Sealed for $state {}
            impl Guard for $state {}
        )*
    };
}

guard_impl!(Select, Update, Delete);

Rust相关问答推荐

展开枚举变量并返回所属值或引用

当第二个`let`依赖于第一个`let()`时,如何在一行中有多个`let()`?

MPSC频道在接收器处阻塞

定义只有一些字段可以缺省的 struct

程序在频道RX上挂起

类型生命周期绑定的目的是什么?

如何在 Rust 中编写一个通用方法,它可以接受任何可以转换为另一个值的值?

trait 对象指针的生命周期

为什么 `tokio::join!` 宏不需要 Rust 中的 `await` 关键字?

仅当函数写为闭包时才会出现生命周期错误

在 Rust 中,Weak 如何知道内部值何时被删除?

如何解析 Rust 中的 yaml 条件字段?

Rust HRTB 是相同的,但编译器说一种类型比另一种更通用

Abortable:悬而未决的期货?

将 (T, ()) 转换为 T 安全吗?

为什么这个 Trait 无效?以及改用什么签名?

为什么我不能将元素写入 Rust 数组中移动的位置,但我可以在元组中完成

为移动和借位的所有组合实现 Add、Sub、Mul、Div

为什么我在这里得到一个闭包实现`FnMut`,所以对捕获变量的引用不能逃脱闭包?

如何重组 struct 以使不能一次多次borrow `x`作为可变的不会发生