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やフォーラムが参考になった。
- Fix semantics of let _ = ... not to drop · Issue #10488 · rust-lang/rust
- Documet let _ = ... behavior saliently, or even warn about it · Issue #40096 · rust-lang/rust
- What are pipes and underscores doing in Rust? - help - The Rust Programming Language Forum
フォーラムで紹介されている以下のタプル構造体を例として用いる。
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
// ======