Типизированный построитель: обход инвариантов во время выполнения

Я хотел бы определить, есть ли способ следовать шаблону состояния типа, но разрешить инварианты, если есть альтернатива.

Предположим, у меня есть шаблон построения структуры-обертки вокруг признака.

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 возвращает Option<Wrapper<T>>, где Option::None означает, что сборка не удалась; сборка была снабжена инвариантами, то есть недопустимыми аргументами.


Я могу использовать шаблон типизированного построителя, чтобы предотвратить время компиляции инвариантов вместо времени выполнения.

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,
        }
    }
}

В этом примере я бы хотел, чтобы TypeBuilderinner имело значение по умолчанию T, если оно доступно по умолчанию.

Таким образом, инварианты будут:

  • None если T: !Default

Я не знаю, возможен ли такой шаблон проектирования без ночных функций, таких как специализация черт.

Итак, вы имеете в виду: если вызывающая сторона не предоставляет значение для inner, вызывая with_inner в сборщике, то он должен создать значение по умолчанию? И поэтому, если T не реализуется Default, вам нужна ошибка компиляции?

harmic 22.04.2024 08:06

@harmic Почти, мне нужна ошибка компиляции, если T не указан и он не реализует Default. Если T указан, то Default не требуется.

Jinyoung Choi 22.04.2024 17:51

Итак, если кто-то создает TypedBuilder, а затем вызывает build без предварительного вызова with_inner && T не реализует Default, то вам нужна ошибка компиляции? Если да - однозначно невозможно.

harmic 23.04.2024 02:54

@harmic, это определенно возможно, см. мой ответ ниже.

Jinyoung Choi 23.04.2024 04:05

Умно, я бы никогда об этом не подумал.

harmic 23.04.2024 05:21
Почему Python в конце концов умрет
Почему Python в конце концов умрет
Последние 20 лет были действительно хорошими для Python. Он прошел путь от "просто языка сценариев" до основного языка, используемого для написания...
1
5
51
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Решение заключалось в том, чтобы использовать второй обобщенный тип для отслеживания создаваемого целевого типа, а затем разрешить инвариант, если целевой тип равен Default.

Смотрите ниже детская площадка:

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;
}

Другие вопросы по теме