

rustで以下のような状態遷移図になるコードを構造体を使って実現したい

![1](https://i.stack.imgur.com/gYJxg.png)

途中までなんとなくで作ってみたのですが、  
状態の変化（有効/無効、入力/出力）を記録しておくためにはどうすればいいのかいまいちわかりません  
例えばenabled_input関数を呼ばなければ、input_pulldown関数は呼び出せないようにしたい  
ぜひ教えていただきたいです

[code]

    struct enabled{
        enabled:ENABLED, //有効か無効か(enabled/disabled)
        direction:DIRECTION, //入力か出力か(input/output)
        mode:MODE, //入力モード(highZ/pulldown/pullup)
    }
    
    /*
    struct Disabled; //ピン無効
    struct Enabled; //ピン有効
    struct Output; //出力状態
    struct Input; //入力状態
    struct PulledLow; //入力モードプルロー
    struct PulledHigh; //入力モードプルアップ
    struct HighZ; //入力モードhighZ
    struct DontCare; //未設定
    */
    
    //どのピンにも対応
    impl enabled {
    
        //ピンを無効に
        pub fn disabled(self){
            
        }
    
        //ピンを有効の入力状態に
        pub fn enabled_input(self)->input_status{
            input_status{
                enabled:Enabled,
                direction:Input,
                mode:HighZ,
            }
        }
    
        //ピンを有効の出力状態に
        pub fn enabled_output(self)->output_status{
            output_status{
                enabled:enabled,
                direction:output,
                mode:dontcare,
            }
        }
    }
    
    struct input_status{
    
    }
    //入力状態の時のみ使える
    impl input_status{
        //入力状態0か1
        pub fn set_is_bit(&self)->bool{
    
        }
        //入力モード高抵抗
        pub fn input_high_z(self){
    
        }
        //入力モードプルダウン
        pub fn input_pulldown(self){
    
        }
        //入力モードプルアップ
        pub fn input_pullup(self){
    
        }
    }
    
    struct output_status{
    
    }
    //出力状態の時のみ使える
    impl output_status{
        //出力状態ローかハイ(0か1)
        pub fn set_bit(&mut self,output:bool)->bool{
    
        }
    }
    
    fn main(){
    
    }
    
[/code]


CC BY-SA 4.0
2021-12-06T03:43:51.710
83973
2021-12-07T22:11:27.877
48756
question
rust
rustでの状態遷移図の実現


> 例えばenabled_input関数を呼ばなければ、input_pulldown関数は呼び出せないようにしたい

以下の2種類の方法がありますので、まずどちらにするか選ばないといけません。

  1. 実行時に状態をチェックしてエラーにする
  2. コンパイル時の型検査でエラーにする

具体的なコードはこちらで解説されているとおりです。

  * 組込みRustブックの『[設計契約](https://tomoyuki-nakabayashi.github.io/book/static-guarantees/design-contracts.html)』

そのページの最初のコードが1.で、『型状態』のところのコードが2.になります。

1.は`bool`型や`enum`を使って状態を管理し、メソッドから`Result`型の値を返すことで処理の成功と失敗を表現します。

2.はジェネリクスを使って状態を管理し、状態遷移できない場合はコンパイルエラーにします。

**追記 (2021年12月7日)**

『設計契約』に載っているコードは`GPIO_CONFIG`
などの自動生成された型の定義がないので、そのままではコンパイルできません。そこで、学習用として、2.（型状態）のコードを元に不足している型定義を仮のもので補い、コンパイルできるようにしました。それらの仮のものは`raw_gpio`というモジュールに入れてあります。

これを使って、以下のことを確認してみてください。

  * このままでコンパイルできること
  * `main`関数内の「これはできません」と書かれているコードをコメントアウトすると、コンパイルエラーになること

このコードを読んでわからないことがあれば、コメント欄、または、別の新しい質問を作って聞いてください。

[code]

    /// GPIOインタフェース
    pub struct GpioConfig<EN, DIR, MD> {
        periph: raw_gpio::RawGpioConfig,
        _enabled: EN,
        _direction: DIR,
        _mode: MD,
    }
    
    // GpioConfigのための型状態
    
    /// ピン無効
    pub struct Disabled;
    
    /// ピン有効
    pub struct Enabled;
    
    /// 出力状態
    pub struct Output;
    
    /// 入力状態
    pub struct Input;
    
    /// 入力モード プルロー
    pub struct PulledLow;
    
    /// 入力モード プルアップ
    pub struct PulledHigh;
    
    /// 入力モード ハイインピーダンス（絶縁状態）
    pub struct HighZ;
    
    /// 未設定
    pub struct DontCare;
    
    impl GpioConfig<Disabled, DontCare, DontCare> {
        fn new() -> Self {
            Self {
                periph: raw_gpio::RawGpioConfig::default(),
                _enabled: Disabled,
                _direction: DontCare,
                _mode: DontCare,
            }
        }
    }
    
    /// これらの関数はどのGPIOピンにも使えます
    impl<EN, DIR, INMD> GpioConfig<EN, DIR, INMD> {
        pub fn into_disabled(mut self) -> GpioConfig<Disabled, DontCare, DontCare> {
            self.periph.disable();
            GpioConfig {
                periph: self.periph,
                _enabled: Disabled,
                _direction: DontCare,
                _mode: DontCare,
            }
        }
    
        pub fn into_enabled_input(mut self) -> GpioConfig<Enabled, Input, HighZ> {
            self.periph.enable();
            self.periph.set_direction_to_input();
            self.periph.set_input_mode_to_high_z();
            GpioConfig {
                periph: self.periph,
                _enabled: Enabled,
                _direction: Input,
                _mode: HighZ,
            }
        }
    
        pub fn into_enabled_output(mut self) -> GpioConfig<Enabled, Output, DontCare> {
            self.periph.enable();
            self.periph.set_direction_to_input();
            self.periph.set_input_mode_to_high_z();
            GpioConfig {
                periph: self.periph,
                _enabled: Enabled,
                _direction: Output,
                _mode: DontCare,
            }
        }
    }
    
    /// この関数はOutputピンに使用できます
    impl GpioConfig<Enabled, Output, DontCare> {
        pub fn set_bit(&mut self, set_high: bool) {
            self.periph.set_output_status(set_high);
        }
    }
    
    /// これらのメソッドは、有効化された入力GPIOに使えます
    impl<INMD> GpioConfig<Enabled, Input, INMD> {
        pub fn bit_is_set(&mut self) -> bool {
            self.periph.read();
            self.periph.get_input_status()
        }
    
        pub fn into_input_high_z(mut self) -> GpioConfig<Enabled, Input, HighZ> {
            self.periph.set_input_mode_to_high_z();
            GpioConfig {
                periph: self.periph,
                _enabled: Enabled,
                _direction: Input,
                _mode: HighZ,
            }
        }
    
        pub fn into_input_pull_down(mut self) -> GpioConfig<Enabled, Input, PulledLow> {
            self.periph.set_input_mode_to_pull_low();
            GpioConfig {
                periph: self.periph,
                _enabled: Enabled,
                _direction: Input,
                _mode: PulledLow,
            }
        }
    
        pub fn into_input_pull_up(mut self) -> GpioConfig<Enabled, Input, PulledHigh> {
            self.periph.set_input_mode_to_pull_high();
            GpioConfig {
                periph: self.periph,
                _enabled: Enabled,
                _direction: Input,
                _mode: PulledHigh,
            }
        }
    }
    
    fn main() {
        // -------------------------------------
        // 例1：未設定から高抵抗入力
        // -------------------------------------
        let pin: GpioConfig<Disabled, _, _> = GpioConfig::new();
    
        // これはできません、ピンが有効になっていません！
        // pin.into_input_pull_down();
    
        // 今度は、未設定から高抵抗入力に変えます
        // 訳注：into_enabled_input()は入力モードを高抵抗にします
        let mut input_pin = pin.into_enabled_input();
    
        // ピンから値を読みます
        let _pin_state = input_pin.bit_is_set();
    
        // これはできません、入力ピンはこのインタフェースを持っていません！
        // input_pin.set_bit(true);
    
        // -------------------------------------
        // 例2：高抵抗入力からプルダウン入力
        // -------------------------------------
        let mut pulled_low = input_pin.into_input_pull_down();
        let _pin_state = pulled_low.bit_is_set();
    
        // -------------------------------------
        // 例3：プルダウン入力から出力、ハイを設定
        // -------------------------------------
        let mut output_pin = pulled_low.into_enabled_output();
        output_pin.set_bit(false);
    
        // これはできません、出力ピンはこのインタフェースを持っていません！
        // output_pin.into_input_pull_down();
    }
    
    /// このモジュールはRustコードの学習用に用意した「仮想的なハードウェア」の状態を持つ
    pub(crate) mod raw_gpio {
    
        #[derive(Debug)]
        enum RawDirection {
            In,
            Out,
        }
    
        #[derive(Debug)]
        enum RawInputMode {
            PulledLow,
            PulledHigh,
            HighZ,
        }
    
        #[derive(Debug)]
        enum RawStatus {
            Low,
            High,
        }
    
        /// GPIO設定構造体
        #[derive(Debug)]
        pub(crate) struct RawGpioConfig {
            enabled: bool,
            direction: RawDirection,
            input_mode: RawInputMode,
            status: RawStatus,
        }
    
        impl Default for RawGpioConfig {
            fn default() -> Self {
                Self {
                    enabled: false,
                    direction: RawDirection::Out,
                    input_mode: RawInputMode::HighZ,
                    status: RawStatus::High,
                }
            }
        }
    
        impl RawGpioConfig {
            pub(crate) fn enable(&mut self) {
                self.enabled = true;
            }
    
            pub(crate) fn disable(&mut self) {
                self.enabled = false;
            }
    
            pub(crate) fn set_direction_to_input(&mut self) {
                self.direction = RawDirection::In;
            }
    
            pub(crate) fn set_input_mode_to_high_z(&mut self) {
                self.input_mode = RawInputMode::HighZ;
            }
    
            pub(crate) fn set_input_mode_to_pull_high(&mut self) {
                self.input_mode = RawInputMode::PulledHigh;
            }
    
            pub(crate) fn set_input_mode_to_pull_low(&mut self) {
                self.input_mode = RawInputMode::PulledLow;
            }
    
            pub(crate) fn read(&mut self) {
                // 学習用のコードなので何もしない
            }
    
            pub(crate) fn get_input_status(&self) -> bool {
                // 学習用のコードなので、とりあえず常にtrueを返すようにしておく
                true
            }
    
            pub(crate) fn set_output_status(&mut self, set_high: bool) {
                if set_high {
                    self.status = RawStatus::High;
                } else {
                    self.status = RawStatus::Low;
                }
            }
        }
    }
    
[/code]


CC BY-SA 4.0
2021-12-06T10:12:11.900
83980
2021-12-07T22:11:27.877
2021-12-07T22:11:27.877
14101
14101
83973
answer