Rustの特殊構文まとめ(随時更新)
はじめに
これは自分がRustで詰まった時に見直すようにまとめたものです。
修正や追加等はコメントまたはGitHubで編集リクエストをお待ちしております。
型宣言のルール
Rustの型宣言は下記のように書きます。
let x: i32 = 5;
let y: &str = "Hello";
let z: Vec<i32> = vec![1, 2, 3];
:
の左側が変数名、右側が型名です。
明らかに推測できる場合は型名を省略できます。
let x = 5; // i32
let y = "Hello"; // &str
let z = vec![1, 2, 3]; // Vec<i32>
!←こいつは何者か
RustのHelloWorldは下記のように書きます。
fn main(){
println!("HelloWorld");
}
println!
と最後に !
があります。
こいつは何者かというとマクロです。
マクロを呼び出す際は !
をつけて呼び出します。
Rustに可変引数をとる関数の宣言はできませんが、
println!
や vec!
などはマクロなので可変引数をとることができます。
またRustのマクロはCのマクロと違い、単なる置換ではありません。
下記は一見8になりそうです。
#include <stdio.h>
#define x() 2 + 2
int main(){
printf("%d\n", x() * 2); // 6
return 0;
}
ですが実行するとCは単なる置換なので、
2 + 2 * 2
となります。
下記のように評価されます。
Rustの場合はマクロも構文として評価されます。
macro_rules! x {
() => {
2 + 2
};
}
fn main(){
println!("{}", x!() * 2);// 8
}
下記のように評価されます。
return省略
Rustでは最後に評価したものが戻り値となります。
ただしこの仕様は文末に限ります。
文末以外ではreturnを省略できません。
文末とは }
の直前を指します。
また、 return
を省略した場合、 ;
をつけると戻り値は ()
になります。
fn average(numbers: &[f64]) -> f64 {
let mut sum = 0.0;
for &n in numbers {
sum += n;
}
sum / numbers.len() //こいつが戻り値
}
fn main() {
println!("平均値: {}", average(&[1.0, 2.0, 3.0]));
}
{}の意味
下記のコードは何が返ってくるでしょうか?
fn main() {
let x = {
let y = 5;
y + 1
};
println!("{}", x);
}
他の言語では見ない構文です。
JavaScriptにも似た構文はありますが。
const x = ()=>{
let y = 5;
return y + 1;
}
console.log(x());
これは関数の宣言です。
Rustの {}
は式を表しています。
{}
内が丸ごと値です。
ただし、前述しましたが、return
を省略したブロック内の最後の式が ;
で終わる場合返り値は()になります。
ローカルの使い捨て関数を宣言していると思ってください。
変数宣言の本当の意味
Rustにおける変数は少し特殊です。
Rustの変数宣言は変数束縛といい、値は所有者(変数)に束縛されます。
また変数を宣言する際は左辺に右辺の式の結果を代入しています。
この時左辺と右辺は同じ型、パターンである必要があります。
言い換えると「letは左辺と右辺が同じ型、パターンなら変数に値を束縛する」が本当の意味です。
下記のコードをみてください。
fn main(){
let (x, y) = (1,2,3); //error
}
右辺は 1,2,3
の3つの値を返しますが左辺は2つの値しか受け取らないのでエラーになります。
下記のコードもエラーになります。
fn main(){
let Some(x) = Some(1); //error
}
一見大丈夫そうですがSomeはNoneを返すかもしれないのでそのパターンを考慮できていないのでエラーになります。
if letの意味
先ほどRustの変数宣言とは「左辺と右辺が同じ型、パターンなら変数に値を束縛する」と説明しました。
下記のコードを見てください
if let
と言う構文が使われています。
fn main() {
if let Some(x) = Some(3) {
println!("{:?}",assert_eq!(x, 3));
};
}
if let
は if
と let
を結合したものではありません。
ですが完全に別ではなく2つの意味を混ぜたような構文です。
let
は何度も言いますが「左辺と右辺が同じ型、パターンなら変数に値を束縛する」と言う意味があります。
if
は「条件が真ならば」という意味があります。
つまり if let
とは let
で束縛が可能なら {}
を実行すると言う意味です。
match
文は予測される全ての型を考慮しなければいけないので冗長的になることがあります。
そんな時は if let
を使いましょう。
_の意味
_
はRustでは様々な意味を持っています。
使用しなくてもいい変数
fn main() {
let _x = 5;
}
matchのワイルドカード
fn main() {
let _x = 5;
match _x {
1 => println!("one"),
2 => println!("two"),
3 => println!("three"),
_ => println!("anything"),
}
}
パターンのワイルドカード
fn main() {
let _x = (1,2,3);
let (_, _, n) = _x;
println!("{}", n); // 3
}
型のワイルドカード
fn main() {
let _x:<Vec<_>> = vec![1,2,3];
}
数値の区切り
fn main() {
let _n = 1_000_000;
}
型推論のワイルドカード
fn main() {
let x = 0.1;
let mut y = 0;
y = x as _;
println!("{}", y); // 0.1
}
所有権
多くのプログラミング言語はメモリ管理をCの様に完全プログラマー任せにするか、
ガベージコレクション(GC)で自動的にメモリを解放するかを決めています。
Rustでは第3の概念の「所有権」で管理しています。
例えば下記コードはエラーになります。
fn main() {
let name1 = String::from("佐藤");
let name2 = name1;
println!("{}", name1); // Error
}
error: borrow of moved value: `name1`
label: move occurs because `name1` has type `String`, which does not implement the `Copy` trait
name1
から name2
に所有権が移ったので、name1
は解放されエラーになります。
しかし下記はエラーになりません。
fn main() {
let age1 = 18;
let age2 = age1;
println!("{}", age1); // 18
}
Copyトレイトを実装しているので、age1
は解放されません。
Copyトレイトなし
- String
- Vec
- など
Copyトレイトあり
- 浮動小数点数型
- char型
- bool型
- など
所有権の概念により解放忘れや無駄なメモリが貯まるのを防ぐことができます。
また誰がアクセス権限を持っているかを明示することで並列処理の際に同時アクセスなどを防ぎます。
並列化は長くなるので割愛します。
文字を数字に変換する
Rustでは文字列を数字に変換するには下記のようにします。
fn main() {
let x = "1";
let y = x.parse::<i32>().unwrap();
println!("{}", y); // 1
}
標準入力から受け取った文字列を数字に変換するには下記のようにします。
fn main() {
let mut x = String::new();
std::io::stdin().read_line(&mut x).unwrap();
let y = x.trim().parse::<i32>().unwrap();
println!("{}", y); // 1
}
tirm()
は文字列の前後の空白を削除するメソッドです。
PythonやRubyなら改行コードがあってもintに変換できるのですが、
Rustでは改行コードがあるとエラーになります。
配列のアクセス
配列のアクセスはRustではスライスとして扱えわれます。
vec!
(可変長配列)でも同様のことができます。
fn main() {
let x = [1,2,3,4,5];
println!("{}", x[0]); // 1
println!("{:?}", &x[1..3]); // [2, 3]
}
[start..end]
はstartからend-1までの要素を取得します。
数値限定の型宣言
Rustでは数値に限り型を後ろにつけることができます。
fn main() {
// 全て同じ意味
let w = 1;
let x = 1i32;
let y:i32 = 1;
let z:i32 = 1i32;
println!("{}", w); // 1
println!("{}", x); // 1
println!("{}", y); // 1
println!("{}", z); // 1
}
条件付き代入
Rustでは条件付き代入ができます。
ただし、戻り値の型が違うとエラーになります。
fn main() {
let x = 1;
let y = if x == 1 {
1
} else {
2
};
println!("{}", y); // 1
}
ループ
一般的な言語は、 for
と while
がありますが、Rustには loop
、 while
、 for
があります。
loop
loopは無限ループを作成します。breakでループを終了します。
fn main() {
let mut count = 0;
loop {
count += 1;
if count == 10 {
break;
}
}
println!("{}", count); // 10
}
while
whileは条件が真の間ループします。
fn main() {
let mut count = 0;
while count < 10 {
count += 1;
}
println!("{}", count); // 10
}
for
forは指定した範囲やコレクションを順に処理します。
fn main() {
for i in 0..10 {
println!("{}", i); // 0から9までを出力
}
}
@でパターンマッチ
Rustでは @
を使ってパターンマッチとバインドを同時に行うことができます。
パターンマッチを行いつつ、その後の処理でマッチした値を使いたい場合に便利です。
例えば下記のコードは id
が3から7の間の場合、 id_variable
にバインドします。
enum Message {
Hello { id: i32 },
}
let msg = Message::Hello { id: 5 };
match msg {
Message::Hello {
id: id_variable @ 3..=7,
} => println!("Found an id in range: {}", id_variable),
Message::Hello { id: 10..=12 } => {
println!("Found an id in another range")
}
Message::Hello { id } => println!("Found some other id: {}", id),
}
id_variable
にマッチした値が入っているため、println!マクロでその値を使うことができます。
2つ目のマッチアームでは、 id
が10から12の間の場合にマッチしますが、どの値にマッチしたかは分かりません。
3つ目のマッチアームでは、 範囲を限定していないので他のマッチアームにマッチしなかった場合にマッチします。
このように @
を使うことで、パターンマッチとバインドを同時に行うことができ、より柔軟なマッチングが可能になります。
この先工事中🚧
(最終更新日: 2024-01-22)
Discussion