Rustの「変数」には、今まで私が扱ってきた言語にはない機能がありました。
- 不変変数: 値が変更できる変数
- 可変変数: 値が変更できない変数
があること。
不変変数
不変変数は再代入できません。
let x = 1;
// 再代入するとコンパイルエラーになる。cannot mutate immutable variable `x`
x = x + 1;
x = x + 1
のように再代入すると「cannot mutate immutable variable x
」コンパイルエラーになります。
なので、下記のように演算ごとに変数を宣言します。
let x = 1;
let x_plus_one = x + 1;
これは別の機会で話した「コードの可読性、品質向上」の観点での「変数の再利用(再代入)は避ける」点で良いことだなと思いました。
ただ、演算結果も演算前と同じ意味の値を代入する場合は、再利用はありだと思います。
たとえば下記のように「合計値」を計算する場合など
let sum = 1;
sum = sum + 1;
この場合は、変数の再代入したいですよね。
安心して下さい、「可変変数」を宣言できます。
可変変数
mutキーワードを指定して宣言すれば可変変数として宣言できます。
可変変数は再代入できます。
let mut sum = 1;
// 再代入OK
sum = sum + 1;
どうして不変性の変数機能があるの?
Rustは標準、推奨は不変変数です。
理由は「コードの可読性、品質向上」の観点で、その変数を宣言から破棄するまで値が変わらないことが保証されていることは、安全にその変数を扱えるからです。
しかし、不変変数を宣言できない言語では、「宣言から破棄するまで値が変わらない」はプログラマー任せです。間違って値を変更してしまう可能性もあります。それがRustの場合は、コンパイル時点でエラーとして扱われるので、不変が完全に保証され安全です。
変数の再宣言ができるぞ
不変変数は、安全性としてはとても良いことだと思います。
ですが、ちょっと困ったこともあります。
演算の度にあたらしい変数を宣言するのか!?
let x = 1;
// 演算毎に変数を宣言
let x_plus_one = x + 1;
let x_plus_two = x_plus_one + 1;
うーむ。変数をたくさん宣言するとになりますね。
同意の変数をたくさん宣言すると
- 変数がたくさんあって逆にコードが見づらくなる。間違った変数を使用してしまいうなどの品質にも影響がある。
- 軽微かもしれないが、変数をたくさん宣言するとメモリ(スタック領域)を消費する。
などちょっと困ったことにもなります。(個人的感想)
では、安全性を犠牲にして再代入可能な可変変数にするしかないのか!
let mut x = 1;
// 演算毎に再代入
x = x + 1;
x = x + 1;
安心して下さい。Rustは 変数の再宣言「シャドーイング」 という機能があります。
let x = 1;
// 演算毎に再宣言
let x = x + 1;
let x = x + 1;
これなら、変数の数も増えないし、不変であることが保証され安全性も犠牲になりません。
不変ってConst(定数)でもいいのでは?
不変という観点の機能としてはConst(定数)がありますが、ならConstでもいいのでは?と思う人もいるかもしれません。
const x = 1;
残念ながらconstの場合、いくつか制限があります。
これが、Const(定数)とlet(不変変数)の違いになります。
Constの場合は必ず型を指定する必要がある。
Rustは型推論という機能があるので、変数定義で型を明示的に型指定しなくもよいです。
型推論は完璧ではないので、場合によっては明示的に型指定する必要があります。
letの場合、型が推論されるので、コンパイルエラーにならない。
// x: i32
let x = 1;
// s: String
let s = String::from("A");
constの場合、型指定は必須。
// コンパイルエラーになる。
// Syntax Error: missing type for `const` or `static`
const x = 1;
Constの場合はmut指定はできない
定数なのであたり前ですよね。。。
// 可変変数宣言 OK
let mut x = 1;
// コンパイルエラーになる。
// Syntax Error: const globals cannot be mutable
const mut x: i32 = 1;
Constの場合は再定義「シャドーイング」できない
let x = 1;
// 再宣言 OK
let x = 2;
const x: i32 = 1;
// コンパイルエラーになる。
// the name `x` is defined multiple times. `x` must be defined only once in the value namespace of this
const x: i32 = 2;
実行時に評価される値は代入できない
xは1(リテラル値)が、実行時に評価され代入されます。
let x = 1;
// yに「xの値を計算した値」を代入できる。
let y = x + 1;
constに代入できる値は コンパイル時点で値が確定されている必要 があります。
xにはリテラル値を代入しているとはいえ、xの値は実行時に評価、確定されるのでconstには代入できません。
let x = 1;
// コンパイルエラーになる。
// attempt to use a non-constant value in a constant
const y: i32 = x + 1;
String::fromによる文字列インスタンスも代入できないません。
// コンパイルエラーになる。
// cannot call non-const fn `<String as From<&str>>::from` in constants
// calls in constants are limited to constant functions, tuple structs and tuple variants
const s: String = String::from("A");
xもconstなら、xの値もコンパイル時に評価、確定されているのでconstに代入可能。
const x: i32 = 1;
// xもconstなので、これはOK
const y: i32 = x + 1;