Rustの文でセミコロンを省略してよい条件
Rustの文でセミコロンを省略してよい条件を説明する。
意味論的な原則
Rustのセミコロンは意味と構文からそれぞれ説明できる。意味論的には、以下の原則を覚えておけば十分である。
- セミコロンで終端された文は強制的に
()
型となる。 - ブロックの途中の文は
()
型でなければならない。ブロックの型はブロックの最後の文の型と等しい。(文がひとつもない場合は()
)
fn main() { let x = { let x = 10; x + 1 }; // x + 1 を返したいので、セミコロンをつけてはいけない if true { 1 } else { 0 }; // 構文上は省略できるが、 () 型にするためにセミコロンをつける println!("Hello!\n"); // 値を返したいわけではないので、セミコロンをつける }
構文上の大原則
大原則は、「文同士はセミコロンで区切らなければならない。ただし }
で終わる文のセミコロンは省略できる」である。
しかし実際には以下の例外を考慮する必要がある。
例外1: }
で終わる式文の場合
文は式文、let文、文マクロ、アイテム文 にわかれている。式文は、式をそのまま文とみなすという規則のことである。
if
, if let
(else
を含む), while
, while let
, loop
, for in
, match
およびブロック式は }
で終わる(これらはC/C++では文だが、Rustでは式であることに注意)。これらの式のパースは、特定条件下で打ち切られる。具体的には、以下の2つの条件を満たしている必要があるようだ。
- これらの式の直前に別のトークンがパースされていない。例えば、
let x = if ..
,(if ..
,1 + if ..
は対象外となる。 - これらの式の直後に
?
や.some_method(..)
が後続しない。例えば、{ }?
や{ }.f()
は対象外となる。
例えば以下の例で、最初の if
文は }
でパースが打ち切られている。しかし、次の if
文は ( 10 )
まででひとつの文となる。つまりこの main
関数には3つの文があることになる。
fn f(x: u32) -> u32 { println!("f({})", x); 10 } fn main() { if true { () } else { () } ( 10 ); 1 + if true { f } else { f } ( 10 ); }
(なお、この打ち切り規則はRESTRICTION_STMT_EXPRと呼ばれ、式文のほかに match
の各節の右辺にも適用される。)
例外2: let
文の場合
let
文ではセミコロンの省略はできない。これはブロックの末尾でも同様である。
fn main() { let x = if true { 0 } else { 0 }; // セミコロンが必要 let x = 0; // セミコロンが必要 }
例外3: }
で終わる文マクロの場合
マクロは {}
, ()
, []
のいずれの括弧でも呼び出せるが、括弧の種類によって構文上の挙動が変わる。文の位置に出現するマクロについては、 {}
で呼び出すとセミコロンを省略できる。
fn main() { println!{"Hello!"} println!{"World!"} }
ただし、書いたマクロが文マクロとして認識されるか、式マクロとして認識されるかには注意が必要である。具体的には以下の条件でマクロが文マクロと認識される。
- 文の位置で始まっている。例えば
1 + foo!{ }
ではfoo!{ }
は式マクロとして認識される。 {}
で呼び出されているか、または;
で終端されている。例えばfoo!{} - 1;
やfoo!(); - 1;
は2つの文として解釈されるが、foo!() - 1;
は1つの文として解釈される。
例外4: }
で終わるアイテム文の場合
アイテムのうちセミコロンを必要としないものは、アイテム文でも同様にセミコロンを必要としない。
fn main() { struct A {} // セミコロン不要 let x = A {}; }
まとめ
Rustの文におけるセミコロンは、意味論的には「型を ()
に強制するために必要」、構文的には「区切り文字として必要。ただし }
の直後では不要」という原則を理解すればそれほど難しくはなさそうだ。しかし、実際には構文解析の一貫性等の問題から、この原則に対する例外があることは、記憶の片隅に留めておいてもいいかもしれない。