🐡

Rust の must_use 属性を使ったことはありますか?

に公開

導入: Rust の must_use 属性を使ったことはありますか?

Rust の #[must_use] (must_use 属性) を使ったことはありますか?

Rust を趣味や業務である程度は書いていても、もしかすると知らないかもしれません。ただ、これ自体は知らなくても、これを使用した効果については知っている (見たことがある) はずです。

実例: Result

たとえば、次のコードでは、この属性の効果を見ることができます。

fn main() {
    fn command() -> Result<(), Box<dyn std::error::Error>> {
        Ok(())
    }
    
    command();
}

Result を返す関数である command があり、呼び出しますが、その戻り値を使用していません。

このコードはコンパイルすると、使用されていない Result についての、次のような警告が表示されます。

warning: unused `Result` that must be used
 --> src/main.rs:6:5
  |
6 |     command();
  |     ^^^^^^^^^
  |
  = note: this `Result` may be an `Err` variant, which should be handled
  = note: `#[warn(unused_must_use)]` (part of `#[warn(unused)]`) on by default
help: use `let _ = ...` to ignore the resulting value
  |
6 |     let _ = command();
  |     +++++++

ここで表示されている unused_must_use のきっかけを与えるものが #[must_use] です。

Result の定義を見ましょう。

https://github.com/rust-lang/rust/blob/1.91.1/library/core/src/result.rs#L541-L560

/// `Result` is a type that represents either success ([`Ok`]) or failure ([`Err`]).
///
/// See the [module documentation](self) for details.
#[doc(search_unbox)]
#[derive(Copy, Debug, Hash)]
#[derive_const(PartialEq, PartialOrd, Eq, Ord)]
#[must_use = "this `Result` may be an `Err` variant, which should be handled"]
#[rustc_diagnostic_item = "Result"]
#[stable(feature = "rust1", since = "1.0.0")]
pub enum Result<T, E> {
    /// Contains the success value
    #[lang = "Ok"]
    #[stable(feature = "rust1", since = "1.0.0")]
    Ok(#[stable(feature = "rust1", since = "1.0.0")] T),

    /// Contains the error value
    #[lang = "Err"]
    #[stable(feature = "rust1", since = "1.0.0")]
    Err(#[stable(feature = "rust1", since = "1.0.0")] E),
}

確かに #[must_use = "..."] の形で must_use 属性が指定されています。

他の例: builder

他の例も考えてみましょう。使用せずに捨ててしまうのがまずい Result はわかりやすいケースですが、他にどのようなものがあるでしょう。

ぼくがパッと思いついたのは builder っぽいパターンでした。

fn main() {
    #[derive(Clone, Default)]
    struct Options {
        b: bool,
        n: i32,
    }
    
    impl Options {
        #[must_use]
        fn b(&self, b: bool) -> Self {
            Self { b, ..self.clone() }
        }
        
        #[must_use]
        fn n(&self, n: i32) -> Self {
            Self { n, ..self.clone() }
        }
    }

    // 誤った使い方
    let options = Options::default();
    options.b(true); // 内部状態を書き換えるものと勘違い 1
    options.n(123); // 勘違い 2
    println!("b={},n={}", options.b, options.n); // b=false,n=0

    // 本来期待している使い方
    let options = Options::default().b(true).n(123);
    println!("b={},n={}", options.b, options.n); // b=true,n=123
}

使い方を誤って作った値を捨ててしまっています。

こういうケースでは上記の例のように #[must_use] を指定していれば、次のような警告が出て、気づきやすくなります。

warning: unused return value of `Options::b` that must be used
  --> src/main.rs:22:5
   |
22 |     options.b(true); // 内部状態を書き換えるものと勘違い 1
   |     ^^^^^^^^^^^^^^^
   |
   = note: `#[warn(unused_must_use)]` (part of `#[warn(unused)]`) on by default
help: use `let _ = ...` to ignore the resulting value
   |
22 |     let _ = options.b(true); // 内部状態を書き換えるものと勘違い 1
   |     +++++++

warning: unused return value of `Options::n` that must be used
  --> src/main.rs:23:5
   |
23 |     options.n(123); // 勘違い 2
   |     ^^^^^^^^^^^^^^
   |
help: use `let _ = ...` to ignore the resulting value
   |
23 |     let _ = options.n(123); // 勘違い 2
   |     +++++++

おわりに

今回は使用しなければいけないということを警告するための #[must_use] を紹介しました。

使わなければならない・知らないと困るようなものではありませんが、知っていれば使える場面があるかもしれません。

参考

GitHubで編集を提案
ドクターメイト

Discussion