rs.nkmk.me

Rustの変数名におけるアンダースコアの意味

Posted: | Tags: Rust

Rustの変数名におけるアンダースコア(アンダーバー)_の意味と使い方について説明する。

アンダースコアは数値リテラルにおける桁区切りにも使われる。

変数名の先頭がアンダースコア: 変数が未使用であることを示す

letで宣言した変数がそのあとで使われないとコンパイラから警告(Warning)される。

let x = 100;
// warning: unused variable: `x`
// help: if this is intentional, prefix it with an underscore: `_x`

変数名の先頭にアンダースコア_が付いていると警告されない。

変数名をアンダースコアで始めることで、 コンパイラに未使用変数について警告しないよう指示することができます。 パターン記法 - 名前を_で始めて未使用の変数を無視する - The Rust Programming Language 日本語版

Note: Identifiers starting with an underscore are typically used to indicate an identifier that is intentionally unused, and will silence the unused warning in rustc. Identifiers - The Rust Reference

let _x = 100;
assert_eq!(_x, 100);

そのあとも変数として使用できるが、変数として使用するなら強い理由がない限りアンダースコアで始まる名前は避けたほうがよいだろう。

変数名がアンダースコア: 変数束縛されない

変数名がアンダースコア_のみの場合は変数束縛されない。

_だけを使うのとアンダースコアで始まる名前を使うことには微妙な違いがあることに注意してください。 _x記法はそれでも、値を変数に束縛する一方で、_は全く束縛しません。 パターン記法 - 名前を_で始めて未使用の変数を無視する - The Rust Programming Language 日本語版

便宜上「変数名がアンダースコア」と書いているが、_という名前の変数として使うことはできない。

let _ = 100;
// assert_eq!(_, 100);
// error: no rules expected the token `_`

例えば、タプルを返す関数(複数の値を返す関数)から、必要のない返り値を無視する(捨てる)場合などに使われる。

fn return_multi() -> (bool, i32, f64) {
    (true, 100, 0.123)
}

let (_, x, _) = return_multi();
assert_eq!(x, 100);

letにおける_xと_の違い

letによる変数束縛における_x(アンダースコアから始まる変数名)と_の違いについて述べる。

所有権の移動

右辺が別の変数の場合、左辺が_xだと所有権が変数_xに移動する。

let a = String::from("abc");
let _x = a;
// assert_eq!(a, "abc");
// error[E0382]: borrow of moved value: `a`
assert_eq!(_x, "abc");

左辺が_の場合、そもそも変数束縛されないので、所有権は移動せず元の変数が所有者のまま。

let a = String::from("abc");
let _ = a;
assert_eq!(a, "abc");

Dropされるタイミング

変数がDropされるタイミングも異なる。公式のドキュメントやリファレンスには明記されていないようで、以下のissueやフォーラムが参考になった。

フォーラムで紹介されている以下のタプル構造体を例として用いる。

struct D(i32);
impl Drop for D {
    fn drop(&mut self) {
        println!("dropped {}", self.0);
    }
}

_xに束縛されたインスタンスはスコープの最後でDropされる。一方、_の場合は変数束縛されないので、生成されたインスタンスには所有者がおらず即座にDropされる。

{
    let _x = D(0);
    let _ = D(1);
    println!("------");
}
// dropped 1
// ------
// dropped 0

_の場合でも、右辺が別の変数だと所有権が移動しないため、右辺の変数がそのまま所有権を持ち続ける。束縛されたインスタンスはスコープの最後でDropされる。

{
    let a = D(0);
    let _ = a;
    println!("------");
}
// ------
// dropped 0

なお、例えばタプルの場合、右辺に別の変数があっても即座にDropされる。

{
    let a = D(0);
    let (x, _) = (100, a);
    println!("------");
}
// dropped 0
// ------

これは、タプル(100, a)を生成した時点でインスタンスの所有権がaから一時変数に移動し、さらに左辺が_であるためインスタンスの所有者がいなくなるから。

関数の仮引数名におけるアンダースコア

関数の仮引数名においても、先頭にアンダースコア_を付けることでその仮引数が関数内で未使用であることをコンパイラに示す。

アンダースコア_のみを仮引数名にすることも可能だが、letの場合とは振る舞いが異なるので注意。

仮引数名が_でも、実引数に指定した変数の所有権が移動する。

fn test_func(_: String) {}

let a = String::from("abc");
test_func(a);
// assert_eq!(a, "abc");
// error[E0382]: borrow of moved value: `a`

所有権が移動するため、即座にDropされるのではなく関数ブロックの最後でDropされる。

fn test_func_d(_: D) {
    println!("------");
}

{
    let x = D(0);
    test_func_d(x);
    println!("======");
}
// ------
// dropped 0
// ======

関連カテゴリー