Rust の有効な識別子(変数名、関数名、トレイト名など)は、数字、文字、アンダースコアで構成され、数字で始まることはできません。これは多くの言語と同じです。Rust では将来的には他の Unicode 文字を識別子として許可する予定であり、raw identifier 機能もあります。これにより、キーワードを識別子として使用することができます。たとえば、r#self は FFI で最も一般的に使用されます。
変数の宣言: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 式で値がバインドされているためです。したがって、コンパイルは正常に行われます。
// 例
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 と見なします。ここでの 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は使用可能です。これは再初期化と呼ばれるプロセスです
}