I would like to determine if there is a way to follow the type state pattern but allow invariants if there is an alternative.
Suppose I have a builder pattern for a wrapper struct around a trait.
trait Trait {
}
struct Wrapper<T: Trait> {
inner: T
}
struct Builder<T: Trait> {
inner: Option<T>
}
impl<T: Trait> Wrapper<T> {
fn builder() -> Builder<T> {
Builder {
inner: None
}
}
}
impl<T: Trait> Builder<T> {
fn with_inner(mut self, inner: T) -> Self {
self.inner = inner;
self
}
fn build(self) -> Option<Wrapper<T>> {
Some(Wrapper {
inner: self.inner?,
})
}
}
Builder::build
returns an Option<Wrapper<T>>
where Option::None
represents that the build failed; the build was supplied with invariants i.e. invalid arguments.
I can use a typed builder pattern to prevent invariants compile time instead of runtime.
struct Invariant;
struct TypedBuilder<T> {
inner: T
}
impl<T: Trait> Wrapper<T> {
fn typed_builder() -> TypedBuilder<Invariant> {
TypedBuilder {
inner: Invariant
}
}
}
impl<T> TypedBuilder<T> {
fn with_inner<T2: Trait>(self, inner: T2) -> TypedBuilder<T2> {
TypedBuilder {
inner: inner
}
}
}
impl<T: Trait> TypedBuilder<T> {
fn build(self) -> Wrapper<T> { // infallible
Wrapper {
inner: self.inner,
}
}
}
In this example, I would like TypeBuilder
's inner
to default T
if it is defaultable.
So the invariants would be:
None
if T: !Default
I don't know if such a design pattern is possible without nightly features such as trait specialization.
The solution was to use a second generic to track the target type to build, then allow the invariant if the target type is Default
.
See below playground:
use std::marker::PhantomData;
trait Trait {}
struct Wrapper<T: Trait> {
inner: T
}
struct Invariant;
struct TypedBuilder<Target, T> {
inner: T,
marker: PhantomData<Target>
}
impl<T: Trait> Wrapper<T> {
fn typed_builder() -> TypedBuilder<T, Invariant> {
TypedBuilder {
inner: Invariant,
marker: PhantomData,
}
}
}
impl<Target, T> TypedBuilder<Target, T> {
fn with_inner<T2: Trait>(self, inner: T2) -> TypedBuilder<Target, T2> {
TypedBuilder {
inner: inner,
marker: self.marker,
}
}
}
impl<T: Trait> TypedBuilder<T, T> {
fn build(self) -> Wrapper<T> { // infallible
Wrapper {
inner: self.inner,
}
}
}
impl<T: Trait + Default> TypedBuilder<T, Invariant> {
fn build(self) -> Wrapper<T> { // infallible
Wrapper {
inner: T::default(),
}
}
}
#[derive(Default)]
struct Foo;
struct Bar;
impl Trait for Foo {}
impl Trait for Bar {}
fn main() {
let _foo: Foo = Wrapper::<Foo>::typed_builder()
.build().inner;
let _foo: Foo = Wrapper::<Foo>::typed_builder()
.with_inner(Foo)
.build().inner;
// Can't compile since Bar is not supplied, and is not default
// let _bar: Bar = Wrapper::<Bar>::typed_builder()
// .build().inner;
let _bar: Bar = Wrapper::<Bar>::typed_builder()
.with_inner(Bar)
.build().inner;
}