有两种主要的方法可以做到这一点.像查伊姆建议的那样,用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);