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] を紹介しました。
使わなければならない・知らないと困るようなものではありませんが、知っていれば使える場面があるかもしれません。
参考
- Diagnostics - The Rust Reference
https://doc.rust-lang.org/reference/attributes/diagnostics.html#the-must_use-attribute
Discussion