導入
こんにちは、もんすんです。
皆さんはRust、使ったことがありますか?(既視感)
私は、いつか触ろう触ろうと考えていたのですが、タイミングを見失っていました。
今回、2024年のアドベントカレンダーを進める上で、やっと機会が生まれたので、触ることにしました。
Rustについて
特徴
Rustには以下のような特徴があります。
触ってみた
早速触っていきます。
今回はVSCodeのdevcontainerを利用しています。
イメージは以下の通り。
"image": "mcr.microsoft.com/devcontainers/rust:1-1-bullseye",
プロジェクト作成
まずは、プロジェクトを作成します。
任意の場所で、以下のコマンドを実行します。
cargo new <project name>
# 今回実行: cargo new first-rust
すると、以下のようなディレクトリとファイルが生成されました。
かなりシンプルですね。
main.rs
の中身
main.rs
の中身は以下のようになっており、いかにも"Hello, World!"と、出力されそうな予感がしますね。
fn main() {
println!("Hello, world!");
}
実行
main関数が存在するディレクトリ下で、以下のコマンドを実行すると、Rustが実行されます。
cargo run
その際にコンパイル等の処理が実行され、コンソールに正しく、「Hello, World!」が表示されました。
この時、target
ディレクトリなどが作成されます。
ソースコードに変更がなければ、
./target/debug/first-rust
を実行すると同じ結果が返却されます。
変数の扱い方
Rustではletで宣言した変数はイミュータブル(不変)になります。
let hensu = 10;
hensu = hensu + 10;
println!("変数の値は{}", hensu);
上記のように記述するとコンパイルエラーとなります。
このようにイミュータブルな変数を宣言したい場合、let mut
を利用します。
- let mut hensu = 10;
+ let mut hensu = 10;
hensu = hensu + 10;
println!("変数の値は{}", hensu);
これでコンパイルエラーは解消されます。
また、let mut
を利用せずに同じ変数名の変数に対して値を代入することも可能です。
以下に例を示します。
let hensu = 10;
let hensu = hensu + 10;
println!("変数の値は{}", hensu);
この例では、1行目と2行目で同じhensu
という名前の変数に値を代入しています。
この場合、3行目のprintln!
の結果は、[変数の値は20]となります。
イミュータブルの変数なのに値の再代入ができてしまいました。
この理由としては、1行目と2行目のhensu
はそれぞれ別のメモリ領域に保存されます。
つまり、値を入れ直しているのではなく、全く別の変数として宣言し直しているだけになります。
型とデータの扱い
Rustは型システムが非常に厳密で、安全性を高めているらしいです。
型を意識することでエラーを未然に防ぐ仕組みが特徴的です。
以下にRustで扱う基本的な型についてみてみました。
基本的な型
-
ブール型
bool
はtrue
またはfalse
を表します。
論理演算に使用されます。
-
数値型: 数値型は以下の種類があります。
-
符号なし整数型:
u8
,u32
,u64
,u128
など(正の整数) -
符号付き整数型:
i8
,i32
,i64
,i128
など(正負の整数) -
ポインタサイズ整数型:
usize
,isize
(メモリ関連のサイズやインデックス) -
浮動小数点数型:
f32
,f64
(小数を含む数値)
-
符号なし整数型:
-
テキスト型
str
は文字列スライスを表します。
その長さは実行時に決まります。
-
コレクション型
配列([T; N]
)は固定長で、要素がすべて同じ型である必要があります。
スライスは配列の部分を参照するために使用され、長さは実行時に決まります。
コレクションの要素は[x]
演算子によって取得できます。x
は取り出す対象の要素のusize
型になります。
-
タプル型:
タプル((value, value, ...)
)は異なる型の値をまとめて1つの要素として扱う際に使用されます。
数値型の扱い
Rustでは数値型が異なると計算できません。
ただ、as
キーワードを使って型変換することが可能です。
let ju = 10u8;
let kyuju = 90u128;
let sum = ju as u128 + kyuju;
println!("{}", sum); // 100
このように型変換することで、異なる型の間での演算が可能になります。
制御構文
Rustでは安全性を意識した独自の制御構文が特徴です。
これらも試していきます。
if/else
Rustのif
文はシンプルで、式としても利用できます。
let value = 30;
let result = if value < 50 { "小さい" } else { "大きい" };
println!("{}", result);
// 小さい
また、上記のようにif
文の最後が;
で終わらない式の形であれば、それがそのまま返り値となります。
ループ処理
Rustには以下のループ構文があります。
for
for
ループはイテレータを利用して繰り返し処理を行います。
for i in 1..5 {
println!("{}", i);
// 1 2 3 4
}
1..5
は1から4までの範囲を表します。一方で1..=5
を使うと1から5までの範囲を反復します。
for i in 1..=5 {
println!("{}", i);
// 1 2 3 4 5
}
while
条件付きでループする場合はwhile
を使用します。
let mut n = 0;
while n < 5 {
n += 1;
}
println!("{}", n);
// 5
loop
for
やwhile
はよく目にしますが、無限ループを簡単に記述する方法がRustにはあります。
loop
文です。break
を使ってループを抜けます。
let mut count = 0;
loop {
count += 1;
if count == 5 {
break;
}
}
println!("{}", count);
// 5
match
によるパターンマッチ
Rustのmatch
はswitch
文に似ていますが、より強力らしいです。
範囲指定やパターンマッチングが可能で、どんな場合でも必ずどれかのパターンにマッチする必要があります。
let x = 7;
match x {
0 => println!("ゼロ"),
1..=5 => println!("1から5の範囲です"),
_ => println!("その他の値です"),
}
さらに、マッチした値を変数に入れて利用することも可能です。
以下の例では、x
が6から10にマッチした際、n
という変数に値を代入しています。
match x {
n @ 6..=10 => println!("範囲内: {}", n),
_ => println!("範囲外"),
}
最後に
今回はRustを触る中で、その安全性やパフォーマンスへのこだわりを少しずつ体感しました。
まだまだ、所有権システムや型安全性といった特性は、理解が不十分に感じております。
ここからRustの深みを味わうことができるのだろうと想像しています。
こういった部分に関しては、さらに深堀りしていきたいと感じました。
この記事を読んで「自分もRustを触ってみたい!」と思っていただけたら幸いです!