Rust
この本は、プログラミング言語Rustについての初心者向けの完全な入門書です。Rustは、速度と安全性の両方を兼ね備えたシステムプログラミング言語であり、コンパイラによるメモリ管理によってセグメンテーションフォールトやダングリングポインタなどの危険なエラーを防止します。この本では、Rustの基礎から始めて、データ型、関数、所有権、トレイト、マクロなどの重要なトピックをカバーしています。また、Rustの機能を実際のコード例を交えて説明し、読者が自分自身でコードを書き始めるための準備を整えます。本書を読み進めることで、Rustで安全で高速なシステムプログラミングを行うための基礎を身につけることができます。
はじめに[編集]
Rust(ラスト)は、高性能かつ安全な並行処理を実現するために設計されたマルチパラダイム汎用プログラミング言語です[1]。 Rustの構文はC++に似ていますが、ボローチェッカーを使用して参照を検証し、メモリ安全性を保証することができます。Rustのボローチェッカーは、Rustコンパイラによって提供される静的解析ツールであり、所有権システムに基づいて、コード内の不正なボロー(借用)を検出するために使用されます。 Rustは、ガベージコレクターを使用せずにメモリ安全性を実現し、オプションでリファレンスカウントによる管理も可能です。 Rustは、システムプログラミング言語と呼ばれ、関数型プログラミングの要素を取り入れつつ、低レベルのメモリ管理メカニズムも提供しています。 そのため、高度な制御を必要とするアプリケーションの開発に最適です。Rustを使うことで、実行時にエラーを発生させることなく、安全で信頼性の高いコードを作成することができます。
Rust開発小史[編集]
Rustは、2006年にMozilla Researchの従業員であるGraydon Hoareが始めた個人プロジェクトから生まれました。Mozillaは、その後、実験的なブラウザエンジンであるServoの開発の一環として、2009年にプロジェクトのスポンサーを務めるようになりました。プロジェクトは、2010年にMozillaによって正式に発表されました。同じ年に、OCamlで書かれた最初のコンパイラから、Rustで書かれた自己ホスト型コンパイラに移行しました。新しいRustコンパイラは、2011年にセルフホスティングに成功しました。
Rustという名前は、その頑強性を表すために、さび菌にちなんで名付けられました。
Rustの型システムは、バージョン0.2、0.3、0.4の間に大きな変更を経験しました。 2012年3月にリリースされたバージョン0.2では、クラスが初めて導入されました。その4ヶ月後のバージョン0.3では、デストラクタとインターフェイスを介した多様性が追加されました。そして、2012年10月にリリースされたバージョン0.4では、継承の手段としてトレイトが追加されました。インターフェイスはトレイトと統一され、別の機能として削除され、クラスは実装と構造化タイプの組み合わせに置き換えられました。バージョン0.4より前のRustでは、契約による型ステート解析もサポートしていましたが、リリース0.4で削除されました。ただし、同じ機能はRustの型システムを利用して実現できます。
2014年1月、Dr. Dobb's Journalの編集長であるAndrew Binstockは、RustがC++に対抗する可能性がある言語D、Go、Nim(当時はNimrod)と共に競合する可能性があると述べました。Binstockによると、Rustは「非常に優雅な言語」と広く見なされていましたが、バージョンの変更が度重なっていたため、採用が遅れたとのことです。最初の安定版であるRust 1.0は、2015年5月15日に発表されました。
2020年8月、COVID-19パンデミックの影響で、Mozillaは世界中の1,000人の従業員のうち250人を解雇しました。Servoチームは解散され、Rustのアクティブな貢献者であるチームメンバーの一部が影響を受けました。この出来事はRustの将来についての懸念を引き起こしました。翌週、Rustコアチームは解雇の深刻な影響を認め、Rust財団の計画が進行中であることを発表しました。財団の最初の目標は、すべての商標とドメイン名の所有権を引き継ぎ、それらの費用に財政的責任を負うことです。
2021年2月8日、AWS、Huawei、Google、Microsoft、およびMozillaの5つの創設企業によって、Rust Foundationの設立が発表されました。2021年4月6日に公開されたGoogleのブログ記事では、C/C++の代替として、Android Open Source Project内でRustのサポートが発表されました。
2021年11月22日、コミュニティの基準と行動規範の執行を担当するModerationチームは、「Core Teamが自分たち以外の誰にも説明義務を負わない」という理由で辞任を表明しました。2022年5月、Rustコアチーム、他のリーダー、およびRust Foundationの一部のメンバーは、この事件に対応してガバナンス改革を実施しました。
セルフホスティング |
セルフホスティングとは、あるソフトウェアが自分自身をコンパイルできることを指します。つまり、そのソフトウェアをコンパイルするために、あらかじめ別のコンパイラーが必要ではなく、そのソフトウェア自体が自分自身をコンパイルできることです。
コンパイラーにおいてセルフホスティングは、最初にコンパイルされたコンパイラーを使用して、同じ言語で書かれたソースコードをコンパイルすることで実現されます。そして、その新しいコンパイラーを使用して、さらに新しいソースコードをコンパイルし、それを繰り返すことで、より新しいバージョンのコンパイラーを作成することができます。 セルフホスティングは、ソフトウェアの信頼性や安定性を高めるために使用されることがあります。また、コンパイラーを自分自身でコンパイルすることは、その言語やコンパイラーの開発者がその言語やコンパイラーの知識と理解を深めるのにも役立ちます。 |
Hello World[編集]
Rustで Hello World を書くと、
- hello.rs
fn main() { println!("Hello, world!"); }
- 実行結果
Hello, world!
hello.rsは、Playgroundに作った、このプログラムへのリンクになっています。
コメント[編集]
Rustのコメントには、C言語/C++と同じく一行コメントと範囲コメントがあります。
- 一行コメント
//
から行末までがコメントと見なされます。- 範囲コメント
/*
から*/
までがコメントと見なされます。- ネストは許されません。
- コメントの例
/* * プログラムのエントリーポイントは、main 関数です。 * 関数定義は fn から始まります。 */ fn main() { println!("Hello, world!"); // println! は関数ではなくマクロで、マクロは識別子の末尾に ! が付きます。 }
変数と型[編集]
Rustは、静的な型システムを採用しています。
- variables-types.rs
fn main() { let x = 999; print!("{}({}), ", x, type_of(x)); let x = -123; print!("{}({}), ", x, type_of(x)); let x = 999u32; print!("{}({}), ", x, type_of(x)); let x = -123i32; print!("{}({}), ", x, type_of(x)); let x = 999u64; print!("{}({}), ", x, type_of(x)); let x = -123i64; print!("{}({}), ", x, type_of(x)); let x = 1.0; print!("{}({}), ", x, type_of(x)); let x = 3.14f32; print!("{}({}), ", x, type_of(x)); println!(); let x = "abc"; print!("{}({}), ", x, type_of(x)); let x = true; print!("{:?}({}), ", x, type_of(x)); let x = [1, 2, 3]; print!("{:?}({}), ", x, type_of(x)); let x = (1, 2, 3); print!("{:?}({}), ", x, type_of(x)); let x = &vec![10, 20, 30]; print!("{:?}({}), ", x, type_of(x)); } fn type_of<T>(_: T) -> &'static str { std::any::type_name::<T>() }
- 実行結果
999(i32), -123(i32), 999(u32), -123(i32), 999(u64), -123(i64), 1(f64), 3.14(f32), abc(&str), true(bool), [1, 2, 3]([i32; 3]), (1, 2, 3)((i32, i32, i32)), [10, 20, 30](&alloc::vec::Vec<i32>),
なお、{
}
はプレースホルダーといい、文字列中のその位置に書式化して展開します。
変数とミュータブル・イミュータブル[編集]
Rustでは、変数を宣言するにはキーワード letを使います。 ディフォルトではイミュータブル(Immutable;宣言後には代入不能)な変数が宣言されます。 ミュータブル(Mutable;宣言後に代入可能)な変数を宣言するには、追加のキーワード mut を使います。
- hello-variables.rs
fn main() { let hello : &str = "Hello, world!"; println!("{}", hello); }
- 実行結果
Hello, world!
- 2行目の
let hello : &str = "Hello, world!";
が変数宣言です[2]。- &str(文字列のスライスのリファレンス)を型とする変数 hello を宣言し、"Hello, world!"で初期化しています。
- Rustには強力な型推論があり多くの場合不要ですが、
let 変数名 : 型名
の書式で型を伴い変数宣言することも出来ます。
mut をつけない場合には変数に「代入不能」と聞くと、C言語などを知っている人は「定数」を思い浮かべるかもしれませが、 Rustにおいて「定数」は, const 宣言された定数や, static 宣言されかつ mut で修飾されていない変数が相当します。
型推論[編集]
Rust では、変数宣言が初期値を伴っていた場合、変数の型を省略することができ、初期値の型が変数の型になります。
- hello-type-inference.rs
fn main() { let hello = "Hello, world!"; println!("{}", hello); }
- 実行結果
- 上に同じ
イミュータブル[編集]
Rust では、値が一度変数に let で束縛されると変更できません。これをイミュータブルと言います。
- hello-immutable.rs
fn main() { let hello : &str = "Hello, world!"; println!("{}", hello); hello = "Hello, rust!"; println!("{}", hello); }
- コンパイル結果
error[E0384]: cannot assign twice to immutable variable `hello` --> src/main.rs:4:5 | 2 | let hello : &str = "Hello, world!"; | ----- | | | first assignment to `hello` | help: consider making this binding mutable: `mut hello` 3 | println!("{}", hello); 4 | hello = "Hello, rust!"; | ^^^^^^^^^^^^^^^^^^^^^^ cannot assign twice to immutable variable For more information about this error, try `rustc --explain E0384`. error: could not compile `playground` due to previous error
- イミュータブルな変数には、代入できないというコンパイルエラーです。
ミュータブル[編集]
代入可能、すなわちミュータブルにするためには、変数宣言にあたり let に続けて mut をつけます。
- hello-mutable.rs
fn main() { let mut hello : &str = "Hello, world!"; println!("{}", hello); hello = "Hello, rust!"; println!("{}", hello); }
- 実行結果
Hello, world! Hello, rust!
同じ変数名での宣言[編集]
同一スコープで同じ変数名での宣言は可能です。 同じ型である必要はありません。ミュータブルであるかイミュータブルであるかも問いません。 同じ変数名での宣言によって、それまで変数に束縛されていた値への参照がなくなります。
- 同じ変数名での宣言
fn main() { let hello : &str = "Hello, world!"; println!("{}", hello); let hello = 154649; println!("{}", hello); }
- 実行結果
Hello, world! 154649
定数[編集]
Rustには2種類の定数があり、どちらもグローバルスコープを含む任意のスコープで宣言することができます。また、どちらも明示的な型を持っている必要があります。
- const: 不変の値
- static: 静的寿命を持つミュータブルな値 静的寿命は推論されるので、指定する必要はありません。
- 2種類の定数
const HELLO : &str = "Hello, world!"; static LANGUAGE: &str = "Rust"; fn main() { println!("{}", HELLO); println!("{}", LANGUAGE); }
- 実行結果
Hello, world! Rust
コードを書き換えてconst宣言や(ミュータブルな)static宣言された変数に代入をしようとすると、エラーになります。
パターン[編集]
- pattern.rs
fn main() { let (mut x, mut y) = (5, 29); println!("x={} Y={}", x, y); let (p, q) = (x, y); x = q; y = p; println!("x={} Y={}", x, y); }
- 実行結果
x=5 Y=29 x=29 Y=5
データー型[編集]
Restには豊富なデーター型(Data Types)があり、それらを組み合わせて新しい型を作ることができます[3]。
スカラー型(Scalar Types)[編集]
スカラー型は単一の値を表します。Rustには、整数、浮動小数点数、論理値、文字という4つの主要なスカラ型があります。
整数型(Integer Types)[編集]
Rustの整数型は、符号の有無とビット幅から12種類のバリエーションがあります。
- i8
- 符号付き8ビット整数
- u8
- 符号なし8ビット整数
- i16
- 符号付き16ビット整数
- u16
- 符号なし16ビット整数
- i32
- 符号付き32ビット整数
- u32
- 符号なし32ビット整数
- i64
- 符号付き64ビット整数
- u64
- 符号なし64ビット整数
- i128
- 符号付き128ビット整数
- u128
- 符号なし128ビット整数
- isize
- 符号付きでアーキテクチャーに自然な整数
- usize
- 符号なしでアーキテクチャーに自然な整数
- isizeとusizeのビット幅はプロセッサーのアーキテクチャーによって定義され、32ビットプロセッサーならば32、64ビットプロセッサーならば64です。
整数リテラル(Integer literals)[編集]
リテラル(Literals)とは、プログラミングのソースコードで使用される、数値や文字列などのデーターを直接表現したものです。
様々な整数リテラル 基数 表現 10 19_800 16 0xbadbeef 8 0o777 2 0b101_111_011 バイト(u8のみ) b'Q'
- 例
fn main() { println!("{:?}", 19_800); println!("{:x}", 0xbadbeef); println!("{:o}", 0o777); println!("{:b}", 0b101_111_011); println!("{}", b'Q'); }
- 実行結果
19800 badbeef 777 101111011 81
- 数値リテラルには、123u8 の様に型名をタイプサーフィックス(type suffix)として補うことで、ビット幅を明記できます(オプショナル)。
- 指定されない場合は(バイト以外は)i32が仮定されます(isizeではありません)。
- 数値リテラルには、読みやすさのため 9_281_636 のように、アンダースコア _ を補うことができます(オプショナル)。
なお、{:x}
の{x}
部分はプレースホルダーのファーマッティング・トレイツです。「x」なら16進数、oなら8進数、bなら2進数で出力します。
浮動小数点数型(Floating-Point Types)[編集]
Rustの浮動小数点数型には、f64 と f32 があり、サイズを明示しないと f64 になります。 浮動小数点数は、IEEE-754規格に従います。
論理値型(The Boolean Type)[編集]
Rustの論理値型の型名は bool、真の値は true 偽の値は false です。
文字型(The Character Type)[編集]
Rustの文字型の型名は char です。 char は Unicode のサロゲートペアであっても保持できます。
文字列型(The String Type)[編集]
- string.rs
fn main() { println!("{:?}", "hello"); // エスケープされた文字列 println!("{:?}", r#"hello"#); // エスケープされないraw文字列 println!("{:?}", b"hello"); // エスケープされたバイト文字列 println!("{:?}", br#"hello"#); // エスケープされないrawバイト文字列 }
- 実行結果
"hello" "hello" [104, 101, 108, 108, 111] [104, 101, 108, 108, 111]
複合型(Compound Types)[編集]
複合型(Compound Types)は、複数の値を1つの型にまとめることができます。 Rustにはタプルとアレイという2つのプリミティブな複合型があります。
タプル型(The Tuple Type)[編集]
タプル(The Tuple)は、さまざまな型の値を1つの複合型にまとめる一般的な方法です。 タプルの長さは固定されており、一度宣言すると大きくしたり小さくしたりすることはできません。
配列型(The Array Type)[編集]
複数の値の集まりを持つもう一つの方法として、配列(The Array)があります。 タプルとは異なり、配列の各要素は同じ型でなければなりません。 Rustの配列は、タプルと同じく長さが固定されています。
プレースホルダー[編集]
println! マクロなどの文字表示マクロでは、文字列中の {
}
の位置に指定された書式で展開します[4]。
C言語の標準関数 printf() と機能は似ていますが書式は大きく異なり、Pythonのf文字列との共通点が多いです。
フォーマッティング・トレイツ(Formatting traits)[編集]
からのプレースホルダー{}
を指定すればその型の自然なフォーマット(fmt::Display; 100 ならば "100")で文字列化されます。
しかし、基数を変えた16進数や8進数や2進数などでフォーマットをしたいときはフォーマッティング・トレイツ(Formatting traits)を使います[5]。
この機能は、 trait を使って実装されています。
- Formatting traits
fn main() { println!("{:?}", 100); println!("{:x}", 100); println!("{:o}", 100); println!("{:b}", 100); println!("{}", 100); }
- 実行結果
100 64 144 1100100 100
ムーブセマンティクスとコピーセマンティクス[編集]
識別子[編集]
スコープ[編集]
制御構造[編集]
Rust では、if
やfor
などの制御構造も式です。
分岐[編集]
Rust は、if と match の2つの分岐構文を持ちます。
if[編集]
ifは、条件式に基づき分岐し、分岐先の式を評価します。
ifの値は、分岐先の式の値です。
elseを省略し条件式が偽であった場合のifの値は ()
です。
- 構文
if-expr := if 条件式 '{' 式1 '}' [ else '{' 式2 ] '}'
- 条件式に整数を使うと
fn main() { let i = 0; if i { println!("zero"); } }
- コンパイルエラー
error[E0308]: mismatched types --> src/main.rs:4:8 | 4 | if i { | ^ expected `bool`, found integer For more information about this error, try `rustc --explain E0308`.
- Rustでは、ifに限らず、条件式は、bool 型でなければいけません。
- if.rs
fn main() { let i = 0; if i == 0 { println!("零"); } else { println!("非零"); } let s = if i == 0 { "ゼロ" } else { "非ゼロ" }; println!("{}", s); }
- 実行結果
零 ゼロ
- 気をつけたいのは、式の値が参照されるifでは、それぞれの式(ここでは
"ゼロ"
と"非ゼロ"
)にセミコロン;
を付けてはいけない点です。- もし セミコロン
;
をつけると、ブロックの値は、Unit 型()
になります。 - ifの、分岐先の2つの式の型は同じでなければいけません。
- else節が省略された場合は、Unit 型を返さなければいけません。
- 式の最後に、セミコロン
};
が必要ということです[6]。
- 式の最後に、セミコロン
- もし セミコロン
- また、
let s = if i == 0 {
の文末の};
のセミコロンも忘れがちです。 - Rust では if に限らず、コードブロックは式の一種で値を返します[7]。その場合、コードブロックの最後の式がコードブロックの値となりセミコロン
;
は不要で、もし、;
をつけると;
の次の式(=空文)の値()
がコードブロックの値になります。
この特徴は、関数型プログラミングを意識したものですが、同時に後に説明する所有権の移譲で重要な役割を果たします。
Some()[編集]
Rustでは、C言語のNULLに相当する値は None です。 通常の変数は None の代入は受付けませんが、Some() を使うと None を取り得る変数が宣言できます[8]。
- some.rs
fn main() { let mut x = Some(0); println!("{:?}({})", x, type_of(x)); x = None; println!("{:?}({})", x, type_of(x)); } fn type_of<T>(_: T) -> &'static str { std::any::type_name::<T>() }
- 実行結果
Some(0)(core::option::Option<i32>) None(core::option::Option<i32>)
Some() 及び None は、標準ライブラリ std::option で定義されていますが、初期化済みなので Use することなく、接頭辞Option::なしで使えます。
if let[編集]
Rustには、if let
という特別な制御構造があります。if let
は、match
式を簡略化するために使用されます。if let
式は、単一のパターンを使用して、変数が指定された値にマッチした場合にのみ式を実行します。if let
を使用すると、冗長なマッチングのコードを削減できます。
以下は、if let
を使用した例です。
- if-let.rs
fn main() { let mut v = vec![2, 3, 5]; if let Some(x) = v.pop() { println!("x = {}", x) } else { println!("done!") } if let Some(x) = v.pop() { println!("x = {}", x) } else { println!("done!") } if let Some(x) = v.pop() { println!("x = {}", x) } else { println!("done!") } if let Some(x) = v.pop() { println!("x = {}", x) } else { println!("done!") } if let Some(x) = v.pop() { println!("x = {}", x) } else { println!("done!") } }
- 実行結果
x = 5 x = 3 x = 2 done! done!
.pop()
メソッドは、ベクターから最後の要素を取り出し、それを返します。ただし、ベクターが空である場合はNone
を返します。- 最初の行では、
vec![2, 3, 5]
という3つの要素を持つベクターを作成しています。 - その後、5回の
if let
式を使用して、ベクターから要素を取り出し、それを表示するか、ベクターが空の場合にはdone!
と表示します。 - 各
if let
式では、変数x
を定義しています。v.pop()
の返り値がSome
であれば、ベクターの最後の要素が変数x
に束縛され、println!
マクロを使用してx
を表示します。- そうでない場合、すなわち、ベクターが空である場合は、
else
節が実行されて、done!
が表示されます。
match[編集]
Rustにおけるmatch
は、値が複数のパターンのいずれかにマッチする場合に、それぞれに対応する処理を実行する制御構文です。構文は以下の通りです。
- 構文
match_expr ::= 'match' expr '{' match_arm* '}' match_arm ::= pattern '=>' expr (',' | ';')? pattern ::= '_' | literal | identifier | ref_pattern | mut_pattern | struct_pattern | tuple_pattern | range_pattern | slice_pattern | box_pattern | '(' pattern '|' pattern ')' | '&' pattern | '@' pattern | refutable_pattern ref_pattern ::= '&' (mut)? identifier mut_pattern ::= 'mut'? identifier struct_pattern ::= path '{' field_pattern (',' field_pattern)* ','? '}' field_pattern ::= identifier ':' pattern tuple_pattern ::= '(' (pattern (',' pattern)*)? ')' range_pattern ::= literal '..' literal slice_pattern ::= '[' (pattern (',' pattern)* ','?)? ']' box_pattern ::= 'box' pattern refutable_pattern ::= pattern ('@' identifier)?
- ここで、exprは任意の式を表し、literalはリテラルを表します。また、pathはパスを表し、identifierは識別子を表します。mutはミュータビリティを表し、','と';'はそれぞれ,と;を表します。|はOR演算子を表し、'&'は参照を表し、'@'はパターンの変数名を表します。また、refutable_patternはマッチングが失敗する可能性のあるパターンを表します。
パターンは様々なものを指定することができますが、ここではいくつか代表的なものを説明します。
- 値を直接指定する:
3
のように、具体的な値を指定します。 - ワイルドカードを使う:
_
のように、どの値にもマッチするパターンです。 - 変数をバインドする:
x
のように、パターンに変数を書くことで、マッチした値を変数に束縛することができます。 - ガード条件を指定する:
x if x > 0
のように、マッチした値に対して条件を指定することができます。
以下に、具体的な例を示します。
- コード例
let x = 42; match x { 0 => println!("zero"), 1 => println!("one"), 2..=10 => println!("two to ten"), _ => println!("something else"), } let mut v = vec![1, 2, 3]; match v.pop() { Some(x) => println!("popped value: {}", x), None => println!("no value"), } let x = Some(42); match x { Some(y) if y > 0 => println!("positive value: {}", y), Some(_) => println!("zero or negative value"), None => println!("no value"), }
- 実行結果
- 最初の例は、変数
x
に対してmatch
式を使用して、x
が異なる値を持つ場合に異なる処理を実行する方法を示しています。x
が0
の場合、println!("zero")
が実行されます。同様に、x
が1
の場合、println!("one")
が実行され、x
が2
から10
までの範囲にある場合、println!("two to ten")
が実行されます。それ以外の場合は、println!("something else")
が実行されます。この例では、_
はワイルドカードパターンとして使用され、どの値にも一致するようになっています。 - 2番目の例は、
match
式を使用して、vec
の最後の値を取得する方法を示しています。v.pop()
がSome(x)
を返す場合、println!("popped value: {}", x)
が実行され、None
を返す場合は、println!("no value")
が実行されます。 - 3番目の例では、
match
式を使用して、Option<i32>
型の値に対してパターンマッチングを行う方法を示しています。x
がSome(y)
を返し、y
が0
より大きい場合、println!("positive value: {}", y)
が実行されます。y
が0
以下の場合、println!("zero or negative value")
が実行されます。また、x
がNone
の場合、println!("no value")
が実行されます。この例では、if
を使用して、Some(y)
の条件を更に厳密化しています。この機能をガードと呼びます。
C言語のswitch
文と異なり、Rustのmatch
式は、すべてのパターンをカバーしていない場合は、コンパイル時に警告を出します。これにより、プログラムにエラーが含まれている可能性がある場合に、それを事前に検出することができます。
反復[編集]
Rustには、以下のような反復構文があります。
それぞれの構文について、詳しく解説していきます。
for[編集]
Rust の for は、指定された範囲内の値を反復処理するために使用されます。通常、配列、ベクトル、範囲、またはイテレータなどの反復可能オブジェクトに対して使用されます。
- 構文
for_expression = "for" loop_variable "in" expression "{" statement* "}"; loop_variable = pattern; expression = (expression_binop | expression_unop | expression) ; pattern = identifier | "_" | literal ;
for_expression
はforループを表し、loop_variable
は反復処理のために使用される変数、expression
は反復処理の対象となるデータのソース、statement
はループ内で実行される文を表します。pattern
は、ループ変数の型と一致するパターンを表します。identifier
は、識別子の名前を表します。literal
は、文字列、数値、真偽値などのリテラル値を表します。- forループは、
loop_variable
によって定義された変数にexpression
で指定されたデータソースの値を順番に割り当て、それぞれの値に対してstatement
を実行します。 identifier
は識別子を、literal
はリテラルをしめします。
Range(末尾を含まない)[編集]
- for-in-range.rs
fn main() { for i in 1..5 { println!("i = {}", i); } }
- 実行結果
i = 1 i = 2 i = 3 i = 4
- このRustのコードは、
for
ループを使用して1から4までの整数を反復処理して、それぞれの値を出力するものです。..
演算子は、範囲演算子として知られており、範囲の開始と終了を指定します。この場合、1から5までの範囲を表していますが、5は含まれないため、ループは1から4までの値を反復処理します。 for
ループの本体では、println!
マクロを使用して、現在のi
の値を表示しています。{}
はプレースホルダーであり、その場所に変数の値が挿入されます。この例では、{}
の中にi
を指定して、ループの反復ごとに変化するi
の値を表示しています。
Range(末尾を含む)[編集]
- for-in-range2.rs
fn main() { for i in 1..=5 { println!("i = {}", i); } }
- 実行結果
i = 1 i = 2 i = 3 i = 4 i = 5
- このRustのコードは、
for
ループを使用して1から5までの整数を反復処理して、それぞれの値を出力するものです。..=
演算子は、範囲演算子として知られており、範囲の開始と終了を指定します。この場合、1から5までの範囲を表していますが、5が含まれるため、ループは1から5までの値を反復処理します。 for
ループの本体では、println!
マクロを使用して、現在のi
の値を表示しています。{}
はプレースホルダーであり、その場所に変数の値が挿入されます。この例では、{}
の中にi
を指定して、ループの反復ごとに変化するi
の値を表示しています。
iter()[編集]
Rustでは、iter()
メソッドを使用して、ベクトルや配列、ハッシュマップなどの要素を反復処理できます。
iter()
メソッドは、コレクションの各要素を順番に取り出すイテレータを作成します。イテレータは、for
ループや他の反復処理メソッドと組み合わせて使用することができます。
- iter.rs
fn main() { let v = vec![1, 3, 5, 7, 11]; for x in v.iter() { println!("x = {}", x) } }
- 実行結果
x = 1 x = 3 x = 5 x = 7 x = 11
- このコードは、
iter()
メソッドを使用して、ベクトルv
を反復処理する方法を示しています。 - 最初の行で、整数のベクトル
v
を定義しています。 - 次に、
for
ループを使用して、v.iter()
のイテレータを取り出します。iter()
メソッドは、ベクトルの要素を順番に取り出すイテレータを返します。 println!
マクロの中で、{}
にx
を表示しています。{}
は、表示する変数を示すプレースホルダーです。
rev()[編集]
iter()
メソッドに加えて、rev()
メソッドを使用すると、要素を逆順で取り出すこともできます。
- iter-rev.rs
fn main() { let v = vec![1, 3, 5, 7, 11]; for x in v.iter().rev() { println!("x = {}", x) } }
- 実行結果
x = 11 x = 7 x = 5 x = 3 x = 1
- このRustのコードは、
for
ループを使用して、ベクトルv
の要素を反復処理し、各要素の値を出力するものです。ただし、v
の要素を逆順に処理します。 v.iter().rev()
は、v
のイテレータを取得し、そのイテレータを逆順にするためのメソッドです。これにより、ベクトルの最後の要素から始まり、最初の要素まで逆順に反復処理します。for
ループの本体では、println!
マクロを使用して、現在のx
の値を表示しています。{}
はプレースホルダーであり、その場所に変数の値が挿入されます。この例では、{}
の中にx
を指定して、ループの反復ごとに変化するx
の値を表示しています。
enumerate()[編集]
enumerate()
メソッドを使用すると、要素のインデックスと値を同時に取り出すこともできます。
- コード例
let v = vec![1, 2, 3]; for (i, val) in v.iter().enumerate() { println!("{}: {}", i, val); }
- このコードでは、
v.iter().enumerate()
メソッドを使用して、v
のイテレータを作成し、各要素のインデックスと値を同時に反復処理しています。for
ループの本体では、変数i
を使ってインデックス、val
を使って値を表示しています。
zip()[編集]
zip()
メソッドを使用すると、複数のイテレータを同時に取り出すことができます。
- コード例
let v1 = vec![1, 2, 3]; let v2 = vec!["one", "two", "three"]; for (i, val) in v1.iter().zip(v2.iter()) { println!("{}: {}", i, val); }
- このコードは、
zip()
メソッドを使用して、2つのベクトルを同時に反復処理する方法を示しています。 - 最初の行で、整数のベクトル
v1
と文字列のベクトルv2
を定義しています。 - 次に、
for
ループを使用して、v1.iter()
とv2.iter()
のイテレータを同時に取り出します。このとき、(i, val)
というタプルの形式で、それぞれのイテレータから次の要素を取り出します。 println!
マクロの中で、{}
にi
とval
をそれぞれ表示しています。{}
の中にある:
は区切り文字で、i
とval
を区別するために使用されています。
while[編集]
Rustにおいて、while
は指定した条件式がtrue
である限り、ブロック内のコードを繰返し実行する制御構文です。
- while.rs
fn main() { let mut i = 0; while i < 5 { println!("i = {}", i); i += 1 }; }
- 実行結果
i = 0 i = 1 i = 2 i = 3 i = 4
- このプログラムは、0から4までの数字を順番に表示するプログラムです。whileループを使用して、条件式
i < 5
がtrue
である間、ループを継続します。ループの各イテレーションでは、println!
マクロを使用して、変数i
の現在の値を表示します。i
の値は、ループ本文の最後でi += 1
によって1ずつ増加します。条件式i < 5
がfalse
になったとき、ループが終了します。最終的に、0から4までの数字が順番に表示されます。
while let[編集]
Rustのwhile let
は、ループ処理の一種で、パターンマッチングを行い、パターンにマッチする値をループ内で取り出しながらループを繰り返します。
while let
は、match
構文の糖衣構文で、一つの値を取り出してパターンマッチングを行い、パターンにマッチする場合は値を取り出し、マッチしない場合はループを終了します。
- while-let.rs
fn main() { let mut n = Some(0); while let Some(i) = n { n = if i > 5 { None } else { println!("i = {}", i); Some(i + 1) } } }
- 実行結果
i = 0 i = 1 i = 2 i = 3 i = 4 i = 5
- このコードは、
n
というOption<i32>
型の変数を定義し、初期値をSome(0)
として設定します。そして、while
ループを使ってn
の値を取り出しながら、それがNone
でなければループを続けます。 while let
の条件式では、n
がSome(i)
とパターンマッチされ、i
に値がバインディングされます。このパターンマッチにより、n
がSome(i)
でなければループは終了します。- ループ本体では、
i
が5
を超える場合は、n
をNone
に更新してループを終了します。そうでなければ、i
を表示して、Some(i + 1)
をn
に代入してループを継続します。 - このプログラムの出力は、0 から 5 までの数値が順番に表示されます。最後に
None
が表示されます。
while let とベクトル[編集]
Rustのwhile let
は、反復可能な値に対して、パターンマッチングを行いながら反復処理を行うことも出来ます。ベクトルに対してwhile letを使うと、ベクトルの末尾から要素を取り出しながら反復処理を行うことができます。
- while-let-again.rs
fn main() { let mut v = vec![1, 3, 5, 7, 11]; while let Some(x) = v.pop() { println!("x = {}", x) } }
- 実行結果
x = 11 x = 7 x = 5 x = 3 x = 1
- このコードは、可変長配列
v
に値を追加し、whileループでその配列から値を取り出し、それを表示するものです。 - まず、可変長配列
v
にvec![1, 3, 5, 7, 11]
という値を設定しています。その後、while let
式を使って、v.pop()
の戻り値がSome(x)
である限り、ループが継続されます。v.pop()
は、v
の最後の要素を取り出し、その要素があればSome
で包んで返し、なければNone
を返します。 while let
式では、取り出した値がSome(x)
である場合に、println!("x = {}", x)
を実行してx
の値を表示します。- つまり、このコードは
v
の末尾から要素を取り出しながら、取り出した要素の値を表示するという処理を続け、最後に配列が空になったらループを終了します。
Rustにdo-whileはありません |
Rustにdo-whileはありません。while の条件式に do のブロックを詰め込むことで同じことが実現できます。
|
loop[編集]
loop
は、明示的な条件式の指定がない限り、無限に繰り返されます。
- loop-and-break.rs
fn main() { let mut i = 0; let result = loop { if i > 3 { break 100; } println!("i = {}", i); i += 1; }; println!("result = {}", result); }
- 実行結果
i = 0 i = 1 i = 2 i = 3 result = 100
- 最初に、変数
i
を0に初期化します。そして、loop
構文で、i
が3より大きくなるまで繰り返します。 - 各反復中に、
if
文が使用されています。if
の条件式は、i
が3より大きくなった場合にはbreak 100;
が実行され、loop
から脱出します。そうでない場合は、println!()
関数を使用して、i
の値を出力し、i
を1増やします。 - 最後に、
loop
から脱出した後にresult
変数に格納される値は、break
文の引数で指定された100
です。そして、println!()
関数を使用して、result
の値を出力します。 - つまり、このコードは、
i
を0から3まで順に増やしながら、各値を出力し、i
が3より大きくなったら100を返し、その後にresult
の値を出力するという処理を行っています。
continue[編集]
continue
を実行すると、ループのブロック内の残りの処理がスキップされ、反復処理が続行されます。
- continue.rs
fn main() { let mut i = 0; let result = loop { if i > 10 { break 100; } if i % 2 == 0 { i += 1; continue; } println!("i = {}", i); i += 1; }; println!("result = {}", result); }
- 実行結果
i = 1 i = 3 i = 5 i = 7 i = 9 result = 100
- このコードは、0から10までの奇数を出力するプログラムです。
- まず、
i
変数を定義して0で初期化し、result
変数を宣言しています。次に、loop
キーワードでループを開始します。 - ループ本体では、
if
文を使ってi
が10より大きくなったら、break
キーワードを使ってループを抜けます。これにより、11以上の奇数が出力されることはありません。 - 次に、
i
が偶数である場合は、continue
キーワードを使ってループの先頭に戻ります。これにより、偶数はスキップされます。 - 最後に、
println!
マクロを使って現在のi
の値を出力し、i
を1増やします。 - ループが終了したら、最後に
result
の値を出力します。break
キーワードが値を返すため、このループはresult
に100を設定します。
Rustのプログラミングでつまづきやすいところ |
Rustはセーフティ、パフォーマンス、そしてエクスプレッションの柔軟性を組み合わせた高度なプログラミング言語です。初めてRustを使う場合、以下のようなトピックでつまづくことがよくあります。
本書では、これらのトピックについて順次取り上げてゆきます。 |
所有と借用[編集]
Rustは、安全で高速なコードを書くことができるように設計された、システムプログラミング言語です。Rustの所有と借用という概念は、言語の中心的な機能であり、これらを理解することは、Rustで効果的にコードを書くために必要不可欠なスキルです。
所有(Ownership)とは、Rustがメモリを管理する方法です。Rustの所有は、特定の値を所有する変数が存在する限り、その値が有効であることを保証します。例えば、次のようなコードがあります。
- 例
let x = 5; let y = x;
- この場合、変数xが値5を所有しており、変数yに値5がコピーされます。ただし、コピーされた値は新しい場所に配置されるため、変数xとyが完全に独立していることに注意してください。
しかし、Rustでは、所有権が移動することがあります。つまり、ある変数が別の変数に割り当てられると、所有権が移動し、元の変数は無効になります。例えば、次のようなコードを考えてみましょう。
- 例
let s1 = String::from("hello"); let s2 = s1;
- この場合、s1が所有する文字列"hello"がs2に移動し、s1は無効になります。この動作は、Rustがデータ競合を回避するために採用している所有権モデルに基づいています。
所有権の移動を避けるために、Rustでは、借用(Borrowing)という概念が導入されています。借用は、所有者がその値を使う他のコードに、その値を一時的に貸し出すことを意味します。借用は、参照(Reference)を使用して行われます。
参照は、値の所有権を持たず、所有者に依存する値へのアクセスを提供するものです。値を参照する場合、次のように書きます。
- 例
let s1 = String::from("hello"); let len = calculate_length(&s1);
- ここで、calculate_length関数は、文字列への参照を取得して、その長さを計算します。この場合、s1の所有権は移動しないため、s1は引き続き有効なままです。
可変参照と不変参照[編集]
参照には、可変参照(Mutable Reference)と不変参照(Immutable Reference)の2種類があります。
可変参照は、変数を変更するために使用されます。可変参照は、「&mut」キーワードを使用して宣言されます。以下は、可変参照を使用して変数の値を変更する例です。
- 可変参照の例
fn main() { let mut my_var = 5; let my_ref = &mut my_var; *my_ref = 10; println!("my_var is now {}", my_var); }
- このコードでは、変数「my_var」を可変にするために「mut」キーワードを使用して宣言し、その後、可変参照「my_ref」を宣言して、変数「my_var」の所有権を借ります。「*my_ref = 10;」という行は、「my_ref」が指し示す変数「my_var」の値を10に変更します。「println!」マクロは、「my_var」の値が変更されていることを確認するために使用されています。
不変参照は、変数を読み取るだけの場合に使用されます。不変参照は、「&」を使用して宣言されます。以下は、不変参照を使用して変数の値を読み取る例です。
- 不変参照の例
fn main() { let my_var = 5; let my_ref = &my_var; println!("my_var is {}", *my_ref); }
- このコードでは、「my_var」を不変に宣言し、「my_ref」を不変参照で宣言しています。「println!」マクロは、「my_ref」が指し示す変数「my_var」の値を出力します。「*my_ref」という式は、参照をデリファレンスするために使用され、その結果、「my_var」の値が出力されます。
要約すると、Rustの可変参照と不変参照は、借用を使用してメモリの安全性を確保するための機能です。可変参照は変数を変更するために使用され、不変参照は変数を読み取るために使用されます。可変参照は「&mut」で宣言され、不変参照は「&」で宣言されます。
マクロ[編集]
Rustには、コンパイル時にコードを生成するためのマクロがあります。Rustのマクロは、マクロ関数とマクロ属性の2種類があります。
マクロ関数[編集]
マクロ関数は、macro_rules!
マクロを使って定義されます。
これは、マクロのパターンとそれに対する置換を定義するマクロです。
マクロの呼び出し元は、パターンにマッチする式を渡し、置換が適用されたコードが生成されます。
- マクロ関数の例
macro_rules! say_hello { () => { println!("Hello, world!"); }; } fn main() { say_hello!(); }
- 実行結果
Hello, world!
- 解説
- 上記の例では、
say_hello!()
マクロを定義しています。これは、空の引数リストに対してprintln!("Hello, world!")
を生成するマクロです。
マクロ属性[編集]
マクロ属性はRustのコンパイル時にコードを修飾するために使用されます。 通常は、関数や構造体、列挙型、フィールドなどに適用されます。 マクロ属性を使用することで、コンパイル時に生成されるコードに追加の情報を提供できます。
- マクロ属性の例
#[derive(Debug)] struct MyStruct { my_field: i32, } fn main() { let my_struct = MyStruct { my_field: 42 }; println!("{:?}", my_struct); }
- 実行結果
MyStruct { my_field: 42 }
- 解説
- 上記の例では、
#[derive(Debug)]
マクロ属性を使用しています。これは、MyStruct
にDebug
トレイトを実装するために必要なコードを生成するマクロ属性です。println!
マクロでmy_struct
を表示する際に、Debug
トレイトのメソッドを呼び出して情報を表示することができます。
クレート[編集]
Rustでは、コードの単位として「クレート」が使用されます。クレートは、Rustのパッケージマネージャである「Cargo」によって管理され、複数のモジュールを含むことができます。クレートは、外部ライブラリを使用することも、他のプロジェクトで再利用することもできます。
クレートは、通常、以下のような構造を持ちます。
my_crate/ ├── src/ │ ├── main.rs │ ├── lib.rs │ └── other_module.rs ├── Cargo.toml └── README.md
- src/ : ソースコードを含むディレクトリ
- src/main.rs : クレートを実行するためのエントリポイント
- src/lib.rs : ライブラリクレートとしてコンパイルされる場合に使用されるエントリポイント
- src/other_module.rs : 他のモジュール
- Cargo.toml : クレートのメタデータ、依存関係、ビルド構成などを含むファイル
- README.md : クレートのドキュメント
Rustにおいて、パッケージはクレートと呼ばれます。クレートは、Rustのコードやライブラリをパッケージ化して共有できるようにするための仕組みです。クレートは、依存関係を解決するためのメタデータと、コードとその他のファイルから構成されています。
Rustには、crates.io
という公式のクレートレジストリがあり、開発者はそこでクレートを共有したり、他の開発者が作成したクレートを使用したりできます。
クレートの作成[編集]
クレートを作成するには、以下の手順を実行します。
cargo new
コマンドを実行して、新しいクレートのためのディレクトリを作成します。$ cargo new my_crate
- これにより、
my_crate
という名前の新しいディレクトリが作成され、その中にCargo.toml
とsrc/main.rs
の2つのファイルが作成されます。Cargo.toml
は、クレートのメタデータを定義するためのファイルであり、src/main.rs
はクレートのエントリーポイントであるメイン関数を含むファイルです。
Cargo.toml
ファイルを編集して、クレートのメタデータを定義します。- Cargo.toml
[package] name = "my_crate" version = "0.1.0" authors = ["Your Name <you@example.com>"]
name
、version
、authors
は、それぞれクレートの名前、バージョン、作者情報を定義するためのフィールドです。他にも多くのフィールドがありますが、ここでは説明しません。
src/main.rs
ファイルを編集して、クレートのコードを書きます。- src/main.rs
fn main() { println!("Hello, world!"); }
- これは、単純なHello Worldプログラムです。
cargo run
コマンドを実行して、クレートをビルドして実行します。$ cd my_crate $ cargo run Compiling my_crate v0.1.0 (/path/to/my_crate) Finished dev [unoptimized + debuginfo] target(s) in 0.34s Running `target/debug/my_crate` Hello, world!
cargo run
コマンドは、クレートをビルドして実行するための便利な方法です。
クレートの公開[編集]
クレートをcrates.io
に公開することができます。crates.io
はRustの公式のパッケージレジストリであり、Rustコミュニティの開発者によって開発された多数のクレートをホストしています。他の開発者があなたのクレートを簡単に見つけ、使用することができます。
クレートをcrates.io
に公開するには、cargo publish
コマンドを使用します。cargo publish
を実行する前に、crates.io
に登録するためにアカウントを作成する必要があります。
以下は、クレートの公開手順の概略です。
crates.io
にアカウントを作成します。cargo login
コマンドを使用して、crates.io
にログインします。ログインには、アカウント名とAPIトークンが必要です。- クレートを
crates.io
に公開する前に、バージョン番号を更新します。これにより、他の開発者が新しいバージョンを使用できるようになります。 cargo publish
コマンドを使用して、クレートをcrates.io
に公開します。
クレートを公開する前に、注意すべきいくつかの点があります。たとえば、クレートには、必要なドキュメントとリンク、正しいライセンス情報が含まれている必要があります。また、公開されたクレートは、他の開発者によって使用される可能性があるため、テストが完了していることを確認することも重要です。
ほかの言語からの移植例[編集]
順列・組合わせ[編集]
Goから順列・組合わせを移植
順列[編集]
- 順列
fn permutation<T: Clone>(s: &[T], n: usize) -> Vec<Vec<T>> { if s.is_empty() { panic!("slice is nil"); } if n == 1 { let mut result = Vec::new(); for v in s.iter() { result.push(vec![v.clone()]); } return result; } let mut result = Vec::new(); for (i, v) in s.iter().enumerate() { let mut sf = Vec::new(); for (j, w) in s.iter().enumerate() { if j != i { sf.push(w.clone()); } } for w in permutation(&sf, n - 1) { let mut v_w = vec![v.clone()]; v_w.extend_from_slice(&w); result.push(v_w); } } result } fn main() { println!("{:?}", permutation(&[1, 2, 3], 1)); println!("{:?}", permutation(&[0, 1, 2], 2)); println!( "{:?}", permutation(&["abc".to_string(), "def".to_string(), "xyz".to_string()], 3) ); println!("{:?}", permutation::<i32>(&[], 2)); }
- 実行結果
[[1], [2], [3]] [[0, 1], [0, 2], [1, 0], [1, 2], [2, 0], [2, 1]] [["abc", "def", "xyz"], ["abc", "xyz", "def"], ["def", "abc", "xyz"], ["def", "xyz", "abc"], ["xyz", "abc", "def"], ["xyz", "def", "abc"]]
- 解説
- 上記の移植において、主に以下の点に注意が必要でした。
- ジェネリック型の宣言方法がGoとは異なるため、
func Permutation[T any](s []T, n int)
のような書き方はできません。Rustでは、fn permutation<T: Clone>(s: &[T], n: usize)
のように、<T>
の前に:
を付けてジェネリック境界を宣言します。 - Goの
make
は、新しい配列やスライスを作成するための組み込み関数ですが、RustではVec::with_capacity()
やVec::new()
を使用します。 panic!("slice is nil")
は、Rustのパニック処理において、エラーメッセージを伴うパニックを発生させるために使用されます。Vec
に要素を追加するには、Goのappend
に相当するRustのメソッドであるVec::push()
や、Vec::extend_from_slice()
を使用します。また、Vec
の要素数は、len()
ではなくlen()
とVec::capacity()
の両方を使って取得する必要があります。
- ジェネリック型の宣言方法がGoとは異なるため、
組合わせ[編集]
- 組合わせ
fn combination<T: Clone>(s: &[T], n: usize) -> Vec<Vec<T>> { if s.is_empty() { panic!("slice is empty"); } if n == 1 { let mut result = Vec::new(); for v in s { result.push(vec![v.clone()]); } return result; } let mut result = Vec::new(); for i in 0..=(s.len() - n) { let v = s[i].clone(); for w in combination(&s[i + 1..], n - 1) { let mut res = vec![v.clone()]; res.extend(w); result.push(res); } } return result; } fn main() { println!("{:?}", combination(&[1, 2, 3], 1)); println!("{:?}", combination(&[0, 1, 2], 2)); println!( "{:?}", combination(&["abc", "def", "xyz"], 3) ); // println!("{:?}", combination(&[], 2)); 要素型が確定できない }
- 実行結果
[[1], [2], [3]] [[0, 1], [0, 2], [1, 2]] [["abc", "def", "xyz"]]
- 解説
- 上記の移植において、主に以下の点に注意が必要でした。
- Rustのジェネリック関数の型パラメータには制約が必要なため、
T
がクローン可能であることを示すClone
トレイトを指定する必要があります。 - Goのスライスと異なり、Rustのスライスは要素数が0の場合にも安全であるため、
ErrNilSlice
に相当する処理はslice.is_empty()
で判定することができます。 - Goのスライスと異なり、Rustのスライスは範囲外アクセスがパニックを引き起こすため、再帰呼び出し時にはスライスの範囲を明示的に指定する必要があります。
- Rustのジェネリック関数の型パラメータには制約が必要なため、
脚註[編集]
- ^ Hoare, Graydon (2016年12月28日). “Rust is mostly safety”. Graydon2. Dreamwidth Studios. 2021年12月3日閲覧。
- ^ 文字リテラルであることを強調するなら
let hello : &'static str = "Hello, world!";
とすべきだったかもしれません。 - ^ “Data Types - The Rust Programming Language”. 2021年12月8日閲覧。
- ^ Module std::fmt
- ^ Formatting traits
- ^ C言語系では、式を文にする為いに
};
が必要です。Rustもそう説明されている場合がありますが、Rustでは式の型の一致が目的です。 - ^ コードブロックが値を持つプログラミング言語としては、BCPL, Ruby, ScalaやKotlinがあります。
- ^ Option in std::option - Rust
外部リンク[編集]
参考文献[編集]
Jim Blandy, Jason Orendorff. オライリージャパン,『プログラミングRust』, 第2版, (2022年1月19日).
Jim Blandy, Jason Orendorff. オライリージャパン,『プログラミングRust』, 第1版, (2018年8月10日).