main
関数
Rustファイル(.rs
)を実行する際はエントリーポイントとなるmain
関数が必要です。
// ファイルの中にmainという関数が必要
fn main() {
let msg = "Hello, world!";
println!("{}", msg);
}
Cargoプロジェクトの場合、src/main.ts
にmain
関数が無い状態でcargo run
を実行すると以下のエラーメッセージが出ます。
error[E0601]: `main` function not found in crate `qiita_sample`
TypeScriptのファイルはどの関数からでも開始できます。
// 直に書いてもいいし・・・
const msg = "Hello, world!";
console.log(msg);
// 好きな関数名で定義してもいい
const hello = () => {
const msg = "Hello, world!";
console.log(msg);
}
hello();
# どちらにしてもファイル名だけ指定すれば動く
node index.ts
変数の宣言
変数の宣言はlet
で行います。この方法で宣言した変数はimmutable(不変)です。
let x = 5; // 不変の変数
println!("The value of x is: {}", x);
後から変数を変更したい場合はlet mut
のようにmut
を付与します。
let mut y = 5; // 可変の変数
println!("The initial value of y is: {}", y);
y = 10; // 値を変更
println!("The new value of y is: {}", y);
TypeScriptでいうletは可変性がある(再代入が可能)ことに注意してください。
また、TypeScriptではconst
を使って宣言しても代入以外での変更自体は可能でした(例:配列の.push
)。
const arr = [1, 2, 3];
arr.push(4); // 配列に値を追加
console.log(arr); // [1, 2, 3, 4]
Rustにおけるimmutableはそれも不可能になるので注意しましょう。Rustにおける不変性はメモリ内部の値そのものが不変であることを意味します。
Rustは静的型付け言語なので、各変数は型を持ちます。
基本的には型推論(宣言時の初期値から型を自動で割り当ててくれる)してくれますが、明示的に指定したい場合はlet <変数名>: <型名> = <初期値>;
の形を取ります。
let z: i32 = 42; // 明示的に型を指定
println!("The value of z is: {}", z);
ここの書き味はTypeScript(を含めた他の静的型付け言語)と似ていますね。
型
Rustに用意されているいくつかの型を紹介します。
-
i8
,i16
,i32
,i64
,i128
: 符号付き整数 -
u8
,u16
,u32
,u64
,u128
: 符号なし整数 -
f32
,f64
: 浮動小数点数 -
bool
: 真偽値 -
char
: 文字 -
&str
: 文字列スライス -
String
: 可変長の文字列
TypeScriptでは数値を扱う場合は単にnumber
とすることがほとんどでしたが、Rustでは性能とメモリの効率を考慮してサイズを詳細に指定できます。
char
は「文字」を表現するのであって、「文字列」を表現するのではないことに注意してください。文字列を表現したい場合は&str
かString
を使用します。
&str
文字列を表現するプリミティブ型になります。どこかしらの領域に存在する文字列へのポインタなので&
がついています(Rustでは&
をつけるとポインタになります)。厳密に言えば、これはUTF-8のスライス(後日の記事で解説)になります。そのため、参照先が変更されるとこちらも変更の影響を受けます。
fn main() {
let greeting: &str = "Hello, world!";
println!("{}", greeting);
}
String
&str
が不変なスライス型であったのに対し、String
型は文字列を格納するための可変な所有型であり、文字列の長さや内容を変更できます。String
の中身はUTF-8のVec
になります(後日の記事で解説)。String
を生成する際は通常、String::from
メソッドやto_string
メソッドを使用します。
fn main() {
let mut greeting = String::from("Hello");
greeting.push_str(", world!"); // 文字列に追加
println!("{}", greeting);
}
相互変換
&str
とString
は相互に型変換が可能です。
fn main() {
let static_str: &str = "I'm a &str";
let string_obj: String = String::from(static_str);
let slice_from_string: &str = &string_obj;
println!("{} and {}", string_obj, slice_from_string);
}
正直筆者はこのあたりの文字列の仕様を理解しきれていません。&str
が厳密な文字列への参照なのに対し、String
が柔軟な文字列型である、という認識程度です。
気をつけなければいけないのが、std::str
やstd::string
といった標準ライブラリが提供する関数が、元の文字列を操作したうえでオリジナルの参照を返すのか、新しくメモリ領域を確保して新しい参照を返すのかをきちんと把握するということです。
そう考えると、パフォーマンスはともかく無邪気にstring
を使えていたTypeScriptは考えることが少なくてサクッと書けたなぁという感想です。
所有権やライフタイムについて
Rustの変数には「所有権」や「ライフタイム」といった独自の仕組みがあります。これらは(個人的には)非常にややこしいですが、こういう機構があるからこそ、Rustの高パフォーマンス性が実現できています(そのはず)。これらについてもこのアドベントカレンダーで順を追って取り上げていきます。