Rust の有効な識別子(変数名、関数名、トレイト名など)は、数字、文字、アンダースコアで構成され、数字で始まることはできません。これは多くの言語と同じです。Rust では将来的には他の Unicode 文字を識別子として許可する予定であり、raw identifier 機能もあります。これにより、キーワードを識別子として使用することができます。たとえば、r#self という用途が最も一般的です。
変数の宣言: let variable: i32 = 100;
、Rust では従来の言語とは異なる変数の宣言方法が採用されています。ここでは、変数の宣言を見てみましょう。変数の宣言では、変数名が先に来て、その後に変数の型が続きます。let variable: i32;
という感じです。
このような変数宣言の利点は、構文解析にとってより分析しやすくなり、変数宣言文で最も重要なのは変数名であり、変数名を前面に出して変数名の重要性を明示することです。型は変数名の追加の説明であり、コンテキストから変数の型を推論することができます。もちろん、Rust の自動型推論には制限があり、推論できない型には型の注釈を手動で追加する必要があります。
変数宣言での let の使用は、関数型言語の考え方を借りています。let はバインディングの意味を示し、変数名とメモリの間にバインディング関係を作成します。Rust では、通常、宣言されたローカル変数と初期化された文を「変数バインディング」と呼びます。ここで強調されているのは「バインディング」の意味であり、これは C++/C の「代入初期化文」とは異なります。
変数定義のいくつかの問題
Rust では、すべての変数は適切に初期化された後でなければ使用できません。未初期化の変数を使用するというエラーは、Rust では起こりません。
宣言された初期化変数のチェック
さきほどのlet variable: i32;
は宣言であり、変数に値を割り当てていないため、他の言語では通用するかもしれませんが、Rust ではコンパイラが直接エラーを報告します(この未割り当て(定義)の変数を後で使用する場合、Rust コンパイラはコードに基本的な静的フローチェックを行い、変数が使用される前に必ず初期化されることを確認します。variable は値にバインドされていないため、このようなコードは多くのメモリの安全性の問題を引き起こす可能性があります。予期しない計算結果、プログラムのクラッシュなどが発生するため、Rust コンパイラはエラーを報告する必要があります。
let variable: i32;
println!("variable = {}", variable); // error[E0381]: use of possibly unintialized 'variable'
分岐フローが初期化されていない変数を生成していないかどうかをチェックする
Rust コンパイラの静的分岐フロー解析はかなり厳格です。
fn main() {
let x: i32;
if true {
x = 1;
} else {
x = 2;
}
println!("x = {}", x);
}
ここでの if ブランチのすべての場合で変数 x に値がバインドされているため、実行できます。しかし、else ブランチを削除すると、コンパイラはエラーを報告します:
error: use of possibly unintialized variable : 'x'
println!("x = {}", x);
ここからわかるように、コンパイラは変数 x が正しく初期化されていないことを検出しています。else ブランチを削除すると、コンパイラの静的分岐フロー解析は、if 式の条件が true であるかどうかを認識できないため、すべての分岐をチェックする必要があります。(これについては、プログラム言語の領域で研究が行われており、ソフトウェアの静的解析などがあります。いくつかの参考資料:南京大学のソフトウェア解析コース)
println!
文も削除すると、正常にコンパイルおよび実行できます。これは、if 式の外部にあるprintln!
で変数 x が使用されていないためです。if 式内で x を使用する唯一の場所で値がバインドされているため、コンパイルは正常に行われます。
// 例があります
fn test(condition: bool ){
let x: i32; // xを宣言します
if condition {
x = 1; // xを初期化します。ここで初期化されます
println!("{}", x);
}
// 条件が満たされない場合、xは初期化されません
//しかし、心配しないでください、ここでxを使用しなければ問題ありません
}
ループ内で未初期化の変数が生成されていないかどうかをチェックする
ループ内で break キーワードを使用すると、ブランチ内の変数の値が返されます。
fn main() {
let x: i32;
loop {
if true {
x = 2;
break;
}
}
println!("{}", x);// 2
}
Rust コンパイラの静的分岐フロー解析は、break が x の値を返すことを認識しています。したがって、loop の外側の println! は x の値を正常に表示できます。
空の配列やベクターで変数を初期化することができます
変数に空の配列やベクターをバインドする場合、明示的に型を指定する必要があります。そうしないと、コンパイラはその型を推論できません。
fn main() {
let a: Vec<i32> = vec![];
let b: [i32; 0] = [];
}
明示的な型注釈を付けない場合、コンパイラはエラーを報告します:error[e0282]: type annotation needed
空の配列やベクターは変数の初期化に使用できますが、現時点では定数や静的変数の初期化には使用できません。
所有権が移動されて初期化されていない変数が生成される
既に初期化された変数 y を別の変数 y2 にバインドすると、Rust は論理的に未初期化の変数と見なします。ここでの y と y2 は移動セマンティクスの変数であり、移動セマンティクスの変数は所有権の移動が発生し、値セマンティクスの変数は他の C++ 言語のデフォルトと同様に値のコピーが行われます。
fn main() {
let x = 42; //プリミティブ型は値セマンティクスであり、デフォルトでスタックに格納されます
let y = Box::new(4); //変数はヒープにボックス化され、Box::newメソッドはヒープ上にメモリを割り当ててポインタを返します
//そして、yにバインドされ、ポインタyはスタックに格納されます
println!("{}", y);
let x2 = x;
let y2 = y;
//println!("{}", y);//所有権が移動されたため、変数yは未初期化の変数と見なすことができます
//ただし、変数に再び値をバインドする場合、変数yは使用可能です。これは再初期化と呼ばれるプロセスです
}