1635863760
このモジュールでは、プログラミング言語の一般的な概念を学び、それらがRustでどのように実装されているかを発見します。概念はRustに固有のものではありませんが、すべてのRustプログラムの基盤を提供します。これらの概念について学ぶことで、あらゆるプログラミング言語での開発をサポートするための理解を得ることができます。
錆遊び場は錆コンパイラにブラウザインタフェースです。言語をローカルにインストールする前、またはコンパイラが利用できない場合は、Playgroundを使用してRustコードの記述を試すことができます。このコース全体を通して、サンプルコードと演習へのPlaygroundリンクを提供します。現時点でRustツールチェーンを使用できない場合でも、コードを操作できます。
Rust Playgroundで実行されるすべてのコードは、ローカルの開発環境でコンパイルして実行することもできます。コンピューターからRustコンパイラーと対話することを躊躇しないでください。Rust Playgroundの詳細については、What isRust?をご覧ください。モジュール。
このモジュールでは、次のことを行います。
この単元では、単純なRustプログラムがどのように構成されているかを確認します。
関数は、特定のタスクを実行するコードのブロックです。プログラム内のコードをタスクに基づいてブロックに分割します。この分離により、コードの理解と保守が容易になります。タスクの関数を定義した後、そのタスクを実行する必要があるときに関数を呼び出すことができます。
すべてのRustプログラムには、という名前の関数が1つ必要mainです。main関数内のコードは、常にRustプログラムで実行される最初のコードです。関数内からmain、または他の関数内から他の関数を呼び出すことができます。
fn main() {
println!("Hello, world!");
}
Rustで関数を宣言するには、fn
キーワードを使用します。関数名の後に、関数が入力として期待するパラメーターまたは引数の数をコンパイラーに通知します。引数は括弧内にリストされています()
。関数本体は、機能のタスクを行い、中括弧内で定義されているコードです{}
。関数本体の開始中括弧が括弧内の引数リストの直後に表示されるように、コードをフォーマットすることをお勧めします。
関数本体では、ほとんどのコードステートメントはセミコロンで終わり;
ます。Rustは、これらのステートメントを順番に処理します。コードステートメントがセミコロンで終わっていない場合、Rustは、開始ステートメントを完了する前に、コードの次の行を実行する必要があることを認識しています。
コード内の実行関係を確認しやすくするために、インデントを使用します。この形式は、コードがどのように編成されているかを示し、関数タスクを完了するためのステップのフローを明らかにします。開始コードステートメントは、左マージンから4スペースインデントされます。コードがセミコロンで終わっていない場合、実行するコードの次の行はさらに4つのスペースでインデントされます。
次に例を示します。
fn main() { // The function declaration is not indented
// First step in function body
// Substep: execute before First step can be complete
// Second step in function body
// Substep A: execute before Second step can be complete
// Substep B: execute before Second step can be complete
// Sub-substep 1: execute before Substep B can be complete
// Third step in function body, and so on...
}
Rustモジュールで演習を行うと、サンプルコードでtodo!
マクロがよく使用されることに気付くでしょう。Rustのマクロは、可変数の入力引数を受け取る関数のようなものです。このtodo!
マクロは、Rustプログラムの未完成のコードを識別するために使用されます。このマクロは、プロトタイピングや、完全ではない動作を示したい場合に役立ちます。
todo!
演習でマクロを使用する方法の例を次に示します。
fn main() {
// Display the message "Hello, world!"
todo!("Display the message by using the println!() macro");
}
todo!
マクロを使用するコードをコンパイルすると、コンパイラーは、完了した機能を見つけることを期待するパニックメッセージを返す可能性があります。
Compiling playground v0.0.1 (/playground)
Finished dev [unoptimized + debuginfo] target(s) in 1.50s
Running `target/debug/playground`
thread 'main' panicked at 'not yet implemented: Display the message by using the println!() macro', src/main.rs:3:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
私たちのmain
関数は1つのタスクを実行します。println!
Rustで事前定義されているマクロを呼び出します。println!
マクロを期待それは画面または表示に一つ以上の入力引数、標準出力。この例では、1つの入力引数であるテキスト文字列「Hello、world!」をマクロに渡します。
さびコピー
fn main() {
// Our main function does one task: show a message
// println! displays the input "Hello, world!" to the screen
println!("Hello, world!");
}
Rust Learnモジュールのレッスンではprintln!
、中括弧{}
やその他の値のインスタンスを含むテキスト文字列を含む引数のリストを使用してマクロを呼び出すことがよくあります。println!
マクロは、中括弧の各インスタンスを置き換え{}
リスト内の次の引数の値を持つテキスト文字列内。
次に例を示します。
fn main() {
// Call println! with three arguments: a string, a value, a value
println!("The first letter of the English alphabet is {} and the last letter is {}.", 'A', 'Z');
}
println!
文字列、値、および別の値の3つの引数を使用してマクロを呼び出します。マクロは引数を順番に処理します。{}
テキスト文字列内の中括弧の各インスタンスは、リスト内の次の引数の値に置き換えられます。
出力は次のとおりです。
The first letter of the English alphabet is A and the last letter is Z.
Rustで変数を作成して使用する
開発者は、データを操作するためのコンピュータープログラムを作成します。データは収集、分析、保存、処理、共有、および報告されます。変数を使用して、コードの後半で参照できる名前付き参照にデータを格納します。
Rustでは、変数はキーワードで宣言されていlet
ます。各変数には一意の名前があります。変数が宣言されると、値にバインドすることも、プログラムの後半で値をバインドすることもできます。次のコードは、という名前の変数を宣言しますa_number
。
let a_number;
a_number
変数がまだ値にバインドされていません。このステートメントを変更して、値を変数にバインドできます。
let a_number = 10;
ノート
キーワード 他のプログラミング言語と同様に、およびのような特定のキーワードは、Rustのみが使用するために予約されています。キーワードを関数または変数の名前として使用することはできません。fnlet
別の例を見てみましょう。次のコードは、2つの変数を宣言しています。最初の変数が宣言され、数値にバインドされます。2番目の変数が宣言されていますが、値にバインドされていません。プログラムの後半で、2番目の変数の値が単語にバインドされます。コードはprintln!
マクロを呼び出して変数値を表示します。
// Declare a variable
let a_number;
// Declare a second variable and bind the value
let a_word = "Ten";
// Bind a value to the first variable
a_number = 10;
println!("The number is {}.", a_number);
println!("The word is {}.", a_word);
この例では、次の出力を出力します。
The number is 10.
The word is Ten.
println!
マクロを呼び出して、a_number
バインドされる前に変数の値を表示しようとすると、コンパイラーはエラーを返します。このエラーメッセージは、RustPlaygroundで確認できます。[実行]ボタンを選択してコードを実行します。
Rustでは、変数バインディングはデフォルトで不変です。変数が不変である場合、値が名前にバインドされた後は、その値を変更することはできません。
たとえばa_number
、前の例の変数の値を変更しようとすると、コンパイラからエラーメッセージが表示されます。
// Change the value of an immutable variable
a_number = 15;
この変更を自分で試して、RustPlaygroundでエラーメッセージを確認できます。
値をmut
変更するには、最初にキーワードを使用して変数バインディングを変更可能にする必要があります。
// The `mut` keyword lets the variable be changed
let mut a_number = 10;
println!("The number is {}.", a_number);
// Change the value of an immutable variable
a_number = 15;
println!("Now the number is {}.", a_number);
この例では、次の出力を出力します。
The number is 10.
Now the number is 15.
変数を変更a_number
できるようになったため、このコードはエラーなしでコンパイルされます。
既存の変数と同じ名前を使用する新しい変数を宣言できます。新しい宣言により、新しいバインディングが作成されます。Rustでは、新しい変数が前の変数をシャドウイングするため、この操作は「シャドウイング」と呼ばれます。古い変数はまだ存在しますが、このスコープでそれを参照することはできなくなりました。
次のコードは、シャドウイングの使用法を示しています。shadow_num
。という名前の変数を宣言します。各let
操作はnumber
、前の変数バインディングをシャドウイングしている間に名前が付けられた新しい変数を作成するため、変数を可変として定義しません。
// Declare first variable binding with name "shadow_num"
let shadow_num = 5;
// Declare second variable binding, shadows existing variable "shadow_num"
let shadow_num = shadow_num + 5;
// Declare third variable binding, shadows second binding of variable "shadow_num"
let shadow_num = shadow_num * 2;
println!("The number is {}.", shadow_num);
出力を推測できますか?この例を実行するには、RustPlaygroundにアクセスしてください。
数値、テキスト、および真/偽の値のデータ型を調べる
Rustは静的に型付けされた言語です。コンパイラは、プログラムをコンパイルして実行するために、コード内のすべての変数の正確なデータ型を知っている必要があります。コンパイラは通常、バインドされた値に基づいて変数のデータ型を推測できます。コード内でタイプを明示的に指定する必要は必ずしもありません。多くの型が可能な場合は、型注釈を使用して特定の型をコンパイラーに通知する必要があります。
次の例では、number
変数を32ビット整数として作成するようにコンパイラーに指示します。u32
変数名の後にデータ型を指定します。:
変数名の後にコロンが使用されていることに注意してください。
let number: u32 = 14;
println!("The number is {}.", number);
変数値を二重引用符で囲むと、コンパイラーは値を数値ではなくテキストとして解釈します。値の推定データ型がu32
変数に指定されたデータ型と一致しないため、コンパイラーはエラーを発行します。
let number: u32 = "14";
コンパイラエラー:
Compiling playground v0.0.1 (/playground)
error[E0308]: mismatched types
--> src/main.rs:2:23
|
2 | let number: u32 = "14";
| --- ^^^^ expected `u32`, found `&str`
| |
| expected due to this
error: aborting due to previous error
このRustPlaygroundで前述のコードを操作できます。
Rustには、数値、テキスト、および真実性を表現するためのいくつかの組み込みプリミティブデータ型が付属しています。これらのタイプのいくつかは、単一の値を表すため、スカラーと呼ばれます。
Rustは、文字列値やタプル値など、データ系列を処理するためのより複雑なデータ型も提供します。
Rustの整数は、ビットサイズと符号付きプロパティによって識別されます。符号付き整数は、正または負の数であってもよいです。符号なし整数は正の数値を指定できます。
長さ | 署名済み | 署名なし | ||
---|---|---|---|---|
8ビット | i8 | u8 | ||
16ビット | i16 | u16 | ||
32ビット | i32 | u32 | ||
64ビット | i64 | u64 | ||
128ビット | i128 | u128 | ||
アーキテクチャに依存 | isize | usize |
タイプは、あなたのプログラムが実行されているコンピュータの種類によって異なります。64ビットタイプは64ビットアーキテクチャで使用され、32ビットタイプは32ビットアーキテクチャで使用されます。整数の型を指定せず、システムが型を推測できない場合、デフォルトで型(32ビットの符号付き整数)が割り当てられます。isizeusizei32
Rustには、10進値用の2つの浮動小数点データ型f32
((32ビット)とf64
(64ビット))があります。デフォルトの浮動小数点型はf64
です。最近のCPUでは、f64
タイプはタイプとほぼ同じ速度ですが、f32
精度が高くなっています。
let number_64 = 4.0; // compiler infers the value to use the default type f64
let number_32: f32 = 5.0; // type f32 specified via annotation
Rustのすべてのプリミティブ数値型は、加算、減算、乗算、除算などの数学演算をサポートしています。
// Addition, Subtraction, and Multiplication
println!("1 + 2 = {} and 8 - 5 = {} and 15 * 3 = {}", 1u32 + 2, 8i32 - 5, 15 * 3);
// Integer and Floating point division
println!("9 / 2 = {} but 9.0 / 2.0 = {}", 9u32 / 2, 9.0 / 2.0);
ノート
println
関数を呼び出すときは、データ型の接尾辞を各リテラル番号に追加して、データ型についてRustに通知します。構文1u32
は、値が数値1であり、値を符号なし32ビット整数として解釈するようにコンパイラーに指示します。型注釈を提供しない場合、Rustはコンテキストから型を推測しようとします。コンテキストがあいまいな場合、i32
デフォルトでタイプ(32ビットの符号付き整数)が割り当てられます。
この例をRustPlaygroundで実行してみることができます。
Rustのブール型は、真実性を格納するために使用されます。bool
タイプは2つの値を持っていますtrue
かfalse
。ブール値は、条件式で広く使用されています。場合bool
ステートメントまたは値がtrueの場合、このアクションを行います。それ以外の場合(ステートメントまたは値がfalse)、別のアクションを実行します。ブール値は、多くの場合、比較チェックによって返されます。
次の例では、大なり>
記号演算子を使用して2つの値をテストします。演算子は、テストの結果を示すブール値を返します。
// Declare variable to store result of "greater than" test, Is 1 > 4? -- false
let is_bigger = 1 > 4;
println!("Is 1 > 4? {}", is_bigger);
Rustは、2つの基本的な文字列タイプと1つの文字タイプのテキスト値をサポートします。文字は単一のアイテムであり、文字列は一連の文字です。すべてのテキストタイプは有効なUTF-8表現です。
char
タイプは、テキストタイプの最も原始的です。値は、アイテムを一重引用符で囲むことによって指定されます。
let uppercase_s = 'S';
let lowercase_f = 'f';
let smiley_face = '😃';
ノート
一部の言語では、char
型を8ビットの符号なし整数として扱います。これはRustu8
型と同等です。char
Rustの型にはユニコードコードポイントが含まれていますが、utf-8エンコーディングを使用していません。char
RustのAは、32ビット幅になるようにパディングされた21ビット整数です。にchar
は、プレーンコードポイント値が直接含まれています。
str
知られているタイプ、文字列スライスは、あるビュー文字列データに変換します。ほとんどの場合、これらの型は、型の前にアンパサンドを付ける参照スタイルの構文を使用して参照します&str
。次のモジュールでリファレンスについて説明します。今の&str
ところ、不変の文字列データへのポインタと考えることができます。文字列リテラルはすべてタイプ&str
です。
文字列リテラルは、Rustの紹介の例で使用すると便利ですが、テキストを使用する可能性のあるすべての状況に適しているわけではありません。コンパイル時にすべての文字列を認識できるわけではありません。例としては、ユーザーが実行時にプログラムを操作し、端末を介してテキストを送信する場合があります。
これらのシナリオでは、RustにはString
。という名前の2番目の文字列型があります。このタイプはヒープに割り当てられます。String
型を使用する場合、コードをコンパイルする前に文字列の長さ(文字数)を知る必要はありません。
ノート
ガベージコレクションされた言語に精通している場合は、Rustに2つの文字列型があるのはなぜか疑問に思われるかもしれません。1文字列は非常に複雑なデータ型です。ほとんどの言語は、ガベージコレクターを使用して、この複雑さを理解しています。システムの言語としてのRustは、文字列に固有の複雑さの一部を明らかにします。複雑さが増すと、プログラムでのメモリの使用方法を非常にきめ細かく制御できます。
1 _実際、Rustには3つ以上の文字列タイプがあります。このモジュールでは、String
と&str
タイプについてのみ説明します。Rustのドキュメントで提供されている文字列型について詳しく知ることができます。
私たちは、違いの完全なアイデア得ることはありませんString
し、&str
私たちは錆の所有権および借入システムについて学ぶまでに。それまでは、String
型データは、プログラムの実行中に変化する可能性のあるテキストデータと考えることができます。&str
参照は、あなたのプログラムの実行と変わらないテキストデータへの不変の図です。
次の例は、Rustでchar
および&str
データ型を使用する方法を示しています。
: char
注釈構文で宣言されています。値は一重引用符を使用して指定されます。: &str
データ型を指定するための注釈構文で宣言されています。他の変数のデータ型は指定されていません。コンパイラは、コンテキストに基づいてこの変数のデータ型を推測します。string_1
変数には、一連の文字の最後に空のスペースが含まれていることに注意してください。
// Specify the data type "char"
let character_1: char = 'S';
let character_2: char = 'f';
// Complier interprets a single item in quotations as the "char" data type
let smiley_face = '😃';
// Complier interprets a series of items in quotations as a "str" data type and creates a "&str" reference
let string_1 = "miley ";
// Specify the data type "str" with the reference syntax "&str"
let string_2: &str = "ace";
println!("{} is a {}{}{}{}.", smiley_face, character_1, string_1, character_2, string_2);
この例の出力は次のとおりです。
😃 is a Smiley face.
この例で&
前str
にアンパサンドを指定しなかった場合はどうなりますか?調べるには、RustPlaygroundでこの例を実行してみてください。
タプルと構造体を使用してデータコレクションを定義する
この単元では、データコレクションまたは複合データの操作に役立つ2つのデータ型(タプルと構造体)について説明します。
タプルは、1つの複合値に収集されたさまざまなタイプの値のグループです。タプル内の個々の値は要素と呼ばれます。値は、括弧で囲まれたコンマ区切りのリストとして指定されます(<value>, <value>, ...)
。
タプルの長さは固定されており、要素の数と同じです。タプルが宣言された後は、サイズを拡大または縮小することはできません。要素を追加または削除することはできません。タプルのデータ型は、要素のデータ型のシーケンスによって定義されます。
これは、3つの要素を持つタプルの例です。
// Tuple of length 3
let tuple_e = ('E', 5i32, true);
次の表は、タプルの各要素の値、データ型、およびインデックスを示しています。
エレメント | 価値 | データ・タイプ |
---|---|---|
0 | E | char |
1 | 5 | i32 |
2 | NS | bool |
このタプルの型シグネチャは、次の3つの要素の型のシーケンスによって定義されます(char, i32, bool)
。
タプル内の要素には、ゼロから始まるインデックス位置からアクセスできます。このプロセスは、タプルインデックスと呼ばれます。タプル内の要素にアクセスするには、構文を使用します<tuple>.<index>
。
次の例は、インデックスを使用してタプル内の要素にアクセスする方法を示しています。
// Declare a tuple of three elements
let tuple_e = ('E', 5i32, true);
// Use tuple indexing and show the values of the elements in the tuple
println!("Is '{}' the {}th letter of the alphabet? {}", tuple_e.0, tuple_e.1, tuple_e.2);
この例は、次の出力を示しています。
Is 'E' the 5th letter of the alphabet? true
この例は、RustPlaygroundで調べることができます。
タプルは、さまざまなタイプを1つの値に結合する場合に役立ちます。タプルは任意の数の値を保持できるため、関数はタプルを使用して複数の値を返すことができます。
構造体は、他の型で構成される型です。構造体の要素はフィールドと呼ばれます。タプルと同様に、構造体のフィールドはさまざまなデータ型を持つことができます。構造体タイプの重要な利点は、各フィールドに名前を付けることができるため、値の意味が明確になることです。
Rustプログラムで構造体を操作するには、最初に名前で構造体を定義し、各フィールドのデータ型を指定します。次に、別の名前で構造体のインスタンスを作成します。インスタンスを宣言するときは、フィールドに特定の値を指定します。
Rustは、クラシック構造体、タプル構造体、ユニット構造体の3つの構造体タイプをサポートしています。これらの構造体タイプは、データをグループ化して操作するさまざまな方法をサポートしています。
<struct>.<field>
。<tuple>.<index>
。タプルと同様に、タプル構造体のインデックス値はゼロから始まります。次のコードは、3種類の構造体タイプの定義例を示しています。
// Classic struct with named fields
struct Student { name: String, level: u8, remote: bool }
// Tuple struct with data types only
struct Grades(char, char, char, char, f32);
// Unit struct
struct Unit;
構造体を定義するには、キーワードにstruct
続けて構造体名を入力します。グループ化されたデータの重要な特性を説明する構造体タイプの名前を選択します。これまで使用してきた命名規則とは異なり、構造体タイプの名前は大文字になります。
構造体タイプはmain
、Rustプログラムの関数やその他の関数の外部で定義されることがよくあります。このため、構造体定義の先頭は左マージンからインデントされません。データがどのように編成されているかを示すために、定義の内側の部分のみがインデントされています。
関数と同様に、古典的な構造体の本体は中括弧内に定義されています{}
。クラシック構造体の各フィールドには、構造体内で一意の名前が付けられています。各フィールドのタイプは、構文で指定されます: <type>
。クラシック構造体のフィールドは、コンマ区切りのリストとして指定されます<field>, <field>, ...
。古典的な構造体の定義はセミコロンで終わりません。
// Classic struct with named fields
struct Student { name: String, level: u8, remote: bool }
従来の構造体定義の利点は、名前で構造体フィールドの値にアクセスできることです。フィールド値にアクセスするには、構文を使用します<struct>.<field>
。
タプルと同様に、タプル構造体の本体は括弧内に定義されています()
。括弧は構造体名の直後に続きます。構造体名と開き括弧の間にスペースはありません。
タプルとは異なり、タプル構造体定義には、各フィールドのデータ型のみが含まれます。タプル構造体のデータ型は、コンマ区切りのリストとして指定されます<type>, <type>, ...
。
// Tuple struct with data types only
struct Grades(char, char, char, char, f32);
構造体タイプを定義した後、タイプのインスタンスを作成し、各フィールドに値を指定することにより、構造体を使用します。フィールド値を設定するときに、定義されているのと同じ順序でフィールドを指定する必要はありません。
次の例では、StudentおよびGrades構造体タイプ用に作成した定義を使用しています。
// Instantiate classic struct, specify fields in random order, or in specified order
let user_1 = Student { name: String::from("Constance Sharma"), remote: true, level: 2 };
let user_2 = Student { name: String::from("Dyson Tan"), level: 5, remote: false };
// Instantiate tuple structs, pass values in same order as types defined
let mark_1 = Grades('A', 'A', 'B', 'A', 3.75);
let mark_2 = Grades('B', 'A', 'A', 'C', 3.25);
println!("{}, level {}. Remote: {}. Grades: {}, {}, {}, {}. Average: {}",
user_1.name, user_1.level, user_1.remote, mark_1.0, mark_1.1, mark_1.2, mark_1.3, mark_1.4);
println!("{}, level {}. Remote: {}. Grades: {}, {}, {}, {}. Average: {}",
user_2.name, user_2.level, user_2.remote, mark_2.0, mark_2.1, mark_2.2, mark_2.3, mark_2.4);
構造体やベクトルなどの別のデータ構造内に格納されている文字列データは、文字列リテラル参照(&str
)からString
型に変換する必要があります。変換を行うには、標準的なString::from(&str)
方法を使用します。この例でこのメソッドをどのように使用しているかに注意してください。
// Classic struct with named fields
struct Student { name: String, level: u8, remote: bool }
...
let user_2 = Student { name: String::from("Dyson Tan"), level: 5, remote: false };
値を割り当てる前に型を変換しないと、コンパイラはエラーを発行します。
error[E0308]: mismatched types
--> src/main.rs:24:15
|
24 | name: "Dyson Tan",
| ^^^^^^^^^^^
| |
| expected struct `String`, found `&str`
| help: try using a conversion method: `"Dyson Tan".to_string()`
error: aborting due to previous error
コンパイラーは、.to_string()
関数を使用して変換を行うことができることを提案しています。この例では、String::from(&str)
メソッドを使用します。
このRustPlaygroundのサンプルコードを操作できます。
複合データに列挙型バリアントを使用する
列挙型は、いくつかのバリアントのいずれかになり得るタイプです。Rustが列挙型と呼ぶものは、より一般的に代数的データ型として知られています。重要な詳細は、各列挙型バリアントがそれに伴うデータを持つことができるということです。
私たちは、使用enum
列挙型変異体の任意の組み合わせを持つことができます列挙型を作成するキーワードを。構造体と同様に、列挙型バリアントには、名前付きフィールド、名前のないフィールド、またはフィールドがまったくない場合があります。構造体型と同様に、列挙型も大文字になります。
次の例では、Webイベントを分類するための列挙型を定義します。列挙型の各バリアントは独立しており、さまざまな量とタイプの値を格納します。
enum WebEvent {
// An enum variant can be like a unit struct without fields or data types
WELoad,
// An enum variant can be like a tuple struct with data types but no named fields
WEKeys(String, char),
// An enum variant can be like a classic struct with named fields and their data types
WEClick { x: i64, y: i64 }
}
この例の列挙型には、異なるタイプの3つのバリアントがあります。
WELoad
関連するデータ型またはデータはありません。WEKeys
データ型String
との2つのフィールドがありchar
ます。WEMClick
名前付きフィールドx
とy
、およびそれらのデータ型(i64
)を持つ匿名の構造体が含まれています。さまざまな種類の構造体タイプを定義する方法と同様のバリアントを使用して列挙型を定義します。すべてのバリアントは、同じWebEvent
列挙型にグループ化されます。列挙型の各バリアントは、独自のタイプではありません。WebEvent
列挙型のバリアントを使用する関数は、列挙型内のすべてのバリアントを受け入れる必要があります。WEClick
バリアントのみを受け入れ、他のバリアントは受け入れない関数を使用することはできません。
列挙型バリアントの要件を回避する方法は、列挙型のバリアントごとに個別の構造体を定義することです。次に、列挙型の各バリアントは対応する構造体を使用します。構造体は、対応する列挙型バリアントによって保持されていたものと同じデータを保持します。このスタイルの定義により、各論理バリアントを独自に参照できます。
次のコードは、この代替定義スタイルの使用方法を示しています。構造体は、データを保持するように定義されています。列挙型のバリアントは、構造体を参照するように定義されています。
// Define a tuple struct
struct KeyPress(String, char);
// Define a classic struct
struct MouseClick { x: i64, y: i64 }
// Redefine the enum variants to use the data from the new structs
// Update the page Load variant to have the boolean type
enum WebEvent { WELoad(bool), WEClick(MouseClick), WEKeys(KeyPress) }
次に、列挙型バリアントのインスタンスを作成するコードを追加しましょう。バリアントごとに、let
キーワードを使用して割り当てを行います。列挙型定義の特定のバリアントにアクセスするには<enum>::<variant>
、二重コロンを使用した構文を使用します::
。
WebEvent
列挙型の最初のバリアントには、単一のブール値、がありWELoad(bool)
ます。前の単元でブール値を操作したのと同様の方法で、このバリアントをインスタンス化します。
let we_load = WebEvent::WELoad(true);
2番目のバリアントには、古典的な構造体が含まれていWEClick(MouseClick)
ます。構造体には2つの名前付きフィールドx
とy
があり、両方のフィールドのi64
データ型があります。このバリアントを作成するには、最初に構造体をインスタンス化します。次に、呼び出しの引数として構造体を渡して、バリアントをインスタンス化します。
// Instantiate a MouseClick struct and bind the coordinate values
let click = MouseClick { x: 100, y: 250 };
// Set the WEClick variant to use the data in the click struct
let we_click = WebEvent::WEClick(click);
最後のバリアントにはタプルが含まれていWEKeys(KeyPress)
ます。タプルには、String
およびchar
データ型を使用する2つのフィールドがあります。このバリアントを作成するには、最初にタプルをインスタンス化します。次に、バリアントをインスタンス化するための呼び出しでタプルを引数として渡します。
// Instantiate a KeyPress tuple and bind the key values
let keys = KeyPress(String::from("Ctrl+"), 'N');
// Set the WEKeys variant to use the data in the keys tuple
let we_key = WebEvent::WEKeys(keys);
このコードでは、新しい構文を使用していることに注意してくださいString::from("<value>")
。この構文はString
、Rustfrom
メソッドを呼び出すことによってtypeの値を作成します。このメソッドは、二重引用符で囲まれたデータの入力引数を想定しています。
列挙型バリアントをインスタンス化するための最終的なコードは次のとおりです。
// Instantiate a MouseClick struct and bind the coordinate values
let click = MouseClick { x: 100, y: 250 };
println!("Mouse click location: {}, {}", click.x, click.y);
// Instantiate a KeyPress tuple and bind the key values
let keys = KeyPress(String::from("Ctrl+"), 'N');
println!("\nKeys pressed: {}{}", keys.0, keys.1);
// Instantiate WebEvent enum variants
// Set the boolean page Load value to true
let we_load = WebEvent::WELoad(true);
// Set the WEClick variant to use the data in the click struct
let we_click = WebEvent::WEClick(click);
// Set the WEKeys variant to use the data in the keys tuple
let we_key = WebEvent::WEKeys(keys);
// Print the values in the WebEvent enum variants
// Use the {:#?} syntax to display the enum structure and data in a readable form
println!("\nWebEvent enum structure: \n\n {:#?} \n\n {:#?} \n\n {:#?}", we_load, we_click, we_key);
このサンプルコードと対話するようにしてください錆遊び場。
Rust Playgroundで、次のコードステートメントを探します。このステートメントは、コードのいくつかの場所で使用されています。
// Set the Debug flag so we can check the data in the output
#[derive(Debug)]
この#[derive(Debug)]
構文により、コードの実行中に、他の方法では標準出力では表示できない特定の値を確認できます。println!
マクロでデバッグデータを表示するには、構文を使用し{:#?}
てデータを読み取り可能な方法でフォーマットします。
Rustの関数を操作する
関数は、Rust内でコードが実行される主要な方法です。この言語で最も重要な関数の1つである関数についてはすでに見てきましたmain
。この単元では、関数の定義方法について詳しく説明します。
Rustの関数定義は、fn
キーワードで始まります。関数名の後に、関数の入力引数を括弧内のデータ型のコンマ区切りリストとして指定します。中括弧は、関数本体の開始位置と終了位置をコンパイラーに通知します。
fn main() {
println!("Hello, world!");
goodbye();
}
fn goodbye() {
println!("Goodbye.");
}
名前と括弧内の入力引数を使用して関数を呼び出します。関数に入力引数がない場合は、括弧を空のままにします。この例では、main
とgoodbye
関数の両方に入力引数がありません。
goodbye
関数の後にmain
関数を定義したことに気づいたかもしれません。を定義するgoodbye
前に関数を定義することもできますmain
。Rustは、ファイルのどこかに関数が定義されている限り、ファイルのどこに関数を定義してもかまいません。
関数に入力引数がある場合、各引数に名前を付け、関数宣言の開始時にデータ型を指定します。引数は変数のように名前が付けられているため、関数本体の引数にアクセスできます。
goodbye
関数を変更して、入力引数として文字列データへのポインタを取得しましょう。
fn goodbye(message: &str) {
println!("\n{}", message);
}
fn main() {
let formal = "Formal: Good bye.";
let casual = "Casual: See you later!";
goodbye(formal);
goodbye(casual);
}
main
2つの異なる引数値を使用して関数から関数を呼び出すことで関数をテストし、出力を確認します。
Formal: Good bye.
Casual: See you later!
関数が値を返す場合、関数の-> <type>
引数のリストの後、関数本体の開始中括弧の前に構文を追加します。矢印の構文->
は、関数が呼び出し元に値を返すことを示しています。この<type>
部分は、返される値のデータ型をコンパイラーに知らせます。
Rustでは、関数のコードの最後の行を返す値と等しくすることにより、関数の最後に値を返すのが一般的な方法です。次の例は、この動作を示しています。このdivide_by_5
関数は、入力された数値を5で割った結果を呼び出し元の関数に返します。
fn divide_by_5(num: u32) -> u32 {
num / 5
}
fn main() {
let num = 25;
println!("25 divided by 5 = {}", num, divide_by_5(25));
}
出力は次のとおりです。
25 divided by 5 = 5
return関数の任意の時点でキーワードを使用して、実行を停止し、呼び出し元に値を送り返すことができます。通常、returnキーワードの使用は、条件付きテストと組み合わせて使用されます。
returnの値numが0の場合に、キーワードを明示的に使用して関数から早期に戻る例を次に示します。
fn divide_by_5(num: u32) -> u32 {
todo!("Check if num is 0") {
// Return early
return 0;
}
num / 5
}
return
キーワードを明示的に使用する場合は、ステートメントをセミコロンで終了します。return
キーワードを使用せずに戻り値を返送する場合、ステートメントをセミコロンで終了しません。num / 5
戻り値ステートメントに終了セミコロンを使用しなかったことにお気づきかもしれません。
関数の宣言の最初の部分は、関数シグネチャと呼ばれます。
このgoodbye
例の関数のシグネチャには、次の特性があります。
fn
:Rustの関数宣言キーワード。goodbye
:関数名。(message: &str)
:関数の引数またはパラメータリスト。入力値として、文字列データへの1つのポインタが必要です。-> bool
:矢印は、この関数が常に返す値のタイプを示しています。このgoodbye
関数は、1つの文字列ポインタを入力として受け入れ、ブール値を出力します
演習:車を作るための関数を書く
この演習では、列挙型、構造体、および関数を使用して、新しい車の注文を処理します。課題は、サンプルコードを修正して、コンパイルして実行することです。
この演習のサンプルコードで作業するには、次の2つのオプションがあります。
ノート
サンプルコードで、todo!
マクロを探します。このマクロは、完了するか更新する必要があるコードを示します。
最初のタスクは、列挙型定義の構文の問題を修正して、コードをコンパイルすることです。
サンプルコードの最初のブロックを開きます。
次のコードをコピーしてローカル開発環境で編集する
か、この準備されたRustPlaygroundでコードを開きます。
Transmission
プログラムが正常にコンパイルされるように、列挙型の構文エラーを修正してください。
次のセクションに進む前に、コードがコンパイルされていることを確認してください。コードはまだ出力を表示していませんが、エラーなしでコンパイルする必要があります。
コンパイラからの警告メッセージは無視してかまいません。警告は、列挙型と構造体の定義を宣言したが、まだ使用していないためです。
次のコードをコピーしてローカル開発環境で編集する
か、この準備されたRustPlaygroundでコードを開きます。
// Declare Car struct to describe vehicle with four named fields
struct Car {
color: String,
transmission: Transmission,
convertible: bool,
mileage: u32,
}
#[derive(PartialEq, Debug)]
// Declare enum for Car transmission type
enum Transmission {
// todo!("Fix enum definition so code compiles");
Manual;
SemiAuto;
Automatic;
}
2. Transmission
プログラムが正常にコンパイルされるように、列挙型の構文エラーを修正してください。
次のセクションに進む前に、コードがコンパイルされていることを確認してください。コードはまだ出力を表示していませんが、エラーなしでコンパイルする必要があります。
コンパイラからの警告メッセージは無視してかまいません。警告は、列挙型と構造体の定義を宣言したが、まだ使用していないためです。
次に、構造体のcar_factory
インスタンスを作成する関数のコードを追加しますCar
。入力引数の値を使用して、車の特性を割り当てます。
// Build a "Car" by using values from the input arguments
// - Color of car (String)
// - Transmission type (enum value)
// - Convertible (boolean, true if car is a convertible)
fn car_factory(color: String, transmission: Transmission, convertible: bool) {
// Use the values of the input arguments
// All new cars always have zero mileage
let car: Car = todo!("Create an instance of a `Car` struct");
}
2. コードを再構築し、コンパイルされることを確認します。繰り返しますが、警告メッセージは無視してかまいません。
3. car
変数の宣言を完了して、「Car」構造体のインスタンスを作成します。新しい車は、関数に渡された入力引数の値を使用する必要があります。すべての新車の走行距離はゼロです。
ヒント
ステートメントを型宣言
let car: Car
からインスタンス化に変更する必要がありますlet car = Car { ... }
。
4. コードを再構築し、コンパイルされることを確認します。
次に、car_factory
関数を更新して、作成されたCar
構造体を返します。値を返すには、関数のシグネチャで値の型を宣言し、関数の本体で値を指定する必要があります。
1. 関数のシグネチャを変更して、戻り値の型をCar
構造体として宣言します。ファイル内の次のコード行を変更します。
fn car_factory(color: String, transmission: Transmission, convertible: bool) = todo!("Return a `Car` struct") {
ヒント
大文字と小文字の区別に注意してください。まだコードをコンパイルしようとしないでください!
2. 新しく作成された車を返すには、Car
構造体をインスタンス化したステートメントを調整します。
let car: Car = todo!("An instance of a `Car` struct", "Set the function return value");
}
ヒント
前のセクションで
let car: Car =
は、Car
構造体のインスタンスを正しく作成するようにステートメントを変更しました。この手順を完了するには、このコードを簡略化できます。Car
構造体を作成し、新しく作成した車を1つのステートメントで返すことができます。let
またはreturn
キーワードを使用する必要はありません。
3. コードを再構築し、エラーなしでコンパイルされることを確認します。
これで、関数を呼び出して車を作成する準備が整いました。
main
既存のコードに関数を追加します。新しいコードは、ファイルの上部または下部に追加できます。fn main() {
// We have orders for three new cars!
// We'll declare a mutable car variable and reuse it for all the cars
let mut car = car_factory(String::from("Red"), Transmission::Manual, false);
println!("Car 1 = {}, {:?} transmission, convertible: {}, mileage: {}", car.color, car.transmission, car.convertible, car.mileage);
car = car_factory(String::from("Silver"), Transmission::Automatic, true);
println!("Car 2 = {}, {:?} transmission, convertible: {}, mileage: {}", car.color, car.transmission, car.convertible, car.mileage);
car = car_factory(String::from("Yellow"), Transmission::SemiAuto, false);
println!("Car 3 = {}, {:?} transmission, convertible: {}, mileage: {}", car.color, car.transmission, car.convertible, car.mileage);
}
2. コードを再構築します。宣言されたすべての項目が使用されるようになったため、コンパイラーはエラーや警告を発行しないはずです。次の出力が表示されます。
Car 1 = Red, Manual transmission, convertible: false, mileage: 0
Car 2 = Silver, Automatic transmission, convertible: true, mileage: 0
Car 3 = Yellow, SemiAuto transmission, convertible: false, mileage: 0
あなたはこの中に調製した溶液を使用してコードを比較することができ錆遊び場。
概要
このモジュールでは、Rustプログラムの基本構造を確認しました。このmain
関数は、すべてのRustプログラムへのエントリポイントです。println!
マクロ変数の値および表示プログラムの進行状況を表示するために使用することができます。変数はlet
キーワードで定義されます。それらの値は、mut
キーワードを使用して不変または可変(変更可能)として宣言できます。
多くのプライマリおよび複合データ型を含む、コアRust言語の概念を調査しました。整数と浮動小数点数、文字とテキスト文字列、およびブール値のtrue / false値の操作方法を学習しました。Rust言語は、データ型を厳密に解釈します。プログラムは、データ型が正しく定義および使用されている場合にのみ、正常にコンパイルおよび実行されます。
演習では、struct
とに格納されているデータを使用して自動車を作成する関数を作成しましたenum
。todo!
サンプルプログラムでマクロのインスタンスを探し、コードを完成させました。Rustプレイグラウンドを使用して、コードを変更し、プログラムをコンパイルして、実行可能ファイルを実行しました。
このラーニングパスの次のモジュールでは、Rustデータ型の詳細と、プログラムでif / else条件式を使用する方法について説明します。
リンク: https://docs.microsoft.com/en-us/learn/modules/rust-create-program/
1635863760
このモジュールでは、プログラミング言語の一般的な概念を学び、それらがRustでどのように実装されているかを発見します。概念はRustに固有のものではありませんが、すべてのRustプログラムの基盤を提供します。これらの概念について学ぶことで、あらゆるプログラミング言語での開発をサポートするための理解を得ることができます。
錆遊び場は錆コンパイラにブラウザインタフェースです。言語をローカルにインストールする前、またはコンパイラが利用できない場合は、Playgroundを使用してRustコードの記述を試すことができます。このコース全体を通して、サンプルコードと演習へのPlaygroundリンクを提供します。現時点でRustツールチェーンを使用できない場合でも、コードを操作できます。
Rust Playgroundで実行されるすべてのコードは、ローカルの開発環境でコンパイルして実行することもできます。コンピューターからRustコンパイラーと対話することを躊躇しないでください。Rust Playgroundの詳細については、What isRust?をご覧ください。モジュール。
このモジュールでは、次のことを行います。
この単元では、単純なRustプログラムがどのように構成されているかを確認します。
関数は、特定のタスクを実行するコードのブロックです。プログラム内のコードをタスクに基づいてブロックに分割します。この分離により、コードの理解と保守が容易になります。タスクの関数を定義した後、そのタスクを実行する必要があるときに関数を呼び出すことができます。
すべてのRustプログラムには、という名前の関数が1つ必要mainです。main関数内のコードは、常にRustプログラムで実行される最初のコードです。関数内からmain、または他の関数内から他の関数を呼び出すことができます。
fn main() {
println!("Hello, world!");
}
Rustで関数を宣言するには、fn
キーワードを使用します。関数名の後に、関数が入力として期待するパラメーターまたは引数の数をコンパイラーに通知します。引数は括弧内にリストされています()
。関数本体は、機能のタスクを行い、中括弧内で定義されているコードです{}
。関数本体の開始中括弧が括弧内の引数リストの直後に表示されるように、コードをフォーマットすることをお勧めします。
関数本体では、ほとんどのコードステートメントはセミコロンで終わり;
ます。Rustは、これらのステートメントを順番に処理します。コードステートメントがセミコロンで終わっていない場合、Rustは、開始ステートメントを完了する前に、コードの次の行を実行する必要があることを認識しています。
コード内の実行関係を確認しやすくするために、インデントを使用します。この形式は、コードがどのように編成されているかを示し、関数タスクを完了するためのステップのフローを明らかにします。開始コードステートメントは、左マージンから4スペースインデントされます。コードがセミコロンで終わっていない場合、実行するコードの次の行はさらに4つのスペースでインデントされます。
次に例を示します。
fn main() { // The function declaration is not indented
// First step in function body
// Substep: execute before First step can be complete
// Second step in function body
// Substep A: execute before Second step can be complete
// Substep B: execute before Second step can be complete
// Sub-substep 1: execute before Substep B can be complete
// Third step in function body, and so on...
}
Rustモジュールで演習を行うと、サンプルコードでtodo!
マクロがよく使用されることに気付くでしょう。Rustのマクロは、可変数の入力引数を受け取る関数のようなものです。このtodo!
マクロは、Rustプログラムの未完成のコードを識別するために使用されます。このマクロは、プロトタイピングや、完全ではない動作を示したい場合に役立ちます。
todo!
演習でマクロを使用する方法の例を次に示します。
fn main() {
// Display the message "Hello, world!"
todo!("Display the message by using the println!() macro");
}
todo!
マクロを使用するコードをコンパイルすると、コンパイラーは、完了した機能を見つけることを期待するパニックメッセージを返す可能性があります。
Compiling playground v0.0.1 (/playground)
Finished dev [unoptimized + debuginfo] target(s) in 1.50s
Running `target/debug/playground`
thread 'main' panicked at 'not yet implemented: Display the message by using the println!() macro', src/main.rs:3:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
私たちのmain
関数は1つのタスクを実行します。println!
Rustで事前定義されているマクロを呼び出します。println!
マクロを期待それは画面または表示に一つ以上の入力引数、標準出力。この例では、1つの入力引数であるテキスト文字列「Hello、world!」をマクロに渡します。
さびコピー
fn main() {
// Our main function does one task: show a message
// println! displays the input "Hello, world!" to the screen
println!("Hello, world!");
}
Rust Learnモジュールのレッスンではprintln!
、中括弧{}
やその他の値のインスタンスを含むテキスト文字列を含む引数のリストを使用してマクロを呼び出すことがよくあります。println!
マクロは、中括弧の各インスタンスを置き換え{}
リスト内の次の引数の値を持つテキスト文字列内。
次に例を示します。
fn main() {
// Call println! with three arguments: a string, a value, a value
println!("The first letter of the English alphabet is {} and the last letter is {}.", 'A', 'Z');
}
println!
文字列、値、および別の値の3つの引数を使用してマクロを呼び出します。マクロは引数を順番に処理します。{}
テキスト文字列内の中括弧の各インスタンスは、リスト内の次の引数の値に置き換えられます。
出力は次のとおりです。
The first letter of the English alphabet is A and the last letter is Z.
Rustで変数を作成して使用する
開発者は、データを操作するためのコンピュータープログラムを作成します。データは収集、分析、保存、処理、共有、および報告されます。変数を使用して、コードの後半で参照できる名前付き参照にデータを格納します。
Rustでは、変数はキーワードで宣言されていlet
ます。各変数には一意の名前があります。変数が宣言されると、値にバインドすることも、プログラムの後半で値をバインドすることもできます。次のコードは、という名前の変数を宣言しますa_number
。
let a_number;
a_number
変数がまだ値にバインドされていません。このステートメントを変更して、値を変数にバインドできます。
let a_number = 10;
ノート
キーワード 他のプログラミング言語と同様に、およびのような特定のキーワードは、Rustのみが使用するために予約されています。キーワードを関数または変数の名前として使用することはできません。fnlet
別の例を見てみましょう。次のコードは、2つの変数を宣言しています。最初の変数が宣言され、数値にバインドされます。2番目の変数が宣言されていますが、値にバインドされていません。プログラムの後半で、2番目の変数の値が単語にバインドされます。コードはprintln!
マクロを呼び出して変数値を表示します。
// Declare a variable
let a_number;
// Declare a second variable and bind the value
let a_word = "Ten";
// Bind a value to the first variable
a_number = 10;
println!("The number is {}.", a_number);
println!("The word is {}.", a_word);
この例では、次の出力を出力します。
The number is 10.
The word is Ten.
println!
マクロを呼び出して、a_number
バインドされる前に変数の値を表示しようとすると、コンパイラーはエラーを返します。このエラーメッセージは、RustPlaygroundで確認できます。[実行]ボタンを選択してコードを実行します。
Rustでは、変数バインディングはデフォルトで不変です。変数が不変である場合、値が名前にバインドされた後は、その値を変更することはできません。
たとえばa_number
、前の例の変数の値を変更しようとすると、コンパイラからエラーメッセージが表示されます。
// Change the value of an immutable variable
a_number = 15;
この変更を自分で試して、RustPlaygroundでエラーメッセージを確認できます。
値をmut
変更するには、最初にキーワードを使用して変数バインディングを変更可能にする必要があります。
// The `mut` keyword lets the variable be changed
let mut a_number = 10;
println!("The number is {}.", a_number);
// Change the value of an immutable variable
a_number = 15;
println!("Now the number is {}.", a_number);
この例では、次の出力を出力します。
The number is 10.
Now the number is 15.
変数を変更a_number
できるようになったため、このコードはエラーなしでコンパイルされます。
既存の変数と同じ名前を使用する新しい変数を宣言できます。新しい宣言により、新しいバインディングが作成されます。Rustでは、新しい変数が前の変数をシャドウイングするため、この操作は「シャドウイング」と呼ばれます。古い変数はまだ存在しますが、このスコープでそれを参照することはできなくなりました。
次のコードは、シャドウイングの使用法を示しています。shadow_num
。という名前の変数を宣言します。各let
操作はnumber
、前の変数バインディングをシャドウイングしている間に名前が付けられた新しい変数を作成するため、変数を可変として定義しません。
// Declare first variable binding with name "shadow_num"
let shadow_num = 5;
// Declare second variable binding, shadows existing variable "shadow_num"
let shadow_num = shadow_num + 5;
// Declare third variable binding, shadows second binding of variable "shadow_num"
let shadow_num = shadow_num * 2;
println!("The number is {}.", shadow_num);
出力を推測できますか?この例を実行するには、RustPlaygroundにアクセスしてください。
数値、テキスト、および真/偽の値のデータ型を調べる
Rustは静的に型付けされた言語です。コンパイラは、プログラムをコンパイルして実行するために、コード内のすべての変数の正確なデータ型を知っている必要があります。コンパイラは通常、バインドされた値に基づいて変数のデータ型を推測できます。コード内でタイプを明示的に指定する必要は必ずしもありません。多くの型が可能な場合は、型注釈を使用して特定の型をコンパイラーに通知する必要があります。
次の例では、number
変数を32ビット整数として作成するようにコンパイラーに指示します。u32
変数名の後にデータ型を指定します。:
変数名の後にコロンが使用されていることに注意してください。
let number: u32 = 14;
println!("The number is {}.", number);
変数値を二重引用符で囲むと、コンパイラーは値を数値ではなくテキストとして解釈します。値の推定データ型がu32
変数に指定されたデータ型と一致しないため、コンパイラーはエラーを発行します。
let number: u32 = "14";
コンパイラエラー:
Compiling playground v0.0.1 (/playground)
error[E0308]: mismatched types
--> src/main.rs:2:23
|
2 | let number: u32 = "14";
| --- ^^^^ expected `u32`, found `&str`
| |
| expected due to this
error: aborting due to previous error
このRustPlaygroundで前述のコードを操作できます。
Rustには、数値、テキスト、および真実性を表現するためのいくつかの組み込みプリミティブデータ型が付属しています。これらのタイプのいくつかは、単一の値を表すため、スカラーと呼ばれます。
Rustは、文字列値やタプル値など、データ系列を処理するためのより複雑なデータ型も提供します。
Rustの整数は、ビットサイズと符号付きプロパティによって識別されます。符号付き整数は、正または負の数であってもよいです。符号なし整数は正の数値を指定できます。
長さ | 署名済み | 署名なし | ||
---|---|---|---|---|
8ビット | i8 | u8 | ||
16ビット | i16 | u16 | ||
32ビット | i32 | u32 | ||
64ビット | i64 | u64 | ||
128ビット | i128 | u128 | ||
アーキテクチャに依存 | isize | usize |
タイプは、あなたのプログラムが実行されているコンピュータの種類によって異なります。64ビットタイプは64ビットアーキテクチャで使用され、32ビットタイプは32ビットアーキテクチャで使用されます。整数の型を指定せず、システムが型を推測できない場合、デフォルトで型(32ビットの符号付き整数)が割り当てられます。isizeusizei32
Rustには、10進値用の2つの浮動小数点データ型f32
((32ビット)とf64
(64ビット))があります。デフォルトの浮動小数点型はf64
です。最近のCPUでは、f64
タイプはタイプとほぼ同じ速度ですが、f32
精度が高くなっています。
let number_64 = 4.0; // compiler infers the value to use the default type f64
let number_32: f32 = 5.0; // type f32 specified via annotation
Rustのすべてのプリミティブ数値型は、加算、減算、乗算、除算などの数学演算をサポートしています。
// Addition, Subtraction, and Multiplication
println!("1 + 2 = {} and 8 - 5 = {} and 15 * 3 = {}", 1u32 + 2, 8i32 - 5, 15 * 3);
// Integer and Floating point division
println!("9 / 2 = {} but 9.0 / 2.0 = {}", 9u32 / 2, 9.0 / 2.0);
ノート
println
関数を呼び出すときは、データ型の接尾辞を各リテラル番号に追加して、データ型についてRustに通知します。構文1u32
は、値が数値1であり、値を符号なし32ビット整数として解釈するようにコンパイラーに指示します。型注釈を提供しない場合、Rustはコンテキストから型を推測しようとします。コンテキストがあいまいな場合、i32
デフォルトでタイプ(32ビットの符号付き整数)が割り当てられます。
この例をRustPlaygroundで実行してみることができます。
Rustのブール型は、真実性を格納するために使用されます。bool
タイプは2つの値を持っていますtrue
かfalse
。ブール値は、条件式で広く使用されています。場合bool
ステートメントまたは値がtrueの場合、このアクションを行います。それ以外の場合(ステートメントまたは値がfalse)、別のアクションを実行します。ブール値は、多くの場合、比較チェックによって返されます。
次の例では、大なり>
記号演算子を使用して2つの値をテストします。演算子は、テストの結果を示すブール値を返します。
// Declare variable to store result of "greater than" test, Is 1 > 4? -- false
let is_bigger = 1 > 4;
println!("Is 1 > 4? {}", is_bigger);
Rustは、2つの基本的な文字列タイプと1つの文字タイプのテキスト値をサポートします。文字は単一のアイテムであり、文字列は一連の文字です。すべてのテキストタイプは有効なUTF-8表現です。
char
タイプは、テキストタイプの最も原始的です。値は、アイテムを一重引用符で囲むことによって指定されます。
let uppercase_s = 'S';
let lowercase_f = 'f';
let smiley_face = '😃';
ノート
一部の言語では、char
型を8ビットの符号なし整数として扱います。これはRustu8
型と同等です。char
Rustの型にはユニコードコードポイントが含まれていますが、utf-8エンコーディングを使用していません。char
RustのAは、32ビット幅になるようにパディングされた21ビット整数です。にchar
は、プレーンコードポイント値が直接含まれています。
str
知られているタイプ、文字列スライスは、あるビュー文字列データに変換します。ほとんどの場合、これらの型は、型の前にアンパサンドを付ける参照スタイルの構文を使用して参照します&str
。次のモジュールでリファレンスについて説明します。今の&str
ところ、不変の文字列データへのポインタと考えることができます。文字列リテラルはすべてタイプ&str
です。
文字列リテラルは、Rustの紹介の例で使用すると便利ですが、テキストを使用する可能性のあるすべての状況に適しているわけではありません。コンパイル時にすべての文字列を認識できるわけではありません。例としては、ユーザーが実行時にプログラムを操作し、端末を介してテキストを送信する場合があります。
これらのシナリオでは、RustにはString
。という名前の2番目の文字列型があります。このタイプはヒープに割り当てられます。String
型を使用する場合、コードをコンパイルする前に文字列の長さ(文字数)を知る必要はありません。
ノート
ガベージコレクションされた言語に精通している場合は、Rustに2つの文字列型があるのはなぜか疑問に思われるかもしれません。1文字列は非常に複雑なデータ型です。ほとんどの言語は、ガベージコレクターを使用して、この複雑さを理解しています。システムの言語としてのRustは、文字列に固有の複雑さの一部を明らかにします。複雑さが増すと、プログラムでのメモリの使用方法を非常にきめ細かく制御できます。
1 _実際、Rustには3つ以上の文字列タイプがあります。このモジュールでは、String
と&str
タイプについてのみ説明します。Rustのドキュメントで提供されている文字列型について詳しく知ることができます。
私たちは、違いの完全なアイデア得ることはありませんString
し、&str
私たちは錆の所有権および借入システムについて学ぶまでに。それまでは、String
型データは、プログラムの実行中に変化する可能性のあるテキストデータと考えることができます。&str
参照は、あなたのプログラムの実行と変わらないテキストデータへの不変の図です。
次の例は、Rustでchar
および&str
データ型を使用する方法を示しています。
: char
注釈構文で宣言されています。値は一重引用符を使用して指定されます。: &str
データ型を指定するための注釈構文で宣言されています。他の変数のデータ型は指定されていません。コンパイラは、コンテキストに基づいてこの変数のデータ型を推測します。string_1
変数には、一連の文字の最後に空のスペースが含まれていることに注意してください。
// Specify the data type "char"
let character_1: char = 'S';
let character_2: char = 'f';
// Complier interprets a single item in quotations as the "char" data type
let smiley_face = '😃';
// Complier interprets a series of items in quotations as a "str" data type and creates a "&str" reference
let string_1 = "miley ";
// Specify the data type "str" with the reference syntax "&str"
let string_2: &str = "ace";
println!("{} is a {}{}{}{}.", smiley_face, character_1, string_1, character_2, string_2);
この例の出力は次のとおりです。
😃 is a Smiley face.
この例で&
前str
にアンパサンドを指定しなかった場合はどうなりますか?調べるには、RustPlaygroundでこの例を実行してみてください。
タプルと構造体を使用してデータコレクションを定義する
この単元では、データコレクションまたは複合データの操作に役立つ2つのデータ型(タプルと構造体)について説明します。
タプルは、1つの複合値に収集されたさまざまなタイプの値のグループです。タプル内の個々の値は要素と呼ばれます。値は、括弧で囲まれたコンマ区切りのリストとして指定されます(<value>, <value>, ...)
。
タプルの長さは固定されており、要素の数と同じです。タプルが宣言された後は、サイズを拡大または縮小することはできません。要素を追加または削除することはできません。タプルのデータ型は、要素のデータ型のシーケンスによって定義されます。
これは、3つの要素を持つタプルの例です。
// Tuple of length 3
let tuple_e = ('E', 5i32, true);
次の表は、タプルの各要素の値、データ型、およびインデックスを示しています。
エレメント | 価値 | データ・タイプ |
---|---|---|
0 | E | char |
1 | 5 | i32 |
2 | NS | bool |
このタプルの型シグネチャは、次の3つの要素の型のシーケンスによって定義されます(char, i32, bool)
。
タプル内の要素には、ゼロから始まるインデックス位置からアクセスできます。このプロセスは、タプルインデックスと呼ばれます。タプル内の要素にアクセスするには、構文を使用します<tuple>.<index>
。
次の例は、インデックスを使用してタプル内の要素にアクセスする方法を示しています。
// Declare a tuple of three elements
let tuple_e = ('E', 5i32, true);
// Use tuple indexing and show the values of the elements in the tuple
println!("Is '{}' the {}th letter of the alphabet? {}", tuple_e.0, tuple_e.1, tuple_e.2);
この例は、次の出力を示しています。
Is 'E' the 5th letter of the alphabet? true
この例は、RustPlaygroundで調べることができます。
タプルは、さまざまなタイプを1つの値に結合する場合に役立ちます。タプルは任意の数の値を保持できるため、関数はタプルを使用して複数の値を返すことができます。
構造体は、他の型で構成される型です。構造体の要素はフィールドと呼ばれます。タプルと同様に、構造体のフィールドはさまざまなデータ型を持つことができます。構造体タイプの重要な利点は、各フィールドに名前を付けることができるため、値の意味が明確になることです。
Rustプログラムで構造体を操作するには、最初に名前で構造体を定義し、各フィールドのデータ型を指定します。次に、別の名前で構造体のインスタンスを作成します。インスタンスを宣言するときは、フィールドに特定の値を指定します。
Rustは、クラシック構造体、タプル構造体、ユニット構造体の3つの構造体タイプをサポートしています。これらの構造体タイプは、データをグループ化して操作するさまざまな方法をサポートしています。
<struct>.<field>
。<tuple>.<index>
。タプルと同様に、タプル構造体のインデックス値はゼロから始まります。次のコードは、3種類の構造体タイプの定義例を示しています。
// Classic struct with named fields
struct Student { name: String, level: u8, remote: bool }
// Tuple struct with data types only
struct Grades(char, char, char, char, f32);
// Unit struct
struct Unit;
構造体を定義するには、キーワードにstruct
続けて構造体名を入力します。グループ化されたデータの重要な特性を説明する構造体タイプの名前を選択します。これまで使用してきた命名規則とは異なり、構造体タイプの名前は大文字になります。
構造体タイプはmain
、Rustプログラムの関数やその他の関数の外部で定義されることがよくあります。このため、構造体定義の先頭は左マージンからインデントされません。データがどのように編成されているかを示すために、定義の内側の部分のみがインデントされています。
関数と同様に、古典的な構造体の本体は中括弧内に定義されています{}
。クラシック構造体の各フィールドには、構造体内で一意の名前が付けられています。各フィールドのタイプは、構文で指定されます: <type>
。クラシック構造体のフィールドは、コンマ区切りのリストとして指定されます<field>, <field>, ...
。古典的な構造体の定義はセミコロンで終わりません。
// Classic struct with named fields
struct Student { name: String, level: u8, remote: bool }
従来の構造体定義の利点は、名前で構造体フィールドの値にアクセスできることです。フィールド値にアクセスするには、構文を使用します<struct>.<field>
。
タプルと同様に、タプル構造体の本体は括弧内に定義されています()
。括弧は構造体名の直後に続きます。構造体名と開き括弧の間にスペースはありません。
タプルとは異なり、タプル構造体定義には、各フィールドのデータ型のみが含まれます。タプル構造体のデータ型は、コンマ区切りのリストとして指定されます<type>, <type>, ...
。
// Tuple struct with data types only
struct Grades(char, char, char, char, f32);
構造体タイプを定義した後、タイプのインスタンスを作成し、各フィールドに値を指定することにより、構造体を使用します。フィールド値を設定するときに、定義されているのと同じ順序でフィールドを指定する必要はありません。
次の例では、StudentおよびGrades構造体タイプ用に作成した定義を使用しています。
// Instantiate classic struct, specify fields in random order, or in specified order
let user_1 = Student { name: String::from("Constance Sharma"), remote: true, level: 2 };
let user_2 = Student { name: String::from("Dyson Tan"), level: 5, remote: false };
// Instantiate tuple structs, pass values in same order as types defined
let mark_1 = Grades('A', 'A', 'B', 'A', 3.75);
let mark_2 = Grades('B', 'A', 'A', 'C', 3.25);
println!("{}, level {}. Remote: {}. Grades: {}, {}, {}, {}. Average: {}",
user_1.name, user_1.level, user_1.remote, mark_1.0, mark_1.1, mark_1.2, mark_1.3, mark_1.4);
println!("{}, level {}. Remote: {}. Grades: {}, {}, {}, {}. Average: {}",
user_2.name, user_2.level, user_2.remote, mark_2.0, mark_2.1, mark_2.2, mark_2.3, mark_2.4);
構造体やベクトルなどの別のデータ構造内に格納されている文字列データは、文字列リテラル参照(&str
)からString
型に変換する必要があります。変換を行うには、標準的なString::from(&str)
方法を使用します。この例でこのメソッドをどのように使用しているかに注意してください。
// Classic struct with named fields
struct Student { name: String, level: u8, remote: bool }
...
let user_2 = Student { name: String::from("Dyson Tan"), level: 5, remote: false };
値を割り当てる前に型を変換しないと、コンパイラはエラーを発行します。
error[E0308]: mismatched types
--> src/main.rs:24:15
|
24 | name: "Dyson Tan",
| ^^^^^^^^^^^
| |
| expected struct `String`, found `&str`
| help: try using a conversion method: `"Dyson Tan".to_string()`
error: aborting due to previous error
コンパイラーは、.to_string()
関数を使用して変換を行うことができることを提案しています。この例では、String::from(&str)
メソッドを使用します。
このRustPlaygroundのサンプルコードを操作できます。
複合データに列挙型バリアントを使用する
列挙型は、いくつかのバリアントのいずれかになり得るタイプです。Rustが列挙型と呼ぶものは、より一般的に代数的データ型として知られています。重要な詳細は、各列挙型バリアントがそれに伴うデータを持つことができるということです。
私たちは、使用enum
列挙型変異体の任意の組み合わせを持つことができます列挙型を作成するキーワードを。構造体と同様に、列挙型バリアントには、名前付きフィールド、名前のないフィールド、またはフィールドがまったくない場合があります。構造体型と同様に、列挙型も大文字になります。
次の例では、Webイベントを分類するための列挙型を定義します。列挙型の各バリアントは独立しており、さまざまな量とタイプの値を格納します。
enum WebEvent {
// An enum variant can be like a unit struct without fields or data types
WELoad,
// An enum variant can be like a tuple struct with data types but no named fields
WEKeys(String, char),
// An enum variant can be like a classic struct with named fields and their data types
WEClick { x: i64, y: i64 }
}
この例の列挙型には、異なるタイプの3つのバリアントがあります。
WELoad
関連するデータ型またはデータはありません。WEKeys
データ型String
との2つのフィールドがありchar
ます。WEMClick
名前付きフィールドx
とy
、およびそれらのデータ型(i64
)を持つ匿名の構造体が含まれています。さまざまな種類の構造体タイプを定義する方法と同様のバリアントを使用して列挙型を定義します。すべてのバリアントは、同じWebEvent
列挙型にグループ化されます。列挙型の各バリアントは、独自のタイプではありません。WebEvent
列挙型のバリアントを使用する関数は、列挙型内のすべてのバリアントを受け入れる必要があります。WEClick
バリアントのみを受け入れ、他のバリアントは受け入れない関数を使用することはできません。
列挙型バリアントの要件を回避する方法は、列挙型のバリアントごとに個別の構造体を定義することです。次に、列挙型の各バリアントは対応する構造体を使用します。構造体は、対応する列挙型バリアントによって保持されていたものと同じデータを保持します。このスタイルの定義により、各論理バリアントを独自に参照できます。
次のコードは、この代替定義スタイルの使用方法を示しています。構造体は、データを保持するように定義されています。列挙型のバリアントは、構造体を参照するように定義されています。
// Define a tuple struct
struct KeyPress(String, char);
// Define a classic struct
struct MouseClick { x: i64, y: i64 }
// Redefine the enum variants to use the data from the new structs
// Update the page Load variant to have the boolean type
enum WebEvent { WELoad(bool), WEClick(MouseClick), WEKeys(KeyPress) }
次に、列挙型バリアントのインスタンスを作成するコードを追加しましょう。バリアントごとに、let
キーワードを使用して割り当てを行います。列挙型定義の特定のバリアントにアクセスするには<enum>::<variant>
、二重コロンを使用した構文を使用します::
。
WebEvent
列挙型の最初のバリアントには、単一のブール値、がありWELoad(bool)
ます。前の単元でブール値を操作したのと同様の方法で、このバリアントをインスタンス化します。
let we_load = WebEvent::WELoad(true);
2番目のバリアントには、古典的な構造体が含まれていWEClick(MouseClick)
ます。構造体には2つの名前付きフィールドx
とy
があり、両方のフィールドのi64
データ型があります。このバリアントを作成するには、最初に構造体をインスタンス化します。次に、呼び出しの引数として構造体を渡して、バリアントをインスタンス化します。
// Instantiate a MouseClick struct and bind the coordinate values
let click = MouseClick { x: 100, y: 250 };
// Set the WEClick variant to use the data in the click struct
let we_click = WebEvent::WEClick(click);
最後のバリアントにはタプルが含まれていWEKeys(KeyPress)
ます。タプルには、String
およびchar
データ型を使用する2つのフィールドがあります。このバリアントを作成するには、最初にタプルをインスタンス化します。次に、バリアントをインスタンス化するための呼び出しでタプルを引数として渡します。
// Instantiate a KeyPress tuple and bind the key values
let keys = KeyPress(String::from("Ctrl+"), 'N');
// Set the WEKeys variant to use the data in the keys tuple
let we_key = WebEvent::WEKeys(keys);
このコードでは、新しい構文を使用していることに注意してくださいString::from("<value>")
。この構文はString
、Rustfrom
メソッドを呼び出すことによってtypeの値を作成します。このメソッドは、二重引用符で囲まれたデータの入力引数を想定しています。
列挙型バリアントをインスタンス化するための最終的なコードは次のとおりです。
// Instantiate a MouseClick struct and bind the coordinate values
let click = MouseClick { x: 100, y: 250 };
println!("Mouse click location: {}, {}", click.x, click.y);
// Instantiate a KeyPress tuple and bind the key values
let keys = KeyPress(String::from("Ctrl+"), 'N');
println!("\nKeys pressed: {}{}", keys.0, keys.1);
// Instantiate WebEvent enum variants
// Set the boolean page Load value to true
let we_load = WebEvent::WELoad(true);
// Set the WEClick variant to use the data in the click struct
let we_click = WebEvent::WEClick(click);
// Set the WEKeys variant to use the data in the keys tuple
let we_key = WebEvent::WEKeys(keys);
// Print the values in the WebEvent enum variants
// Use the {:#?} syntax to display the enum structure and data in a readable form
println!("\nWebEvent enum structure: \n\n {:#?} \n\n {:#?} \n\n {:#?}", we_load, we_click, we_key);
このサンプルコードと対話するようにしてください錆遊び場。
Rust Playgroundで、次のコードステートメントを探します。このステートメントは、コードのいくつかの場所で使用されています。
// Set the Debug flag so we can check the data in the output
#[derive(Debug)]
この#[derive(Debug)]
構文により、コードの実行中に、他の方法では標準出力では表示できない特定の値を確認できます。println!
マクロでデバッグデータを表示するには、構文を使用し{:#?}
てデータを読み取り可能な方法でフォーマットします。
Rustの関数を操作する
関数は、Rust内でコードが実行される主要な方法です。この言語で最も重要な関数の1つである関数についてはすでに見てきましたmain
。この単元では、関数の定義方法について詳しく説明します。
Rustの関数定義は、fn
キーワードで始まります。関数名の後に、関数の入力引数を括弧内のデータ型のコンマ区切りリストとして指定します。中括弧は、関数本体の開始位置と終了位置をコンパイラーに通知します。
fn main() {
println!("Hello, world!");
goodbye();
}
fn goodbye() {
println!("Goodbye.");
}
名前と括弧内の入力引数を使用して関数を呼び出します。関数に入力引数がない場合は、括弧を空のままにします。この例では、main
とgoodbye
関数の両方に入力引数がありません。
goodbye
関数の後にmain
関数を定義したことに気づいたかもしれません。を定義するgoodbye
前に関数を定義することもできますmain
。Rustは、ファイルのどこかに関数が定義されている限り、ファイルのどこに関数を定義してもかまいません。
関数に入力引数がある場合、各引数に名前を付け、関数宣言の開始時にデータ型を指定します。引数は変数のように名前が付けられているため、関数本体の引数にアクセスできます。
goodbye
関数を変更して、入力引数として文字列データへのポインタを取得しましょう。
fn goodbye(message: &str) {
println!("\n{}", message);
}
fn main() {
let formal = "Formal: Good bye.";
let casual = "Casual: See you later!";
goodbye(formal);
goodbye(casual);
}
main
2つの異なる引数値を使用して関数から関数を呼び出すことで関数をテストし、出力を確認します。
Formal: Good bye.
Casual: See you later!
関数が値を返す場合、関数の-> <type>
引数のリストの後、関数本体の開始中括弧の前に構文を追加します。矢印の構文->
は、関数が呼び出し元に値を返すことを示しています。この<type>
部分は、返される値のデータ型をコンパイラーに知らせます。
Rustでは、関数のコードの最後の行を返す値と等しくすることにより、関数の最後に値を返すのが一般的な方法です。次の例は、この動作を示しています。このdivide_by_5
関数は、入力された数値を5で割った結果を呼び出し元の関数に返します。
fn divide_by_5(num: u32) -> u32 {
num / 5
}
fn main() {
let num = 25;
println!("25 divided by 5 = {}", num, divide_by_5(25));
}
出力は次のとおりです。
25 divided by 5 = 5
return関数の任意の時点でキーワードを使用して、実行を停止し、呼び出し元に値を送り返すことができます。通常、returnキーワードの使用は、条件付きテストと組み合わせて使用されます。
returnの値numが0の場合に、キーワードを明示的に使用して関数から早期に戻る例を次に示します。
fn divide_by_5(num: u32) -> u32 {
todo!("Check if num is 0") {
// Return early
return 0;
}
num / 5
}
return
キーワードを明示的に使用する場合は、ステートメントをセミコロンで終了します。return
キーワードを使用せずに戻り値を返送する場合、ステートメントをセミコロンで終了しません。num / 5
戻り値ステートメントに終了セミコロンを使用しなかったことにお気づきかもしれません。
関数の宣言の最初の部分は、関数シグネチャと呼ばれます。
このgoodbye
例の関数のシグネチャには、次の特性があります。
fn
:Rustの関数宣言キーワード。goodbye
:関数名。(message: &str)
:関数の引数またはパラメータリスト。入力値として、文字列データへの1つのポインタが必要です。-> bool
:矢印は、この関数が常に返す値のタイプを示しています。このgoodbye
関数は、1つの文字列ポインタを入力として受け入れ、ブール値を出力します
演習:車を作るための関数を書く
この演習では、列挙型、構造体、および関数を使用して、新しい車の注文を処理します。課題は、サンプルコードを修正して、コンパイルして実行することです。
この演習のサンプルコードで作業するには、次の2つのオプションがあります。
ノート
サンプルコードで、todo!
マクロを探します。このマクロは、完了するか更新する必要があるコードを示します。
最初のタスクは、列挙型定義の構文の問題を修正して、コードをコンパイルすることです。
サンプルコードの最初のブロックを開きます。
次のコードをコピーしてローカル開発環境で編集する
か、この準備されたRustPlaygroundでコードを開きます。
Transmission
プログラムが正常にコンパイルされるように、列挙型の構文エラーを修正してください。
次のセクションに進む前に、コードがコンパイルされていることを確認してください。コードはまだ出力を表示していませんが、エラーなしでコンパイルする必要があります。
コンパイラからの警告メッセージは無視してかまいません。警告は、列挙型と構造体の定義を宣言したが、まだ使用していないためです。
次のコードをコピーしてローカル開発環境で編集する
か、この準備されたRustPlaygroundでコードを開きます。
// Declare Car struct to describe vehicle with four named fields
struct Car {
color: String,
transmission: Transmission,
convertible: bool,
mileage: u32,
}
#[derive(PartialEq, Debug)]
// Declare enum for Car transmission type
enum Transmission {
// todo!("Fix enum definition so code compiles");
Manual;
SemiAuto;
Automatic;
}
2. Transmission
プログラムが正常にコンパイルされるように、列挙型の構文エラーを修正してください。
次のセクションに進む前に、コードがコンパイルされていることを確認してください。コードはまだ出力を表示していませんが、エラーなしでコンパイルする必要があります。
コンパイラからの警告メッセージは無視してかまいません。警告は、列挙型と構造体の定義を宣言したが、まだ使用していないためです。
次に、構造体のcar_factory
インスタンスを作成する関数のコードを追加しますCar
。入力引数の値を使用して、車の特性を割り当てます。
// Build a "Car" by using values from the input arguments
// - Color of car (String)
// - Transmission type (enum value)
// - Convertible (boolean, true if car is a convertible)
fn car_factory(color: String, transmission: Transmission, convertible: bool) {
// Use the values of the input arguments
// All new cars always have zero mileage
let car: Car = todo!("Create an instance of a `Car` struct");
}
2. コードを再構築し、コンパイルされることを確認します。繰り返しますが、警告メッセージは無視してかまいません。
3. car
変数の宣言を完了して、「Car」構造体のインスタンスを作成します。新しい車は、関数に渡された入力引数の値を使用する必要があります。すべての新車の走行距離はゼロです。
ヒント
ステートメントを型宣言
let car: Car
からインスタンス化に変更する必要がありますlet car = Car { ... }
。
4. コードを再構築し、コンパイルされることを確認します。
次に、car_factory
関数を更新して、作成されたCar
構造体を返します。値を返すには、関数のシグネチャで値の型を宣言し、関数の本体で値を指定する必要があります。
1. 関数のシグネチャを変更して、戻り値の型をCar
構造体として宣言します。ファイル内の次のコード行を変更します。
fn car_factory(color: String, transmission: Transmission, convertible: bool) = todo!("Return a `Car` struct") {
ヒント
大文字と小文字の区別に注意してください。まだコードをコンパイルしようとしないでください!
2. 新しく作成された車を返すには、Car
構造体をインスタンス化したステートメントを調整します。
let car: Car = todo!("An instance of a `Car` struct", "Set the function return value");
}
ヒント
前のセクションで
let car: Car =
は、Car
構造体のインスタンスを正しく作成するようにステートメントを変更しました。この手順を完了するには、このコードを簡略化できます。Car
構造体を作成し、新しく作成した車を1つのステートメントで返すことができます。let
またはreturn
キーワードを使用する必要はありません。
3. コードを再構築し、エラーなしでコンパイルされることを確認します。
これで、関数を呼び出して車を作成する準備が整いました。
main
既存のコードに関数を追加します。新しいコードは、ファイルの上部または下部に追加できます。fn main() {
// We have orders for three new cars!
// We'll declare a mutable car variable and reuse it for all the cars
let mut car = car_factory(String::from("Red"), Transmission::Manual, false);
println!("Car 1 = {}, {:?} transmission, convertible: {}, mileage: {}", car.color, car.transmission, car.convertible, car.mileage);
car = car_factory(String::from("Silver"), Transmission::Automatic, true);
println!("Car 2 = {}, {:?} transmission, convertible: {}, mileage: {}", car.color, car.transmission, car.convertible, car.mileage);
car = car_factory(String::from("Yellow"), Transmission::SemiAuto, false);
println!("Car 3 = {}, {:?} transmission, convertible: {}, mileage: {}", car.color, car.transmission, car.convertible, car.mileage);
}
2. コードを再構築します。宣言されたすべての項目が使用されるようになったため、コンパイラーはエラーや警告を発行しないはずです。次の出力が表示されます。
Car 1 = Red, Manual transmission, convertible: false, mileage: 0
Car 2 = Silver, Automatic transmission, convertible: true, mileage: 0
Car 3 = Yellow, SemiAuto transmission, convertible: false, mileage: 0
あなたはこの中に調製した溶液を使用してコードを比較することができ錆遊び場。
概要
このモジュールでは、Rustプログラムの基本構造を確認しました。このmain
関数は、すべてのRustプログラムへのエントリポイントです。println!
マクロ変数の値および表示プログラムの進行状況を表示するために使用することができます。変数はlet
キーワードで定義されます。それらの値は、mut
キーワードを使用して不変または可変(変更可能)として宣言できます。
多くのプライマリおよび複合データ型を含む、コアRust言語の概念を調査しました。整数と浮動小数点数、文字とテキスト文字列、およびブール値のtrue / false値の操作方法を学習しました。Rust言語は、データ型を厳密に解釈します。プログラムは、データ型が正しく定義および使用されている場合にのみ、正常にコンパイルおよび実行されます。
演習では、struct
とに格納されているデータを使用して自動車を作成する関数を作成しましたenum
。todo!
サンプルプログラムでマクロのインスタンスを探し、コードを完成させました。Rustプレイグラウンドを使用して、コードを変更し、プログラムをコンパイルして、実行可能ファイルを実行しました。
このラーニングパスの次のモジュールでは、Rustデータ型の詳細と、プログラムでif / else条件式を使用する方法について説明します。
リンク: https://docs.microsoft.com/en-us/learn/modules/rust-create-program/
1636035960
Rustには、言語に組み込まれたシンプルでありながら強力なテストスイートが付属しています。この単元では、プログラムの正確性をさらに保証するために、さまざまなテスト戦略を作成する方法について説明します。
このモジュールでは、次のことを学びます。
if
、Rustでステートメントがどのように機能するかを理解します。ユニットテストを書く
Rustの単体テストは#[test]
、非テストコードが期待どおりに機能していることを確認する属性でマークされた単純な関数です。これらの関数は、コードをテストするときにのみコンパイルされます。
テスト関数は、テストするコードを実行します。次に、多くの場合、assert!
またはassert_eq!
マクロを使用して結果を確認します。
次のコード例では、単純なadd
関数とadd_works
、#[test]
属性でマークされた別の関数を定義します。
fn add(a: i32, b: i32) -> i32 {
a + b
}
#[test]
fn add_works() {
assert_eq!(add(1, 2), 3);
assert_eq!(add(10, 12), 22);
assert_eq!(add(5, -2), 3);
}
コマンドを実行すると、$ cargo test
出力は次の例のようになります。
running 1 test
test add_works ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
cargo tests
動作を確認するために、失敗したテストを含めてみましょう。
#[test]
fn add_fails() {
assert_eq!(add(2, 2), 7);
}
$ cargo test
コマンドを使用してテストを再度実行すると、add_works
テストに合格したことが出力に表示されます。また、add_fails
失敗したことを示し、の失敗した呼び出しに関する情報を含める必要がありますassert_eq
。
running 2 tests
test add_works ... ok
test add_fails ... FAILED
failures:
---- add_fails stdout ----
thread 'add_fails' panicked at 'assertion failed: `(left == right)`
left: `4`,
right: `7`', src/main.rs:14:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
failures:
add_fails
test result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out
多くのシナリオでは、条件によってが発生するかどうかをテストすることが重要panic!
です。
このshould_panic
属性を使用すると、を確認できますpanic!
。この属性をテスト関数に追加すると、関数内のコードがパニックになったときにテストに合格します。コードがパニックにならない場合、テストは失敗します。
これで、add_fails
テスト関数は予想されるパニックをキャプチャし、合格テストとして扱うことができます。
#[test]
#[should_panic]
fn add_fails() {
assert_eq!(add(2, 2), 7);
}
そして、私たちのテスト結果は次のようになります。
running 2 tests
test add_works ... ok
test add_fails ... ok
test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
[test]
属性で注釈が付けられた関数には、属性で注釈を付けることもでき[ignore]
ます。この属性により、テスト中にそのテスト機能がスキップされます。
[ignore]
属性は、必要に応じてテストを無視する理由を書き込むことができます。
#[test]
#[ignore = "not yet reviewed by the Q.A. team"]
fn add_negatives() {
assert_eq!(add(-2, -2), -4)
}
無視されたテスト関数は、引き続き型チェックおよびコンパイルされますが、テストでは実行されません。
running 3 tests
test add_negatives ... ignored
test add_works ... ok
test add_fails ... ok
test result: ok. 2 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out
ほとんどの単体テストは、#[cfg(test)]
属性を持つサブモジュールに入ります。
fn add(a: i32, b: i32) -> i32 {
a + b
}
#[cfg(test)]
mod add_function_tests {
use super::*;
#[test]
fn add_works() {
assert_eq!(add(1, 2), 3);
assert_eq!(add(10, 12), 22);
assert_eq!(add(5, -2), 3);
}
#[test]
#[should_panic]
fn add_fails() {
assert_eq!(add(2, 2), 7);
}
#[test]
#[ignore]
fn add_negatives() {
assert_eq!(add(-2, -2), -4)
}
}
cfg
属性には、条件付きコンパイルを制御し、唯一の述語がある場合は、それが添付だものをコンパイルしますtrue
。test
我々は、コマンドを実行するたびにコンパイルフラグは、貨物によって自動的に発行された$ cargo test
私たちがテストを実行するとき、それは常にtrueになりますので、。
use super::*;
宣言は、内部コードに必要なadd_function_tests
アクセスするためのモジュールadd
外側モジュール。
演習-単体テストを書く
この演習では、is_even
関数の2つのテスト関数を記述します。コードがコンパイルされ、両方のテストに合格すると、演習が完了したことがわかります。2つのテスト関数の本体のみを編集する必要があります。
pub fn is_even(num: i32) -> bool {
num % 2 == 0
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn is_true_when_even() {
assert!();
}
#[test]
fn is_false_when_odd() {
assert!();
}
}
ドキュメントテストを書く
Rustを使用すると、ドキュメントの例をテストとして実行できます。Rustライブラリを文書化する主な方法は、ドキュメントコメントと呼ばれるトリプルスラッシュ(///)でソースコードに注釈を付けることです。ドキュメントのコメントはMarkdownで記述されており、コードブロックをサポートしているため、これらのコードブロックはコンパイルされ、テストとして使用されます。
この機能を試すには、最初に新しいライブラリプロジェクトを作成する必要があります。
$ cargo new --lib basic_math
$ cd basic_math
src/lib.rs
Visual Studio Codeでファイルを開き、既存のコンテンツを次のコードに置き換えます。
/// Generally, the first line is a brief summary describing the function.
///
/// The next lines present detailed documentation.
/// Code blocks start with triple backticks. The code has an implicit `fn main()` inside and `extern crate <cratename>`,
/// which means you can just start writing code.
///
/// ```
/// let result = basic_math::add(2, 3);
/// assert_eq!(result, 5);
/// ```
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
単体テストと同様に、ドキュメントテストは、パニックを起こさずに実行すれば合格します。特定の結果を確認するには、assert!
マクロを使用して、実際の出力が期待どおりであることを確認します。コマンドを使用してこのコードのテストスイートを呼び出すことができ$ cargo test
、出力は次の例のようになります。
$ cargo test
Finished test [unoptimized + debuginfo] target(s) in 0.00s
Running target/debug/deps/basic_math-910b859a2a6f3c3f
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
Doc-tests basic_math
running 1 test
test src/lib.rs - add (line 6) ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
出力は、0個の単体テストと1個のドキュメントテストがあることを示していることに注意してください。
演習-ドキュメントテストを書く
この演習では、関数div
との次のコードスニペットでドキュメントテストを作成しますsub
。
div
関数については、次のドキュメントテストを記述します。
10
割ったものをアサートします。25
6
割ったものをアサートします。32
10
割ったものを主張し0
ます。sub
関数については、次のドキュメントテストを記述します。
9
引いた値が2
等しいことをアサートします7
。6
引いた値が9
等しいことをアサートします-3
。/// This function divides two numbers.
///
/// # Example #1: 10 / 2 == 5
///
/// ```
/// let result = doctests_exercise::div(...); // TODO: finish this test!
/// assert_eq!(result, 2);
/// ```
///
/// # Example #2: 6 / 2 = 3
///
/// ```
/// TODO: Write this doctest!
/// ```
///
/// # Panics
///
/// The function panics if the second argument is zero.
///
/// ```rust,should_panic
/// // panics on division by zero
/// TODO: Write this doctest!
/// ```
pub fn div(a: i32, b: i32) -> i32 {
if b == 0 {
panic!("Divide-by-zero error");
}
a / b
}
/// This function subtracts two numbers.
///
/// # Example #1: 9 - 2 == 7
///
/// ```
/// TODO: Write this doctest!
/// ```
///
/// # Example #2: 6 - 9 == -3
///
/// ```
/// TODO: Write this doctest!
/// ```
pub fn sub(a: i32, b: i32) -> i32 {
a - b
}
ドキュメントテストは図書館の箱でのみ利用できるため、最初にコンピューターでプロジェクトを作成することを忘れないでください。この目的を達成するために、ターミナルで次のコマンドを実行できます。
$ cargo new --lib doctests_exercise
$ cd doctests_exercise
クレートの名前は、この演習の最初の割り当てですでに入力されているため、関数の名前の前に付ける必要があります。
統合テストを書く
ユニットテストとドキュメントテストは、簡潔で具体的なテストを提供します。しかし、クレート全体をテストすることもお勧めします。次に、クレートのさまざまなコード部分が期待どおりに一緒に機能することを確認できます。
クレート全体をテストするには、統合テストを使用できます。Rustテストスイートは、このタイプのテストをサポートします。このタイプのテストは、ライブラリのパブリックAPIに含まれる関数のみを呼び出します。統合テストを使用して、他のユーザーがコードを使用したときにコードがどのように機能するかを確認できます。
これらのテストのユニークな点は、それらが別々のディレクトリとファイルに存在するため、ライブラリコードを外部でテストできることです。Cargoとの統合テストを実行するときは、それらをtestsディレクトリに配置します。Cargoは、このディレクトリ内の各ソースファイルを実行します。srcディレクトリと同じレベルのプロジェクトディレクトリにテストを作成します。
新しい小さなプロジェクトを作成して、いくつかの統合テストを書いてみましょう。ターミナルで次のコマンドを実行します。
$ cargo new --lib rusty_pizza
$ cd rusty_pizza
この例ではPizza
、privateメソッドとpublicメソッドを持つ単純な構造体を使用します。
pub struct Pizza {
pub topping: String,
pub inches: u8,
}
impl Pizza {
pub fn pepperoni(inches: u8) -> Self {
Pizza::bake("pepperoni", inches)
}
pub fn mozzarella(inches: u8) -> Self {
Pizza::bake("mozzarella", inches)
}
fn bake(topping: &str, inches: u8) -> Self {
Pizza {
topping: String::from(topping),
inches,
}
}
}
上記のスニペットは、ピザを準備するためにプライベートメソッドに依存Pizza
する2つのパブリックメソッドとを備えた構造体を特徴Pizza::pepperoni
としています。Pizza::mozzarellaPizza::bake
ディレクトリのtests
横に名前を付けた新しいディレクトリを作成しますsrc
。pizzas.rs
次の内容で名前が付けられた新しいファイルを配置します。
use rusty_pizza::Pizza;
#[test]
fn can_make_pepperoni_pizza() {
let pizza = Pizza::pepperoni(12);
assert_eq!(pizza.topping, "pepperoni");
assert_eq!(pizza.inches, 12);
}
#[test]
fn can_make_mozzarella_pizza() {
let pizza = Pizza::mozzarella(16);
assert_eq!(pizza.topping, "mozzarella");
assert_eq!(pizza.inches, 16);
}
統合テストのセットアップが完了したので、cargo test
コマンドを実行して結果を確認できます。
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
Running target/debug/deps/pizzas-1996564f80b33a1e
running 2 tests
test can_make_mozzarella_pizza ... ok
test can_make_pepperoni_pizza ... ok
test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
Doc-tests rusty_pizza
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
出力から、Rustがテストの結果を別々のセクションに配置していることがわかります。単体テストの結果が最初に表示され、次に統合の結果、最後にドキュメントの結果が表示されます。
統合テストのセクションでは、tests/pizzas.rs
ファイル内の2つのテストがテストスイートによって収集および実行されたことがわかります。
バイナリクレートは他のクレートが使用できる機能を公開しないため、統合テストでテストできるのはライブラリクレートのみです。その結果、多くのRustバイナリクレートには、のsrc/lib.rs
ほとんどのコードを含むファイルが含まれていますsrc/main.rs
。統合テストでは、クレートをライブラリとしてインポートすることにより、バイナリの機能をテストできuse
ます。
概要
このモジュールでは、次のことを学びました。
このラーニングパスの次のモジュールでは、これまでに学習したすべてのスキルを使用して、完全に機能するRustプログラムを開発します。
リンク: https://docs.microsoft.com/en-us/learn/modules/rust-automated-tests/
1636058460
この最後のモジュールでは、最新の実世界のRustプログラムを開発します。
このアプリケーションは、コマンドラインのToDoトラッカーです。タスクをテキストファイルに記録し、ターミナルにリストとして表示して、完了のマークを付けます。
このモジュールでは、次のことを学びます。
アプリケーションの概要
コーディングを開始する前に、このアプリケーションに実装する予定のすべての部分について考える必要があります。これは、やること項目を管理するためのコマンドラインジャーナルアプリになります。派手なインターフェースについて心配する必要はありません。ただし、ユーザーがコマンドラインに発行するアクションを解釈するには、コマンドライン引数を処理して解析する必要があります。
プログラムインターフェイスは、次の3つの簡単なアクションを処理します。
プログラムは、やること項目をある種のストレージに保持します。テキストファイルは、この種のデータを格納するのに十分である必要があります。そのため、JSONなどのファイル形式に固執して情報をエンコードできます。データをストレージに保存し、ストレージから取得する必要があります。
アプリケーションのユースケースを指定したので、各アクションを独自のモジュールに割り当てることができます。コマンドライン解析とタスク永続化のためのモジュールを用意し、そのmain.rs
モジュールを使用してそれらをリンクし、考えられるすべてのエラーを処理することは理にかなっています。
To Doタスクを操作するためTask
、各ToDo項目を追跡するための構造体も必要です。
そうは言っても、最初のプロジェクトテンプレートを作成しましょう。ローカル開発環境cargo new
で、ターミナルのコマンドを使用して新しいCargoプロジェクトを作成します。プロジェクトを呼び出しますrusty-journal
。
$ cargo new rusty-journal
Created binary (application) `rusty-journal` package
次の単元では、新しいモジュール、タイプ、および関数をプログラムに追加します。
CLIモジュールを作成します
cli
モジュールは、ユーザーがコマンドラインインターフェイス(したがって名前)を介して入力するユーザー入力を処理します。モジュールをプロジェクトにsrc/cli.rs
追加するには、プロジェクトルートで名前を付けたファイルを作成し、そのmain.rs
ファイルに次の行を追加する必要があります。
mod cli;
新しいモジュールにはコードが含まれていません。それを変えましょう。
structopt
Rustの標準ライブラリを使用して、コマンドライン引数を解析および処理できます。しかし、それを合理的にうまく行うには、膨大な量のコードと労力が必要になります。structopt
単純な構造体を定義するのと同じくらい簡単にこのタスクを実行できる、というサードパーティのクレートを使用します。
コマンドを実行することにより、cargo search structopt
それが利用可能かどうかを確認し、最新バージョンを判別できます。
$ cargo search structopt
structopt = "0.3.21" # Parse command-line argument by defining a struct.
...
ファイルの[dependencies]
セクションに次のエントリを追加して、プロジェクトの依存関係として追加しましょうCargo.toml
。
[dependencies]
structopt = "0.3"
これからは、コードのどの部分からでも直接参照できます。
CommandLineArgs
構造体を作成する次に、プログラムが実行できるすべての可能なアクションを表す構造体を作成する必要があります。前の単元では、これらのアクションを定義しました。
structopt
READMEページを注意深く読んだ場合、これらの交互のオプションを表現する最良の方法は、を使用しenum
て3つのアクションすべてを保持することであると判断するかもしれません。
を使用する前にstructopt
、コマンドライン引数を表すタイプを見てみましょう。
use std::path::PathBuf;
pub enum Action {
Add { task: String },
Done { position: usize },
List,
}
pub struct CommandLineArgs {
pub action: Action,
pub journal_file: Option<PathBuf>,
}
Action
列挙型は、我々のプログラムの中で必要がありますアクションの種類ごとに1つのバリエーションがあります。
Action::Add
またはのString
ように"buy milk"
、追加されるタスクを説明するを保持します"take the dog on a walk"
。Action::Done
完了としてマークするタスクの番号を保持します。たとえば、a2
は、番号付きのToDoリストの2番目のタスクを取り消します。Action::List
ターミナルにタスクリストを出力します。次に、CommandLineArgs
構造体はAction
列挙型をラッパーとして保持します。また、。Option
という名前のオプションの引数(タイプに注意)も保持しますjournal_file
。この引数は、ユーザーがデフォルトではないジャーナルファイルをポイントしたい場合に使用します。
action
とjournal_file
型を一緒にラップするとjournal_file
、Action
列挙型で宣言されたすべてのネストされたサブコマンドにオプションの引数を適用できます。
StructOpt
これらのタイプは、structopt
属性を使用して注釈を付けるまでは役に立ちません。最終的なソースコードは次のようになります。
use std::path::PathBuf;
use structopt::StructOpt;
#[derive(Debug, StructOpt)]
pub enum Action {
/// Write tasks to the journal file.
Add {
/// The task description text.
#[structopt()]
text: String,
},
/// Remove an entry from the journal file by position.
Done {
#[structopt()]
position: usize,
},
/// List all tasks in the journal file.
List,
}
#[derive(Debug, StructOpt)]
#[structopt(
name = "Rusty Journal",
about = "A command line to-do app written in Rust"
)]
pub struct CommandLineArgs {
#[structopt(subcommand)]
pub action: Action,
/// Use a different journal file.
#[structopt(parse(from_os_str), short, long)]
pub journal_file: Option<PathBuf>,
}
cli.rs
ファイルの最終バージョンでは#[derive(StructOpt)]
、いくつかの #[structopt]
属性を使用して、CommandLineArgs
構造体を使用してコマンドライン引数パーサーを生成するようにRustに指示しました 。ドキュメント文字列(///
)は、コマンドラインインターフェイスの各側面の説明を提供するために使用されます。
プログラムを試乗する時が来ました。ただし、最初に、main.rs
ソースファイルを次のように変更します。
mod cli;
use structopt::StructOpt;
fn main() {
cli::CommandLineArgs::from_args();
}
cargo run
コマンドを使用すると、構造体structopt
から生成されたヘルプメッセージが表示されますCommandLineArgs
。印象的ですね。
$ cargo run
Finished dev [unoptimized + debuginfo] target(s) in 0.04s
Running `target/debug/rusty-journal`
Rust Journal 0.1.0
A command line to-do app written in Rust
USAGE:
rusty-journal [OPTIONS] <SUBCOMMAND>
FLAGS:
-h, --help Prints help information
-V, --version Prints version information
OPTIONS:
-j, --journal-file <journal-file> Use a different journal file
SUBCOMMANDS:
add Write tasks to the journal file
done Remove an entry from the journal file by position
help Prints this message or the help of the given subcommand(s)
list List all tasks in the journal file
サブコマンドが間違った引数で呼び出された場合でも、プログラムはエラーを生成します。試してみる!
structopt
引数パーサーとして使用するポイントは、コマンドラインインターフェイスを有効に呼び出すたびにCommandLineArgs
値が生成されることです。プログラムでこれらの値を使用して、ユーザーが望む特定の動作を呼び出すことができます。
アプリのいくつかの異なる使用がどのように構造体の異なる値をもたらすかを見てください。まず、main.rs
ファイルを変更して、の結果を出力しfrom_args()
ます。次に、さまざまな引数を使用してプログラムを呼び出してみてください。
mod cli;
use structopt::StructOpt;
fn main() {
println!("{:#?}", cli::CommandLineArgs::from_args());
}
それぞれの異なる呼び出しが構造体の異なる値をどのようにインスタンス化するかに注意してください。
// $ cargo run -- add "buy milk"
CommandLineArgs {
action: Add {
text: "buy milk",
},
journal_file: None,
}
// $ cargo run -- done 4
CommandLineArgs {
action: Done {
position: 4,
},
journal_file: None,
}
// $ cargo run -- -j groceries.txt list
CommandLineArgs {
action: List,
journal_file: Some(
"groceries.txt",
),
}
これで、main.rs
ファイル内のこれらの値を使用して、プログラムの実行をガイドできます。
次に、tasks
モジュールファイルを見てみましょう。
タスクモジュールを作成する
このtasks
モジュールは、タスクと、それらを保存してアクセスする方法を表します。
srcディレクトリにtasks.rs
。という名前の新しいファイルを作成します。そのファイル内で、プログラムでToDoアイテムがどのように表示されるかを表す単純な構造体を定義することから始めます。
use chrono::{DateTime, Utc};
#[derive(Debug)]
pub struct Task {
pub text: String,
pub created_at: DateTime<Utc>,
}
構造体には2つのフィールドがあります。
text
のようなタスクの説明を格納します"pay the bills"
。created_at
タスクの作成のタイムスタンプを格納します。やることリストをタスクのベクトルとして表すため、status
またはis_complete
フィールドは追加しません(Vec<Task>
)。したがって、タスクが完了したら、それをベクターから簡単に削除できます。
サードパーティのクレートを使用していることに気付いたかもしれませんchrono
。構造体のUtc
パラメーターを指定しましたDateTime
。chrono
Rustで日付と時刻のデータを処理する必要がある場合に使用するのに適したクレートです。瞬間を表すための簡単なAPIを提供します。
これを使用しているため、Cargo.toml
ファイルで宣言する必要があります。
[dependencies]
structopt = "0.3"
chrono = "0.4"
次のステップは、新しいタスクをインスタンス化するためのメソッドを実装することです。タスクには、常に現在の日付と時刻のタイムスタンプが付けられます。Task
構造体の後に次のコードを追加します。
impl Task {
pub fn new(text: String) -> Task {
let created_at: DateTime<Utc> = Utc::now();
Task { text, created_at }
}
}
このコードはTask::new
関数を定義します。この関数には、タスクの説明のみが必要です。Utc::now()
メソッドを使用して現在のタイムスタンプをキャプチャします。
タスク構造体が完成したようです。それでは、このモジュールの次の項目である永続性に取り組みましょう 。
ToDoリストをタスクのベクトルとして表すため、JSONファイルを使用してデータを永続化することが簡単にできます。それを達成するための最善の行動は、Rustエコシステムからの別の優れたクレートを使用することですserde_json
。
serde_json
続行する前に、Rustでのエンコードとデコードに関するいくつかの推奨プラクティスについて説明する必要があります。
構造体と列挙型インスタンスを永続化する必要がある場合は、シリアル化について考える必要があります。そのデータをプログラムに戻す必要があるときは、逆シリアル化について話します。
シリアル化と逆シリアル化は、データをバイトストリームに格納し、情報を失うことなく後で使用するためにデータを取得するプロセスです。次に、これらのバイトを接続を介して送信したり、ストレージデバイスのファイルに保存したりできます。このOWASPチートシートから、シリアル化と逆シリアル化の詳細を学ぶことができます。
Rustコミュニティは、serde
Rustデータ構造のほとんどのシリアル化と逆シリアル化を効率的かつ一般的に処理するためのクレートを推奨しています。この既存のクレートを使用することで、生産性と慣用性をさらに高めることができます。
Task
タイプのシリアル化を開始するには、2つのクレートが必要です。
serde
。私たちのタイプがSerialize
とDeserialize
特性を導き出すことを可能にするベースクレート。serde_json
。これらの特性を、選択したファイル仕様形式であるJSONに実装するクレート。いつものように、最初のステップは、含ませることであるserde_json
とserde
して[dependencies]
、私たちのセクションCargo.toml
ファイル。今回は、いくつかのserde
機能を条件付きでコンパイルする必要があるため、別の表記法を使用してそれらを指定します。これで、ファイルは次のようになります。
[dependencies]
structopt = "0.3"
serde_json = "1.0" # Add serde_json.
[dependencies.serde] # Add serde in its own section.
version = "1.0"
features = ["derive"] # We'll need the derive feature.
[dependencies.chrono]
version = "0.4"
features = ["serde"] # We're also going to need the serde feature for the chrono crate, so we can serialize the DateTime field.
これでTask
、からの新機能を使用するように構造体を適応させることができるはずですserde
。tasks.rs
ファイルを開き、構造体を次のように変更します。
use chrono::{serde::ts_seconds, DateTime, Local, Utc};
use serde::Deserialize;
use serde::Serialize;
#[derive(Debug, Deserialize, Serialize)]
pub struct Task {
pub text: String,
#[serde(with = "ts_seconds")]
pub created_at: DateTime<Utc>,
}
違いに注意してください。
Deserialize
とを追加しましたSerialize
。created_at
フィールドに注釈を付け、ts_seconds
から属性に渡しchrono
て、そのタイプが2つの新しい特性をどのように実装するかを通知できるようにしました。serde(with = ...)chronoserdeDatetime
このTask
型でシリアル化と逆シリアル化の両方を実行できるようになったので、次に進んでファイル処理関数を実装できます。
プログラムが実行する必要のある3種類のアクションを確認しましょう。
モジュールインターフェイスはそのリストと同じくらい単純である必要があるため、アクションごとに1つずつ、合計3つの関数を使用します。
use std::io::Result;
use std::path::PathBuf;
pub fn add_task(journal_path: PathBuf, task: Task) -> Result<()> { ... }
pub fn complete_task(journal_path: PathBuf, task_position: usize) -> Result<()> { ... }
pub fn list_tasks(journal_path: PathBuf) -> Result<()> { ... }
まず、各関数のシグネチャを見てください。それらすべてにjournal_path: PathBuf
引数が必要であることに注意してください。これは、すべてのユーザーが作業を完了するためにファイルパス(タスクが保存されるファイルへのパス)が必要なためです。
add_task
また、Task
引数が必要です。その引数は、リストに追加されるタスクを指定します。complete_tasktask_position
どちらTask
を削除するかを示す引数が必要です。タスクが削除されると、それは完了したことを意味します。list_tasks
追加情報は必要ありません。ジャーナルファイルに現在保存されているすべてのタスクをきれいな形式でユーザーに表示するだけです。関数はすべて同じ戻りタイプを持っています:std::io::Result<()>
。この形式は、戻りタイプがI / O結果であることを示します。このリターンタイプは、物理的な単語でデータを処理するときに発生する可能性のある、さまざまな望ましくない結果が予想されることを示しています。Ok
変異体は、ちょうど空のタプルであり、()
一般的に全くデータに関連付けられているタイプです。その唯一の目的は、関数がを返し、Ok
エラーが発生しなかったことを通知することです。
次の3つのユニットでは、各関数の内容を詳しく説明します。
タスクを追加する関数を書く
このadd_task
関数Task
は、JSONファイルにエンコードされている可能性のある既存のタスクのコレクションに新しい値を追加する必要があります。
したがって、そのコレクションにタスクを挿入する前に、まずそのファイルを読み取り、その内容からタスクのベクトルをアセンブルする必要があります。
最初のバージョンは次のようになります。
use std::fs::OpenOptions;
use std::io::{BufReader, Result, Seek, SeekFrom};
// ...
pub fn add_task(journal_path: PathBuf, task: Task) -> Result<()> {
// Open the file.
let mut file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open(journal_path)?;
// Consume the file's contents as a vector of tasks.
let mut tasks: Vec<Task> = match serde_json::from_reader(&file) {
Ok(tasks) => tasks,
Err(e) if e.is_eof() => Vec::new(),
Err(e) => Err(e)?,
};
// Rewind the file after reading from it.
file.seek(SeekFrom::Start(0))?;
// Write the modified task list back into the file.
tasks.push(task);
serde_json::to_writer(file, &tasks)?;
Ok(())
}
この関数を4つのステップで見ていきましょう。
まず、使用してファイルを開くOpenOptions
、私たちのような、ファイルを操作するためのいくつかのモードを指定することを可能にする、read
、write
、およびcreate
(ファイルがまだ存在していないときのために)。
?
そのステートメントの後の疑問符記号()は、ボイラープレートコードをあまり記述せずにエラーを伝播するために使用されます。エラーが含まれている関数の戻りタイプと一致する場合、エラーを早期に返すためのシンタックスシュガーです。したがって、これらのスニペットは同等です。
fn function_1() -> Result(Success, Failure) {
match operation_that_might_fail() {
Ok(success) => success,
Err(failure) => return Err(failure),
}
}
fn function_2() -> Result(Success, Failure) {
operation_that_might_fail()?
}
このパターンは、このプログラムで行うように、複数のI / O操作を実行する必要があるコードで多く使用されます。
2番目のステップは、実際にファイルを読み取ることです。ファイルを読み取るserde_json
ために、Reader
トレイトを実装するタイプを要求します。File
形質は、私たちはちょうどにパラメータとして渡すことタイプの実装serde_json.from_reader
我々が受け取ることを期待することを宣言しながら、機能Vec<Task>
、それから。
ファイルシステムへのアクセスはI / Oアクションであり、さまざまな理由で失敗する可能性があることに注意してください。したがって、特定の場合にプログラムがどのように動作するか(場合によっては回復するか)を検討する必要があります。たとえば、serde_json
解析するものが見つからずにファイルの終わりに達すると、エラーが返されます。このイベントは常に空のファイルで発生するため、回復できる必要があります。
特定の種類のエラーから回復するためguards
に、match
式で使用してVec
、特定のエラーが発生したときに空を作成します。Vec
空のto-doリストを表します。
トレイトを実装しているためserde_json::Error
、std::io::Error
タイプに簡単に変換できることに注意してください。これにより、オペレーターを使用して開梱したり、早期に返品したりすることができます。From
?
カーソルをファイルの最後に移動したため、再度書き込む前にファイルを巻き戻す必要があります。ファイルを巻き戻さないと、カーソルの最後の位置から書き込みが開始され、JSONファイルの形式が正しくなくなります。モジュールのSeek
トレイトとSeekFrom
列挙型を使用しstd::io
てファイルを巻き戻します。
最後に、Task
関数パラメーターとして受け取った値をタスクリストにプッシュし、を使用serde_json
してタスクベクトルをファイルに書き込みます。次に、内の空のタプル値を返し、Ok
すべてが計画どおりに進んだことを示します。
タスクを完了する関数を書く
このcomplete_task
関数は、ファイルに保存されているToDoリストからタスクを削除しようとします。関数は次のアクションを完了する必要があります。
complete_task
関数の最初の実装は、コードに従うようになります。ただし、コードの重複の兆候はすでに見られるため、リファクタリングを行う必要があります。
use std::io::{Error, ErrorKind, Result, Seek, SeekFrom}; // Include the `Error` type.
pub fn complete_task(journal_path: PathBuf, task_position: usize) -> Result<()> {
// Open the file.
let file = OpenOptions::new()
.read(true)
.write(true)
.open(journal_path)?;
// Consume the file's contents as a vector of tasks.
let tasks = match serde_json::from_reader(file) {
Ok(tasks) => tasks,
Err(e) if e.is_eof() => Vec::new(),
Err(e) => Err(e)?,
};
// Remove the task.
if task_position == 0 || task_position > tasks.len() {
return Err(Error::new(ErrorKind::InvalidInput, "Invalid Task ID"));
}
tasks.remove(task_position - 1);
// Rewind and truncate the file.
file.seek(SeekFrom::Start(0))?;
file.set_len(0)?;
// Write the modified task list back into the file.
serde_json::to_writer(file, &tasks)?;
Ok(())
}
この関数の記述を開始する前に、add_task
関数で使用したファイルを読み取るための同じコードがここで必要であることがわかります。また、list_tasks
関数を実装するときにも必要になります。この複製の必要性は、コードをリファクタリングし、その動作を専用の関数にカプセル化する必要があることを示しています。その後、3つのアクションすべてのロジックでコードを再利用できます。
Task
コレクションをリファクタリングするcollect_tasks
ファイルの解析を処理する関数を作成できます。
fn collect_tasks(mut file: &File) -> Result<Vec<Task>> {
file.seek(SeekFrom::Start(0))?; // Rewind the file before.
let tasks = match serde_json::from_reader(file) {
Ok(tasks) => tasks,
Err(e) if e.is_eof() => Vec::new(),
Err(e) => Err(e)?,
};
file.seek(SeekFrom::Start(0))?; // Rewind the file after.
Ok(tasks)
}
この関数は、への参照を受け取り、File
を返しますstd::io::Result<Vec<Task>>
。それはそれio::Error
が起こることを期待していることを意味します。ボーナスとして、ファイルの内容を読み取る前、および呼び出し元に戻す前に、ファイルを巻き戻します。
これでadd_task
、新しい関数を使用するように関数をリファクタリングできます。
pub fn add_task(journal_path: PathBuf, task: Task) -> Result<()> {
let file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open(journal_path)?;
let mut tasks = collect_tasks(&file)?;
tasks.push(task);
serde_json::to_writer(file, &tasks)?;
Ok(())
}
このリファクタリングの後、add-task
見た目がはるかに良くなり、読みやすくなります。
complete_task
これで、complete_task
関数でリファクタリングされたコードを最終的に使用できるようになりました。
pub fn complete_task(journal_path: PathBuf, task_position: usize) -> Result<()> {
// Open the file.
let file = OpenOptions::new()
.read(true)
.write(true)
.open(journal_path)?;
// Consume file's contents as a vector of tasks.
let mut tasks = collect_tasks(&file)?;
// Try to remove the task.
if task_position == 0 || task_position > tasks.len() {
return Err(Error::new(ErrorKind::InvalidInput, "Invalid Task ID"));
}
tasks.remove(task_position - 1);
// Write the modified task list back into the file.
file.set_len(0)?;
serde_json::to_writer(file, &tasks)?;
Ok(())
}
最初の部分と2番目の部分、および4番目の部分のいくつかは、いくつかadd_task
の例外を除いて、関数で行ったのと同じことを行っています。
file.set_len(0)
操作を使用してファイルを切り捨てるときは、空白のページにバイトを書き込んでいることを確認します。3番目のセクションでは、タスクの位置を指定して、ベクトルからタスクを削除します。ポジションが有効でない場合io::Error
は、問題を説明するカスタムメイドで早めに戻ります。
タスクを出力する関数を書く
定義する必要がある3番目で最後のアクションはlist_tasks
関数です。必要なのは、ジャーナルファイルを読み取り、タスクのリストがある場合はそれを印刷することだけです。
pub fn list_tasks(journal_path: PathBuf) -> Result<()> {
// Open the file.
let file = OpenOptions::new().read(true).open(journal_path)?;
// Parse the file and collect the tasks.
let tasks = collect_tasks(&file)?;
// Enumerate and display tasks, if any.
if tasks.is_empty() {
println!("Task list is empty!");
} else {
let mut order: u32 = 1;
for task in tasks {
println!("{}: {}", order, task);
order += 1;
}
}
Ok(())
}
この関数は、ファイルに書き込む必要がないため、兄弟よりも少し単純です。collect_tasks
ヘルパー関数を再利用して、リファクタリングの有用性を証明します。次に、タスクベクトルの内容を一覧表示する前に、タスクベクトルが空かどうかを確認します。
リストを印刷するときは、で始まる単純なカウンターを使用し1
てタスクを列挙します。この番号は、ユーザーがcomplete_task
アクションに渡す番号と同じになります。
Task
構造体がまだDisplay
トレイトを実装していないため、このコードはコンパイルされないことに注意してください。他のモジュールで見たように、Display
トレイトはエンドユーザーに構造体表現を表示するために使用されます。これはまさにここで行っていることです。
Display
私たちのタイプの特性を実装するのは簡単です。私たちがする必要があるのはfmt
、次のような関数を実装することだけです。
use std::fmt;
impl fmt::Display for Task {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let created_at = self.created_at.with_timezone(&Local).format("%F %H:%M");
write!(f, "{:<50} [{}]", self.text, created_at)
}
}
このDisplay::fmt
関数では、DateTime<Utc>
タイムスタンプをDateTime<Local>
構造体に変換して、ユーザーがタスクが作成された日時を現地時間で確認できるようにします。
最初から型created_at
を使用してフィールドを定義しなかったのはなぜか疑問に思われるかもしれませんDateTime<Local>
。chrono::serde::ts_seconds
モジュールはDateTime
構造体がUtc
型に特化していることを期待しているため、これは行いませんでした。
次に、マクロを使用してTask
表現をFormatter
値に書き込みます。タイプは次のように表されます。fwrite!Task
{:<50}
:50個のスペースが埋め込まれた左揃えの文字列。[{}]
:タスクが作成された日時(括弧内)。これで、tasks.rs
モジュールファイルへの道のりは終わりです。タスクモジュールの完全なコードを確認したい場合は、RustPlaygroundで確認してください。最後のステップは、によってキャプチャされたユーザー入力をcli::CommandLineArgs
、このモジュールで定義されている3つの関数にバインドすることです。
次のセクションでは、main.rs
ファイル内のこれらの端を接続して、アプリケーションを完成させます
メインモジュールを完了する
プログラムがユーザーインタラクション(cli
モジュールを使用)とファイル処理(tasks
モジュールを使用)を処理できるようになったので、すべてが期待どおりに機能するかどうかを確認するために試してみることができます。
デフォルトのジャーナルファイルを使用したり、ユーザーにわかりやすいエラーを表示したりするなど、まだいくつかのことを磨く必要がありますが、後でそれらのタスクに対処しましょう。
次に行うべきことは、Actions
構造体をtasks
モジュールで定義された3つのパブリック関数に接続することです。main.rs
ファイルを開き、次のようにします。
use structopt::StructOpt;
mod cli;
mod tasks;
use cli::{Action::*, CommandLineArgs};
use tasks::Task;
fn main() {
// Get the command-line arguments.
let CommandLineArgs {
action,
journal_file,
} = CommandLineArgs::from_args();
// Unpack the journal file.
let journal_file = journal_file.expect("Failed to find journal file");
// Perform the action.
match action {
Add { text } => tasks::add_task(journal_file, Task::new(text)),
List => tasks::list_tasks(journal_file),
Done { position } => tasks::complete_task(journal_file, position),
}
.expect("Failed to perform action")
}
私たちのmain.rs
アウトラインは本当にシンプルに見えます。
私たちはすることから始め構造化代入たちのCommandLineArgs
私たちは私たちのタスク処理関数に独立して、それらの値を渡すことができますので、そのフィールドに構造体を。
journal_file
はタイプOption<PathBuf>
であるため、ジャーナルファイルへのパスを抽出するか、を発行する必要がありpanic
ます。プログラムにデフォルトファイルを検索させるために、後でこの手順を再検討します。今のところ、この.expect
命令は正常に機能します。
最後に、可能なものAction
をそれぞれその関数に一致させ、列挙型から関数に必要なフィールドを渡します。すべての関数が型を返すため.expect
、match
ブロックの最後で呼び出しますが、Result
失敗する可能性があります。繰り返しになりますが、失敗した場合にユーザーに適切なエラーメッセージを提供するために、この機能を後で磨きます。
やるだけやってみよう。ターミナルを開き、次のコマンドを入力します。
$ cargo run -- -j test-journal.json add "buy milk"
$ cargo run -- -j test-journal.json add "take the dog for a walk"
$ cargo run -- -j test-journal.json add "water the plants"
$ cargo run -- -j test-journal.json list
1: buy milk [2021-01-08 16:39]
2: take the dog for a walk [2021-01-08 16:39]
3: water the plants [2021-01-08 16:39]
$ cargo run -- -j test-journal.json done 2
$ cargo run -- -j test-journal.json list
1: buy milk [2021-01-08 16:39]
2: water the plants [2021-01-08 16:39]
プログラムは正常に実行されているようです。
私たちは、cargo run --
後--
に渡されたすべての引数がプログラムcargo
自体ではなくプログラムに送信されることを確認するために呼び出すことから始めました。
次に、サブコマンドのadd
後にタスク名の文字列を使用して、3つのタスクを続けて追加しました。次に、list
サブコマンドは3つのタスクを順番に表示し、タイムスタンプを右端に表示しました。次に、done 2
サブコマンドを呼び出して、2番目のタスクが完了したことを示します。list
もう一度電話すると、そのタスクはなくなりました。かなりすごいですね。
test-journal.json
ファイルを覗くと、次の内容が表示されます。
[{"text":"buy milk","created_at":1610134741},{"text":"water the plants","created_at":1610134762}]
それぞれTask
がJSONオブジェクトとして表されており、各ファイルが1つのキーであることがわかります。タスクの説明は文字列として保存され、タイムスタンプはエポックからの秒数として保持されます。
JSONファイルをきれいに印刷すると、次のようになります。
[
{
"text": "buy milk",
"created_at": 1610134741
},
{
"text": "water the plants",
"created_at": 1610134762
}
]
次の2つのセクションでは、デフォルトのジャーナルファイルを使用するようにプログラムを構成し、よりきれいなエラーメッセージを表示することで、プログラムの使いやすさを向上させます。
デフォルトのジャーナルファイルを使用してタスクを保存する
一部のコマンドラインアプリケーションでは、ドットファイルや構成ファイルなどのユーザー所有のファイルをホームディレクトリに配置するのが一般的です。したがって、デフォルトのジャーナルファイルもそこに配置することをお勧めします。
ホームディレクトリはユーザーのオペレーティングシステムによって異なるためhome
、ディレクトリを決定するために呼び出されるサードパーティのクレートに依存します。
まず、それをCargo.toml
ファイルに追加します。
[dependencies]
home = "0.5" # <--- Add `home` to our project dependencies.
serde_json = "1.0"
structopt = "0.3"
[dependencies.chrono]
features = ["serde"]
version = "0.4"
[dependencies.serde]
features = ["derive"]
version = "1.0"
これでmain.rs
、home::home_dir()
関数を使用するようにファイルを更新できます。この関数は、ユーザーのホームディレクトリを検索し、Option<PathBuf>
タイプのjournal_file
フィールドと同じように、タイプで返しますCommandLineArgs
。
// ...
use std::path::PathBuf;
fn find_default_journal_file() -> Option<PathBuf> {
home::home_dir().map(|mut path| {
path.push(".rusty-journal.json");
path
})
}
fn main() {
let CommandLineArgs {
action,
journal_file,
} = CommandLineArgs::from_args();
let journal_file = journal_file
.or_else(find_default_journal_file)
.expect("Failed to find journal file.");
// ...
}
という新しい関数を作成しましたfind_default_journal_file
。入力引数を受け取らず、を返しますOption<PathBuf>
。
その関数内で、デフォルトのジャーナルファイルへのフルパスを作成しようとします。関数の出力Option
から型を取得し 、文字列をパスにプッシュする無名関数を使用してhome::home_dir
そのmap
メソッドを呼び出すことにより".rusty-journal.json"
、パスを作成します。出力がいる場合home::home_dir
であるNone
ので、何の行動は、取られないmap
でのみ動作しますSome
バリアント。
次に、main
関数で、元の値がであった場合にのみ、呼び出しで更新される変数をシャドウイングしjournal_file
ます。このメソッドは、メソッドの逆を実行します。バリアントがである場合にのみ、保持している関数を呼び出します。find_default_journal_fileNone.or_elsemapNone
ユーザーがターゲットジャーナルファイルを提供してfind_default_journal_file
おらず、適切なファイルが見つからない場合、ジャーナルファイルなしでは何も実行できないため、プログラムはパニックに陥ります。
わかりやすいエラーメッセージを表示する
現在、存在しないジャーナルファイルから読み取ろうとすると、プログラムは次の出力でパニックになります。
$ cargo run -- done 2
thread 'main' panicked at 'Failed to perform action: Os { code: 2, kind: NotFound, message: "No such file or directory" }'
このエラーはユーザーにとって少し冗長なので、見栄えを良くする必要があります。そのタスクを処理するために多くのコードを書くことができますが、ユーザーに有用でかなりのエラーを表示するための優れたクレートがあります。それはと呼ばれanyhow
ます。
anyhow
クレートの背後にあるロジックは、独自のエラータイプを提供することです。このタイプにはきれいな印刷プロパティがあり、などの他のエラーから簡単に変換できます std::io::Error
。anyhow
プロジェクトに追加するのは簡単です。main
関数の戻り型として配置するだけです。
まず、Cargo.toml
ファイルで宣言します。
[dependencies]
anyhow = "1.0" # <--- Add `anyhow` to our project dependencies.
home = "0.5"
serde_json = "1.0"
structopt = "0.3"
[dependencies.chrono]
features = ["serde"]
version = "0.4"
[dependencies.serde]
features = ["derive"]
version = "1.0"
次に、main
関数のシグネチャを更新して、タイプを返すようにしますanyhow::Result<()>
。
use anyhow::anyhow;
use std::path::PathBuf;
use structopt::StructOpt;
mod cli;
mod tasks;
use cli::{Action::*, CommandLineArgs};
use tasks::Task;
fn find_default_journal_file() -> Option<PathBuf> {
home::home_dir().map(|mut path| {
path.push(".rust-journal.json");
path
})
}
fn main() -> anyhow::Result<()> {
let CommandLineArgs {
action,
journal_file,
} = CommandLineArgs::from_args();
let journal_file = journal_file
.or_else(find_default_journal_file)
.ok_or(anyhow!("Failed to find journal file."))?;
match action {
Add { text } => tasks::add_task(journal_file, Task::new(text)),
List => tasks::list_tasks(journal_file),
Done { position } => tasks::complete_task(journal_file, position),
}?;
Ok(())
}
ほとんどのエラータイプはに変換anyhow::Error
できるため、?
構文を使用expect
してコードから呼び出しを削除できます。また、anyhow!
マクロを使用anyhow::Error
して、提供されたエラーメッセージを含むオンザフライを生成していることに注意してください。
これで、プログラム内から返されるI / Oエラーによって引き起こされるすべてのパニックメッセージが、次のようにユーザーに表示されます。
$ cargo run -- -j missing-journal done 2
Error: No such file or directory (os error 2)
これは、数行のコードを追加することでかなり改善されます。
概要
おめでとう!最新のRustコマンドラインアプリケーションを作成しました。ジャーナルアプリケーションを使用すると、リストに新しいタスクを追加したり、完了したタスクを削除したり、未処理のタスクをすべて印刷したりできます。
これで、プログラムをリリースモードでコンパイルし、コンパイルしたプログラムを世界と共有することができます。プログラムをコンパイルするには、ターミナルに移動してコマンドを実行しcargo run --release
ます。
コンパイルされたバイナリ(実行可能ファイル)はtarget/release/
ディレクトリにあり、プロジェクト名にちなんで名前が付けられます。macOSまたはLinuxを使用している場合は、と呼ばれrusty-journal
ます。Windowsを使用している場合は、と呼ばれrusty-journal.exe
ます。
コマンドラインから直接呼び出すことができます。もう貨物は必要ありません!そのディレクトリがPATH
環境変数にリストされていることを確認してください。
リンク: https://docs.microsoft.com/en-us/learn/modules/rust-create-command-line-program/