Как лучше всего иметь дело со структурным полем, которое может изменять типы - PullRequest
0 голосов
/ 13 января 2020

Я работаю с библиотекой, которая использует типы Rust для отслеживания состояния. В качестве упрощенного примера, скажем, у вас есть две структуры:

struct FirstStruct {}
struct SecondStruct {}

impl FirstStruct {
    pub fn new() -> FirstStruct {
        FirstStruct {}
    }
    pub fn second(self) -> SecondStruct {
        SecondStruct {}
    }
    // configuration methods defined in this struct
}

impl SecondStruct {
    pub fn print_something(&self) {
        println!("something");
    }
    pub fn first(self) -> FirstStruct {
        FirstStruct {}
    }
}

И для фактического использования этих структур вы обычно следуете шаблону, подобному такому, после печати вы можете остаться во втором состоянии или go вернуться в первое состояние в зависимости от того, как вы используете библиотеку:

fn main() {
    let first = FirstStruct::new();
    let second = first.second(); // consumes first
    second.print_something();
    // go back to default state
    let _first = second.first();
}

Я хочу создать свою собственную структуру, которая обрабатывает изменения состояния внутри и упрощает интерфейс. Это также позволяет мне иметь единственную изменяемую ссылку вокруг, которую я могу передать другим функциям и вызвать метод печати. Его использование должно выглядеть примерно так:

fn main() {
    let mut combined = CombinedStruct::new(FirstStruct::new());
    combined.print();
}

Я нашел следующее решение, которое работает, по крайней мере, в этом упрощенном примере:

enum StructState {
    First(FirstStruct),
    Second(SecondStruct),
}

struct CombinedStruct {
    state: Option<StructState>,
}

impl CombinedStruct {
    pub fn new(first: FirstStruct) -> CombinedStruct {
        CombinedStruct {
            state: Some(StructState::First(first)),
        }
    }
    pub fn print(&mut self) {
        let s = match self.state.take() {
            Some(s) => match s {
                StructState::First(first) => first.second(),
                StructState::Second(second) => second,
            },
            None => panic!(),
        };
        s.print_something();
        // If I forget to do this, then I lose access to my struct
        // and next call will panic
        self.state = Some(StructState::First(s.first()));
    }
}

Я все еще довольно плохо знаком с Rust, но мне это не кажется правильным. Я не уверен, есть ли какая-то концепция, которая мне не хватает, которая могла бы упростить это или это решение могло бы привести к проблемам владения, поскольку мое приложение усложнялось. Есть ли лучший способ сделать это?

Playground link

1 Ответ

0 голосов
/ 13 января 2020

Однажды у меня была похожая проблема, и я пошел в основном с вашим решением, но я избежал Option.

Т.е. я в основном оставил ваш

enum StructState {
    First(FirstStruct),
    Second(SecondStruct),
}

Если операция пытается преобразовать От FirstStruct до SecondStruct я ввел функцию try_to_second примерно следующим образом:

impl StructState {
    fn try_to_second(self) -> Result<SecondState, StructState> {
        /// implementation
    }
}

В этом случае Err означает, что StructState не был преобразован в SecondStruct и сохраняет статус-кво, в то время как значение Ok указывает на успешное преобразование.

В качестве альтернативы вы можете попытаться определить try_to_second для FirstStruct:

impl FirstStruct {
    fn try_to_second(self) -> Result<FirstStruct, SecondStruct> {
        /// implementation
    }
}

Опять же, Err / Ok обозначает неудачу / успех, но в этом случае у вас есть более конкретная информация, закодированная в типе.

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