Подтипы, использующие подобную дженерику конструкцию - PullRequest
0 голосов
/ 10 ноября 2018

У меня есть структура, в которой существует ровно две версии. То есть структура имеет атрибут, описывающий, какая это версия (bool или enum с двумя вариантами), которая передается в качестве аргумента в конструктор. Какая версия будет иметь структура, известно во время компиляции. В большинстве методов этой структуры вызывается соответствующий метод (для атрибута этой структуры), в зависимости от значения атрибута. Это приводит ко многим операторам if в impl этой структуры.

Я подумал о том, чтобы переместить весь код в черту, но это не показалось уместным: динамическая диспетчеризация не требуется, почти во всех методах не будет параметра self, и для всех атрибутов потребуются сеттеры / геттеры. Я все еще остался бы с двумя идентичными объявлениями структуры. Кроме того, эта черта не описывает общее поведение, которое должны реализовывать другие структуры.

Было бы идеально, если бы я мог вместо этого создать общую версию этой структуры и создать одну из двух версий. Чтобы из-за отсутствия лучшего слова, два «подтипа» моей структуры, только с одним методом, который имеет другое поведение. Возможна ли такая вещь?

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

1 Ответ

0 голосов
/ 10 ноября 2018

Используйте черту для поведения, которое имеет несколько реализаций. Существует множество комбинаций способов их использования, вот один из них:

use std::marker::PhantomData;

trait Core {
    fn print();
}

#[derive(Debug, Default)]
struct PrintA;
impl Core for PrintA {
    fn print() {
        print!("a")
    }
}

#[derive(Debug, Default)]
struct PrintB;
impl Core for PrintB {
    fn print() {
        print!("b")
    }
}

#[derive(Debug, Default)]
struct Thing<C>(PhantomData<C>);

impl<C: Core> Thing<C> {
    fn common() {
        print!(">");
        C::print();
        println!("<")
    }
}

fn main() {
    Thing::<PrintA>::common();
    Thing::<PrintB>::common();
}

Или другое:

trait Core {
    fn select<'a>(left: &'a i32, right: &'a i32) -> &'a i32;
}

#[derive(Debug, Default)]
struct Left;
impl Core for Left {
    fn select<'a>(left: &'a i32, _right: &'a i32) -> &'a i32 {
        left
    }
}

#[derive(Debug, Default)]
struct Right;
impl Core for Right {
    fn select<'a>(_left: &'a i32, right: &'a i32) -> &'a i32 {
        right
    }
}

#[derive(Debug, Default)]
struct Thing<C> {
    kind: C,
    left: i32,
    right: i32,
}

impl Thing<Left> {
    fn new_left(left: i32, right: i32) -> Self {
        Self {
            left,
            right,
            kind: Left,
        }
    }
}
impl Thing<Right> {
    fn new_right(left: i32, right: i32) -> Self {
        Self {
            left,
            right,
            kind: Right,
        }
    }
}

impl<C: Core> Thing<C> {
    fn add_one(&self) -> i32 {
        self.select() + 1
    }

    fn select(&self) -> &i32 {
        C::select(&self.left, &self.right)
    }
}

pub fn l() -> i32 {
    let l = Thing::new_left(100, 200);
    l.add_one()
}

pub fn r() -> i32 {
    let r = Thing::new_right(100, 200);
    r.add_one()
}

Следует отметить, что последний пример компилируется в следующий код LLVM IR:

define i32 @_playground_l() {
start:
  ret i32 101
}

define i32 @_playground_r()  {
start:
  ret i32 201
}

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

  • черты не подразумевают динамическую отправку. См Мономорфизация .
  • методы черт не требуют self

Если бы не ремонтопригодность, я просто скопировал бы весь код

Похоже на то, что макросы могут подойти, если вы не можете обрабатывать черты.

...