I have a struct with several states:
struct Select;
struct Insert;
struct Delete;
// ...more states
struct Query<T> {
// ... some non-generic fields
marker: PhantomData<T>
}
I have some functionality which I would like to implement for some, but not all of states. I imagine it should look something like this:
impl Query<T> for T: Select | Update | Delete {
// implement only once
fn foo() -> Query<T>;
}
Is this possible and if so, how?
There are two main methods you could do that. With trait guards, as Chayim suggested, or with a macro. Let's see how each of those solutions work and what are their trade-offs.
This is a pretty easy concept, however it has a subtle nuances. We want to define some kind of Guard
trait, implement it for some types and then leverage generic implementation. For example:
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!()
}
}
This however has an important drawback. Since Guard
is a public trait if someone would implement it for some other type Other
then impl<T: Guard>
would apply to Other
type as well. This could be undesired, as depending on your project's requirements this could lead to broken invariants.
We could try making Guard
a private trait, but this currently (rustc 1.70.0
, 2021 Edition
) results in a warning and will become an error in the 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
We can solve it by using a 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!()
}
}
This however doubles number of implementations we have to write and results in slightly uglier API (since we "leak" private seal to public API). It could also result in less readable documentation, since reader must check which types implement Guard
in the first place.
Alternatively you could use a declarative macro. This would result in a very similar syntax to what you described.
macro_rules! foo_impl {
($($state:ty),*) => {
$(impl Query<$state> {
pub fn foo() -> Query<$state> {
todo!()
}
})*
};
}
foo_impl!(Select, Update, Delete);
This has a couple of advantages:
If you on the other hand like better solution with traits you can still write a macro that would automatically implement guard and it's seal for your types.
macro_rules! guard_impl {
($($state:ty),*) => {
$(
impl sealed::Sealed for $state {}
impl Guard for $state {}
)*
};
}
guard_impl!(Select, Update, Delete);