質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

90.76%

  • Rust

    19questions

    Rustとは、Mozilla(モジラ)製の実験的な並列かつマルチパラダイムのプログラミング言語です。

Optionalのmatch地獄から脱出したい

解決済

回答 2

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 655

yumetodo

score 2002

普段C++書いていて今回Rustに挑戦しています。

struct TypeElement<'a> {
    before: Option<&'a str>,
    after: Option<&'a str>,
    value: Option<&'a str>,
}

のようなstructに対してPartialEqを実装しようとしています。

  1. valueが双方値を持つなら値の比較結果
  2. (beforeが双方値を持つならその値の比較結果) かつ (afterが双方値を持つなら値の比較結果、双方持たないなら真)
  3. その他は偽

という条件を書こうとした結果、matchがネストしすぎた地獄を産んでしまいました。

impl<'a> PartialEq<TypeElement<'a>> for TypeElement<'a> {
    fn eq<'b>(&self, other: &TypeElement<'b>) -> bool {
        match self.value {
            Some(l) => {
                match other.value {
                    // check `value`
                    Some(r) => l.eq(r),
                    None => false,
                }
            },
            None => {
                match other.value {
                    Some(_) => false,
                    // check `before`, `after`
                    None => {
                        match self.before {
                            Some(b1) => {
                                match other.before {
                                    Some(b2) => {
                                        b1.eq(b2) && match self.after {
                                            Some(a1) => {
                                                match other.after {
                                                    Some(a2) => a1.eq(a2),
                                                    None => false,
                                                }
                                            },
                                            None => {
                                                match other.after {
                                                    Some(_) => false,
                                                    None => true,
                                                }
                                            },
                                        }
                                    }
                                    None => false
                                }
                            },
                            None => false,
                        }
                    },
                }
            },
        }
    }
}

C++17のstd::optionaloperator==があるのですが、Rustにはそれに該当するものを見つけられませんでした。

もうすこし可読性のある書き方をするにはどうしたらいいでしょうか?


追記

gyu-donさんの回答を受けて

impl<'a> PartialEq<TypeElement<'a>> for TypeElement<'a> {
    fn eq<'b>(&self, other: &TypeElement<'b>) -> bool {
        if self.value.is_some() {
            self.value == other.value
        } else {
            self.before.is_some() && self.before == other.before && self.after == other.after
        }
    }
}

のように書き換えたのですが、8e6aa6f

> cargo test
   Compiling cpp_template_type_indenter_lib v0.1.0 (file:///C:/Users/yumetodo/Documents/rust/cpp_template_type_indenter_lib)
error[E0308]: mismatched types
  --> src\type_element.rs:19:83
   |
19 |             self.before.is_some() && self.before == other.before && self.after == other.after
   |                                                                                   ^^^^^^^^^^^ lifetime mismatch
   |
   = note: expected type `std::option::Option<&'a str>`
              found type `std::option::Option<&'b str>`
note: the lifetime 'b as defined on the method body at 15:5...
  --> src\type_element.rs:15:5
   |
15 | /     fn eq<'b>(&self, other: &TypeElement<'b>) -> bool {
16 | |         if self.value.is_some() {
17 | |             self.value == other.value
18 | |         } else {
19 | |             self.before.is_some() && self.before == other.before && self.after == other.after
20 | |         }
21 | |     }
   | |_____^
note: ...does not necessarily outlive the lifetime 'a as defined on the impl at 14:1
  --> src\type_element.rs:14:1
   |
14 | / impl<'a> PartialEq<TypeElement<'a>> for TypeElement<'a> {
15 | |     fn eq<'b>(&self, other: &TypeElement<'b>) -> bool {
16 | |         if self.value.is_some() {
17 | |             self.value == other.value
...  |
21 | |     }
22 | | }
   | |_^

error: aborting due to previous error(s)

error[E0308]: mismatched types
  --> src\type_element.rs:19:83
   |
19 |             self.before.is_some() && self.before == other.before && self.after == other.after
   |                                                                                   ^^^^^^^^^^^ lifetime mismatch
   |
   = note: expected type `std::option::Option<&'a str>`
              found type `std::option::Option<&'b str>`
note: the lifetime 'b as defined on the method body at 15:5...
  --> src\type_element.rs:15:5
   |
15 | /     fn eq<'b>(&self, other: &TypeElement<'b>) -> bool {
16 | |         if self.value.is_some() {
17 | |             self.value == other.value
18 | |         } else {
19 | |             self.before.is_some() && self.before == other.before && self.after == other.after
20 | |         }
21 | |     }
   | |_____^
note: ...does not necessarily outlive the lifetime 'a as defined on the impl at 14:1
  --> src\type_element.rs:14:1
   |
14 error:|  /Could not compile `cpp_template_type_indenter_lib`.
impl<'a> PartialEq<TypeElement<'a>> for TypeElement<'a> {
15Build failed, waiting for other jobs to finish...
 | |     fn eq<'b>(&self, other: &TypeElement<'b>) -> bool {
16 | |         if self.value.is_some() {
17 | |             self.value == other.value
...  |
21 | |     }
22 | | }
   | |_^

error: aborting due to previous error(s)

error: Could not compile `cpp_template_type_indenter_lib`.

To learn more, run the command again with --verbose.

のように怒られます。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 2

checkベストアンサー

+2

タプルを使うと2者をまとめてmatchに掛けることができます。

struct TypeElement<'a> {
    before: Option<&'a str>,
    after: Option<&'a str>,
    value: Option<&'a str>,
}


impl<'a> PartialEq for TypeElement<'a> {
    fn eq<'b>(&self, other: &TypeElement<'b>) -> bool {
        match (self.value, other.value) {
            (Some(l), Some(r)) => return l == r,
            // pass through
            (None, None) => (),
            (_, _) => return false,
        };
        // if both of `self` and `other` don't have values,
        // then compare `before`s and `after`s
        let before = match (self.before, other.before) {
            (Some(b1), Some(b2)) => b1 == b2,
            (_, _) => false,
        };
        let after = match (self.after, other.after) {
            (Some(a1), Some(a2)) => a1 == a2,
            (None, None) => true,
            (_, _) => false,
        };
        before && after
    }
}

beforeとafterをそれぞれ比較したあとで&&を取っているのでショートサーキットされてませんが必要ならばreturnで早期にリターンすることも可能です。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/12/11 18:18

    横道にそれますが、PartialEq<TypeElement<'a>>ではなくPartialEqだけでいいのはどういう仕組によるものでしょうか?

    キャンセル

  • 2017/12/11 18:43

    型パラメータにデフォルト値が設定されているからです。
    https://doc.rust-lang.org/std/cmp/trait.PartialEq.html

    ところで今更気づいたのですが`eq`にライフタイムパラメータが付けられるのはちょっと変ですね。

    ```rust
    impl<'a> PartialEq for TypeElement<'a> {
    fn eq(&self, other: &TypeElement<'a>) -> bool {
    ...
    }
    }
    ```

    のようにかかないといけない筈ですが、隠れた仕様なのかコンパイラのバグなのか即断出来ないです。
    念の為上記のようにライフタイムパラメータなしで書くことをおすすめします。

    キャンセル

  • 2017/12/11 20:01

    あれっ、ライフタイムが違うstrって比較できない・・・?

    キャンセル

  • 2017/12/11 23:20

    形式上はそうですが`& T`のライフタイムはRustコンパイラが自動で調整してくれるので多くの場合は気にしなくて大丈夫です。
    調整というのは長い方のライフタイムを短い方に合わせるので上記'aと'bのようにどちらが長いか決められないときにエラーになります。

    参考
    https://doc.rust-lang.org/src/core/str/mod.rs.html#1611-1618

    キャンセル

+1

C++17のstd::optionalはoperator==があるのですが、Rustにはそれに該当するものを見つけられませんでした。

https://doc.rust-lang.org/std/option/enum.Option.html に
impl<T> PartialEq<Option<T>> for Option<T> where T: PartialEq<T>
とあるので、TPartialEqならば、Option<T>PartialEqになってます。

https://play.rust-lang.org/?gist=1b3aacc49fc6ac4f2b2b2a3f6acd286b&version=stable
のとおり、
双方がSome(x)ならばx同士の比較結果が返って、双方がNoneならばtrueが返る挙動のようです。
(現時点でのOptionのソースを読んでみると、derive(PartialEq)で定義されているので、ここのenumの場合の挙動を示します)

None同士でtrueになるのを避けつつ比較するには、 self.value.is_some() && self.value == other.value のようにしたらいいかと思います。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/12/11 11:13

    あああ、リファレンス見落としていた・・・。試してみます。

    キャンセル

  • 2017/12/11 18:10

    試してみましたがlifetimeが合わないとか怒られます・・・

    キャンセル

  • 2017/12/12 02:37

    ちゃんと試さずにいい加減なこと書いてすみません・・・・。

    ```rust
    fn main() {
    let longlife = Some("aaa");
    let aaa = "aaa".to_owned();
    {
    let shortlife = Some(aaa.as_str());

    // ok
    shortlife == longlife;

    // ng
    longlife == shortlife;
    }
    }
    ```

    たしかにOptionの比較怪しいですね。

    キャンセル

  • 2017/12/22 20:27

    Twitterでバグっぽくてnightlyだと治っているみたいな情報を得ました・・・
    https://twitter.com/_sunnyone/status/941687631078817792

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 90.76%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る

  • Rust

    19questions

    Rustとは、Mozilla(モジラ)製の実験的な並列かつマルチパラダイムのプログラミング言語です。