W.I.S. Laboratory
menu-bar

Rust


Rustの行末セミコロンはいるのかいらないのか

RustはCやC++などと同様、行末にはセミコロンが基本的に必要だ。
ただ、セミコロンを書いたらエラーになる箇所と、書かなかったらエラーになる箇所と、書いても書かなくてもエラーにはならない箇所がある。
原則的には、下のような感じ。

書いたらエラーになる箇所

  • 戻り値のある関数で return を明示していない最終行

書かなかったらエラーになる箇所

  • let文の終わり
  • 代入文の終わり
  • 戻り値のある関数の最終行ではない行(つまりコードのほとんどの行末)

書いても書かなくてもエラーにならない箇所

  • 戻り値の無い関数の最終行
  • 戻り値のある関数で return を明示した最終行

Rust では行末セミコロンがある場合と無い場合で意味合いが異なる。
ある場合は「その値を破棄する」、無い場合は「その値を返す」という意味になるのだが、書いても書かなくてもエラーにならない箇所はどちらにしても何も起こらない。

また原則的にはブレース閉じ「 } 」の後にセミコロンは不要なのだが、例外的に「 }; 」としなくてはならない箇所がある。
変数に何かを代入した後には必ずセミコロンを書かなくてはならない。
例えば構造体やユニオンのインスタンスを変数に代入した場合や、if-elseを式として使い戻り値を変数に代入した場合、クロージャを変数に代入した場合などには必要となる。(letがあってもなくても必要)

struct Foo {
 name: String,
 age: i32,
 height: i32,
}

impl Foo {
 fn get_linkingstr(&self) -> String {
  let mut linkstr = "".to_string();
  let age = self.age.to_string();
  let height = self.height.to_string();
  linkstr.push_str(&self.name);
  linkstr.push_str(" ");
  linkstr.push_str(&age);
  linkstr.push_str(" ");
  linkstr.push_str(&height);

  linkstr // ← この値を返したい(returnしたい)のでセミコロンはいらない
 }
}

fn main() {
 let a = 1;
 print!("{} ", a);

 let f: Foo;
 f = Foo {
  name: String::from("Taro"),
  age: 15,
  height: 170,
 }; // ← インスタンスを変数に代入した後なのでセミコロンが必要

 println!("{}", f.get_linkingstr());

 let b = 8;
 let i;
 i = if b % 2 == 0 {
  b * 10
 } else {
  b * 20
 }; // ← if-else式の戻り値を変数に代入した後なのでセミコロンが必要

 println!("{}", i);

 let c;
 c = | x: i32 | { x * 2 }; // ← クロージャを変数に代入した後なのでセミコロンが必要

 println!("{}", c(15));
}

他にも例外がいろいろあって「必ずこうだよ」といえない部分も少なからずあるのだが、だいたいこの辺りを押さえておけば混乱することはないと思う。
セミコロンを書いてはいけないのは、戻り値のある関数の最終行なのだが、return を明示した場合は書いても書かなくてもエラーにはならない。
return を明示せず行末セミコロン無しのほうが Rust らしい書き方になると思う。
この最後に評価された値が関数の戻り値になるというのは、Rust に限ったことではなく、Cもそういう仕様だった。(ずっと昔の話だ)
「関数最終行で return を明示したほうがコードが読みやすくなりバグを減らせる」という理由で慣例的に明示するようになったらしい。(現在ではreturn明示の無い関数を書くとコンパイラに怒られる)
私がCを書き始めた頃(何十年も前)は、return 明示をせずに戻り値のある関数を書いている人がまだ大勢いた記憶があり、その頃先輩から「returnは書かなくても値は戻るけど、最近はreturn明示をする書き方が良いとされている。バグを減らすためにもそのほうがいい」と教わったのを覚えている。
Rustはその書き方を復活させて「セミコロンのあるなし」で戻り値かどうかをコンパイラが明確に区別できるようにした、という感じがする。

関数の戻り値よりも混乱しそうなのは、ブレース閉じが「 } 」なのか「 }; 」なのかというところかもしれない。
値やアドレスを変数に代入した後はセミコロンが必要なので、とりあえずそれだけ意識しておけばエラーは減らせると思う。
もちろん関数の最終行で、変数に代入せずセミコロンを書かなければ、その値を戻り値にできる。
「その値を変数に代入しなかったら戻り値として返せる時」はセミコロンが必要だ。
それはブレースに限ったことではなく、リテラルもそうだし、配列やベクタのブラケット、タプルやボックスのパーレンなども同様だ。

let x = 10;
let s = "abc";
let a = [10, 20, 30];
let v = vec![10, 20, 30];
let t = (10, "ab", 30);
let b = Box::new(10);
let s = S {x:10, y:20, z:30};
let i = if x>10 {x} else {x*2};
let c = |x:i32| {x*2};

代入文の最後は、それがブレースだろうがブラケットだろうがパーレンだろうが、例外なくセミコロンを書く必要がある。
それはC++でも同じだしJavaやPHPでもそうなのでRustが特別というわけではない。
ただif-else式については、通常のif文にはセミコロンが不要であることや、if-elseが式として使えるkotlinの場合は最後のセミコロンが不要ということも手伝ってか、違和感が大きい。
またインスタンスを生成して変数に代入する部分も、他の言語は大抵「new クラス名;」で終わりメンバの初期化はコンストラクタ任せであることが多いので、Rustの書き方が浮いて見えるということもあるかもしれない。(とはいえC++11以降では似たような書き方ができるし、JavaScriptのオブジェクトリテラルを低レベル言語で書けると思えばすんなり入ってくるのではないかと)


[ 戻る ]
saluteweb