19 4月 2025

Rustチュートリアル:初心者ガイド

Rustは常に開発者の関心を集めており、Stack Overflowの調査で複数年連続で「最も愛されている」プログラミング言語の称号を獲得しています。これは単なる誇大広告ではありません。Rustは、他のシステムプログラミング言語に見られる一般的な問題点に対処する、パフォーマンス、安全性、そして現代的な言語機能の魅力的な組み合わせを提供します。Rustが特別な理由に興味があり、その旅を始めたいと考えているなら、この初心者向けガイドは、あなたが始めるための基礎知識を提供します。コアとなる構文、所有権のようなユニークな概念、そしてRustエコシステムを支える必須のツールを探求します。

なぜRustプログラミングを学ぶのか?

Rustは、信頼性が高く効率的なソフトウェアを構築するための言語として位置づけられています。その主な利点は、ガベージコレクタに頼ることなくメモリ安全性を実現し、恐れることのない並行性を可能にすることにあります。Rustを学ぶべき理由は以下の通りです:

  1. パフォーマンス: Rustはネイティブのマシンコードに直接コンパイルされ、CやC++に匹敵するパフォーマンスを提供します。この速度を安全性を犠牲にすることなく達成しており、ゲームエンジン、オペレーティングシステム、ブラウザコンポーネント、高性能ウェブサービスのようなパフォーマンスが重要なアプリケーションに適しています。
  2. メモリ安全性: Rustの代表的な機能は、借用ライフタイムによって補完される所有権システムです。このシステムは、コンパイル時にメモリ安全性を保証します。C/C++のような言語でしばしば問題となるダングリングポインタ、バッファオーバーフロー、データ競合を忘れてください。Rustコンパイラは厳格な門番として機能し、コードが実行される前にこれらの一般的なエラーを防ぎます。
  3. 並行性: 並行プログラミング(複数のタスクが見かけ上同時に実行されること)は、正しく行うのが非常に難しいことで知られています。Rustはこの問題に正面から取り組みます。所有権と型システムが連携して、コンパイル時にデータ競合を防ぎ、マルチスレッドアプリケーションを記述することを大幅に容易かつ安全にします。この「恐れることのない並行性」により、開発者は一般的な落とし穴なしに、現代のマルチコアプロセッサを効果的に活用できます。
  4. 現代的なツール: Rustには、コアエクスペリエンスに統合された優れたパッケージマネージャ兼ビルドツールであるCargoが付属しています。Cargoは、依存関係の管理、テスト、ビルド、そしてクレート(Rustでのパッケージやライブラリの用語)の公開をシームレスに処理し、開発ワークフロー全体を効率化します。
  5. 成長するエコシステムとコミュニティ: Rustコミュニティは、活気があり、アクティブで、初心者に対して歓迎的であることで知られています。ライブラリ(クレート)のエコシステムは急速に拡大しており、Web開発(Actix、Rocket、Axumなどのフレームワーク)、ネットワーキング(非同期操作のためのTokioなど)、組み込みシステム、データサイエンス、コマンドラインツールなど、多様な分野をカバーしています。

Rustプログラミングを学ぶ:必須ツール

最初のRustコードを書く前に、Rustツールチェインをセットアップする必要があります。Rustをインストールする標準的かつ推奨される方法は、Rustツールチェインインストーラーであるrustupを使用することです。

  1. rustup: このコマンドラインツールはRustのインストールを管理します。これにより、異なるRustバージョン(stable、beta、nightlyビルドなど)をインストール、更新、そして簡単に切り替えることができます。お使いのオペレーティングシステム固有のインストール手順については、公式Rustウェブサイト(https://www.rust-lang.org/tools/install)をご覧ください。
  2. rustc: これはRustコンパイラです。.rsファイルにRustソースコードを書いた後、rustcはそれらをコンピュータが理解できる実行可能バイナリやライブラリにコンパイルします。重要ではありますが、日常のワークフローでrustcを直接呼び出すことはあまりありません。
  3. cargo: これはRustのビルドシステム兼パッケージマネージャであり、最も頻繁にやり取りするツールです。Cargoは多くの一般的な開発タスクを調整します:
    • 新しいプロジェクトの作成(cargo new)。
    • プロジェクトのビルド(cargo build)。
    • プロジェクトの実行(cargo run)。
    • 自動テストの実行(cargo test)。
    • 依存関係の管理(Cargo.tomlファイルにリストされた外部ライブラリ、すなわちクレートを自動的に取得してコンパイルする)。
    • 他の人が使用できるように、独自のクレートをcrates.io(中央Rustパッケージレジストリ)に公開する。

ローカルインストールなしで素早く実験したい場合は、公式のRust PlaygroundのようなオンラインプラットフォームやReplitのような統合開発環境が優れた選択肢です。

最初のRustプログラム: Hello, Rust!

新しい言語を学ぶ際の通過儀礼である、伝統的な「Hello, world!」プログラムから始めましょう。main.rsという名前のファイルを作成し、次のコードを追加します:

fn main() {
    // この行はコンソールにテキストを出力します
    println!("Hello, Rust!");
}

基本的なツールを使ってこの単純なプログラムをコンパイルして実行するには:

  1. コンパイル: ターミナルまたはコマンドプロンプトを開き、main.rsを含むディレクトリに移動して、次を実行します:
    rustc main.rs
    
    このコマンドはRustコンパイラ(rustc)を呼び出し、実行可能ファイル(Linux/macOSではmain、Windowsではmain.exeなど)を作成します。
  2. 実行: ターミナルからコンパイルされたプログラムを実行します:
    ./main
    # Windowsの場合: .\main.exe を使用
    

しかし、単一ファイルを超えるものについては、Cargoを使用するのが標準的で、はるかに便利なアプローチです:

  1. 新しいCargoプロジェクトを作成: ターミナルで次を実行します:
    cargo new hello_rust
    cd hello_rust
    
    Cargoはhello_rustという名前の新しいディレクトリを作成し、その中にsrcサブディレクトリ(すでに「Hello, Rust!」コードが入力されたmain.rsを含む)とCargo.tomlという名前の設定ファイルが含まれます。
  2. Cargoで実行: 単に次を実行します:
    cargo run
    
    Cargoはコンパイルステップを処理し、その後、結果のプログラムを実行して、コンソールに「Hello, Rust!」と表示します。

コードスニペットを分解してみましょう:

  • fn main(): これはmain関数を定義します。fnキーワードは関数宣言を示します。mainは特別な関数名であり、すべての実行可能なRustプログラムが実行を開始するエントリーポイントです。括弧()は、この関数が入力パラメータを取らないことを示します。
  • {}: 波括弧はコードブロックまたはスコープを定義します。関数に属するすべてのコードは、これらの括弧内に入ります。
  • println!("Hello, Rust!");: この行はコンソールにテキストを出力するアクションを実行します。
    • println!はRustのマクロです。マクロは関数に似ていますが、重要な違いがあります:感嘆符!で終わります。マクロはコンパイル時のコード生成を実行し、通常の関数よりも強力で柔軟性を提供します(println!が行うような可変数の引数の処理など)。println!マクロは提供されたテキストをコンソールに出力し、最後に自動的に改行文字を追加します。
    • "Hello, Rust!"文字列リテラルです – テキストを表す固定された文字シーケンスで、二重引用符で囲まれています。
    • ;: セミコロンは文の終わりを示します。実行可能なRustコードのほとんどの行(文)はセミコロンで終わります。

初心者のためのRustチュートリアル:主要な概念

それでは、Rustプログラミング言語の基本的な構成要素について詳しく見ていきましょう。

変数と可変性

変数はデータ値を格納するために使用されます。Rustでは、letキーワードを使用して変数を宣言します。

let apples = 5;
let message = "Take five";

Rustの核心的な概念は、変数がデフォルトで不変であるということです。これは、一度値が変数名に束縛されると、後でその値を変更できないことを意味します。

let x = 10;
// x = 15; // この行はコンパイル時エラーを引き起こします!不変変数 `x` には二度と代入できません。
println!("The value of x is: {}", x); // {} は x の値のプレースホルダーです

このデフォルトの不変性は、データの偶発的な変更を防ぐことで、より安全で予測可能なコードを書くのに役立つ意図的な設計上の選択です。これはバグの一般的な原因となり得ます。値が変更可能な変数が必要な場合は、宣言時にmutキーワードを使用して明示的に可変としてマークする必要があります。

let mut count = 0; // 'count'を可変として宣言
println!("Initial count: {}", count);
count = 1; // 'count'が'mut'で宣言されたため、これは許可されます
println!("New count: {}", count);

Rustはシャドーイングも許可します。同じスコープ内で以前の変数と同じ名前の新しい変数を宣言できます。新しい変数は古い変数を「シャドーイング」し、その名前の後続の使用は新しい変数を参照することを意味します。これは、完全に新しい変数を作成しているため、ミューテーションとは異なります。新しい変数は異なる型を持つことさえ可能です。

let spaces = "   "; // 'spaces' は最初は文字列スライス (&str)
let spaces = spaces.len(); // 'spaces' は長さを保持する新しい変数(整数、usize)によってシャドーイングされた
println!("Number of spaces: {}", spaces); // 整数値を出力

基本データ型

Rustは静的型付け言語です。これは、すべての変数の型がコンパイル時にコンパイラによって知られている必要があることを意味します。しかし、Rustには優れた型推論があります。多くの場合、明示的に型を書き出す必要はありません。コンパイラはしばしば値と使用方法に基づいて型を推測できます。

let quantity = 10;         // コンパイラは i32 (デフォルトの符号付き整数型) を推論
let price = 9.99;          // コンパイラは f64 (デフォルトの浮動小数点型) を推論
let active = true;         // コンパイラは bool (真偽値) を推論
let initial = 'R';         // コンパイラは char (文字) を推論

明示的にしたい場合や必要な場合(例:明確さのため、またはコンパイラが助けを必要とする場合)、コロン : に続けて型名を指定して型アノテーションを提供できます。

let score: i32 = 100;       // 明示的に符号付き32ビット整数
let ratio: f32 = 0.5;       // 明示的に単精度浮動小数点数
let is_complete: bool = false; // 明示的に真偽値
let grade: char = 'A';      // 明示的に文字 (Unicodeスカラー値)

Rustにはいくつかの組み込みスカラー型(単一の値を表す)があります:

  • 整数: 符号付き整数 (i8, i16, i32, i64, i128, isize) は正と負の両方の整数を格納します。符号なし整数 (u8, u16, u32, u64, u128, usize) は非負の整数のみを格納します。isizeusize型はコンピュータのアーキテクチャ(32ビットまたは64ビット)に依存し、主にコレクションのインデックス付けに使用されます。
  • 浮動小数点数: f32(単精度)とf64(倍精度)。デフォルトはf64です。
  • 真偽値: bool型にはtrueまたはfalseの2つの可能な値があります。
  • 文字: char型は単一のUnicodeスカラー値(単なるASCII文字よりも包括的)を表し、シングルクォートで囲まれます(例:'z', 'π', '🚀')。

文字列: String vs &str

Rustでテキストを扱う際には、しばしば2つの主要な型が関与し、初心者にとっては混乱を招くことがあります:

  1. &str(「文字列スライス」と発音):これは、メモリ内のどこかに格納されたUTF-8エンコードされたバイトシーケンスへの不変参照です。文字列リテラル("Hello"など)は&'static str型であり、プログラムのバイナリに直接格納され、プログラムの全期間中存在します。スライスは、データを所有することなく文字列データへのビューを提供します。サイズは固定です。
  2. String:これは、所有され成長可能で、可変な、UTF-8エンコードされた文字列型です。Stringデータはヒープに格納され、サイズ変更が可能です。通常、文字列データを変更する必要がある場合、または文字列が自身のデータを所有し、自身のライフタイムを管理する必要がある場合(関数から文字列を返す場合や構造体に格納する場合など)にStringを使用します。
// 文字列リテラル(プログラムバイナリに格納され、不変)
let static_slice: &'static str = "I am immutable";

// リテラルから所有されたヒープ割り当ての String を作成
let mut dynamic_string: String = String::from("Start");

// String を変更(可変であり、データを所有しているため可能)
dynamic_string.push_str(" and grow");
println!("{}", dynamic_string); // 出力: Start and grow

// String の一部を参照する文字列スライスを作成
// このスライスは dynamic_string からデータを借用する
let slice_from_string: &str = &dynamic_string[0..5]; // "Start" を参照
println!("Slice: {}", slice_from_string);

関数

関数は、コードを名前付きの再利用可能な単位に整理するための基本です。特別なmain関数には既に遭遇しました。

// 関数定義
fn greet(name: &str) { // 1つのパラメータ 'name' を取る。これは文字列スライス (&str)
    println!("Hello, {}!", name);
}

// 2つの i32 パラメータを取り、i32 を返す関数
fn add(a: i32, b: i32) -> i32 {
    // Rustでは、関数本体の最後の式は、
    // セミコロンで終わっていなければ自動的に返される。
    a + b
    // これは return a + b; と書くのと同じ。
}

fn main() {
    greet("Alice"); // 'greet' 関数を呼び出す

    let sum = add(5, 3); // 'add' を呼び出し、返された値を 'sum' に束縛する
    println!("5 + 3 = {}", sum); // 出力: 5 + 3 = 8
}

関数に関するキーポイント:

  • 関数を宣言するにはfnキーワードを使用します。
  • 括弧内にパラメータ名とその型を指定します。
  • 関数の戻り値の型を矢印->の後に指定します。関数が値を返さない場合、その戻り値の型は暗黙的に()(空のタプル、しばしば「ユニット型」と呼ばれる)になります。
  • 関数本体は一連の(アクションを実行する命令、通常は;で終わる)で構成され、オプションで(値に評価されるもの)で終わります。
  • 関数ブロックの最後の式の値は、セミコロンがない場合、自動的に返されます。returnキーワードは、関数内のどこからでも明示的に早期リターンするために使用できます。

制御フロー

Rustは、コードが実行される順序を決定するための標準的な制御フロー構造を提供します:

  • if/else/else if: 条件付き実行に使用されます。
let number = 6;

if number % 4 == 0 {
    println!("number is divisible by 4");
} else if number % 3 == 0 {
    println!("number is divisible by 3"); // この分岐が実行される
} else {
    println!("number is not divisible by 4 or 3");
}

// 重要なことに、'if' は Rust では式であり、値に評価されることを意味します。
// これにより、'let' 文で直接使用できます。
let condition = true;
let value = if condition { 5 } else { 6 }; // value は 5 になります
println!("The value is: {}", value);
// 注意: 'if' 式の両方の分岐は同じ型に評価される必要があります。
  • ループ: 繰り返し実行に使用されます。
    • loop: 無限ループを作成します。通常、ループを終了するためにbreakを使用し、オプションで値を返します。
    • while: 指定された条件が真である限りループします。
    • for: コレクションの要素または範囲を反復処理します。これはRustで最も一般的に使用され、しばしば最も安全なループタイプです。
// loop の例
let mut counter = 0;
let result = loop {
    counter += 1;
    if counter == 10 {
        break counter * 2; // ループを終了し 'counter * 2' を返す
    }
};
println!("Loop result: {}", result); // 出力: Loop result: 20

// while の例
let mut num = 3;
while num != 0 {
    println!("{}!", num);
    num -= 1;
}
println!("LIFTOFF!!!");

// for の例(配列の反復処理)
let a = [10, 20, 30, 40, 50];
for element in a.iter() { // .iter() は配列の要素に対するイテレータを作成する
    println!("the value is: {}", element);
}

// for の例(範囲の反復処理)
// (1..4) は 1, 2, 3 を含む範囲を作成する(4 は含まない)
// .rev() はイテレータを逆順にする
for number in (1..4).rev() {
    println!("{}!", number); // 3!, 2!, 1! を出力
}
println!("LIFTOFF AGAIN!!!");

コメント

コンパイラが無視する説明やメモをコードに追加するためにコメントを使用します。

// これは単一行コメントです。行末まで続きます。

/*
 * これは複数行のブロックコメントです。
 * 数行にまたがることができ、長い説明に
 * 便利です。
 */

 let lucky_number = 7; // 行末にコメントを置くこともできます。

所有権の理解:Rustの核心概念

所有権はRustの最も特徴的で中心的な機能です。これは、Rustがガベージコレクタを必要とせずにコンパイル時にメモリ安全性を保証することを可能にするメカニズムです。所有権を把握することがRustを理解する鍵となります。それは3つのコアなルールに従います:

  1. 所有者: Rustの各値には、その所有者として指定された変数が存在します。
  2. 唯一の所有者: 特定の値の所有者は、常に1つだけです。
  3. スコープとDrop: 所有者変数がスコープ外に出ると(例えば、宣言された関数が終了するなど)、それが所有する値は破棄(drop)されます。これは、そのメモリが自動的に解放されることを意味します。
{ // s はここでは有効ではありません。まだ宣言されていません
    let s = String::from("hello"); // s はこの時点から有効です。
                                    // s はヒープに割り当てられた String データを「所有」します。
    // ここで s を使用できます
    println!("{}", s);
} // スコープはここで終了します。's' はもはや有効ではありません。
  // Rust は 's' が所有する String のために特別な 'drop' 関数を自動的に呼び出し、
  // そのヒープメモリを解放します。

所有される値(StringVec、または所有される型を含む構造体など)を別の変数に代入するか、値渡しで関数に渡すと、所有権はムーブされます。元の変数は無効になります。

let s1 = String::from("original");
let s2 = s1; // String データの所有権は s1 から s2 へムーブ(移動)されます。
             // s1 はこの時点以降、有効とは見なされなくなります。

// println!("s1 is: {}", s1); // コンパイル時エラー!ムーブ後にここで値が借用されています。
                              // s1 はもはやデータを所有していません。
println!("s2 is: {}", s2); // s2 が現在の所有者であり、有効です。

このムーブの挙動は、「二重解放」エラー(2つの変数がスコープ外に出るときに誤って同じメモリ位置を解放しようとする)を防ぎます。整数、浮動小数点数、真偽値、文字などのプリミティブ型はCopyトレイトを実装しており、代入されたり関数に渡されたりするときにムーブされる代わりに単純にコピーされることを意味します。

借用と参照

所有権を移転せずに、関数に値を使用させたい場合はどうすればよいでしょうか? 参照を作成できます。参照を作成することは借用と呼ばれます。参照を使用すると、所有権を取得せずに、別の変数が所有するデータにアクセスできます。

// この関数は String への参照 (&) を取ります。
// String を借用しますが、所有権は取りません。
fn calculate_length(s: &String) -> usize {
    s.len()
} // ここで s (参照) はスコープ外になります。しかし、それは String データを所有していないため、
  // 参照がスコープ外になってもデータは破棄されません。

fn main() {
    let s1 = String::from("hello");

    // '&' シンボルを使用して s1 への参照を渡します。
    // s1 は依然として String データを所有しています。
    let len = calculate_length(&s1);

    // 所有権は決してムーブされなかったため、s1 はここでまだ有効です。
    println!("The length of '{}' is {}.", s1, len);
}

参照は、変数と同様にデフォルトで不変です。借用したデータを変更したい場合は、&mutで示される可変参照が必要です。ただし、Rustはデータ競合を防ぐために、可変参照に関して厳格なルールを強制します:

借用ルール:

  1. 任意の時点で、以下のいずれかを持つことができます:
    • 1つの可変参照 (&mut T)。
    • 任意数の不変参照 (&T)。
  2. 参照は常に有効でなければなりません(参照するデータより長生きすることはできません – これはライフタイムによって管理され、しばしば暗黙的です)。

これらのルールはコンパイラによって強制されます。

// この関数は String への可変参照を取ります
fn change(some_string: &mut String) {
    some_string.push_str(", world");
}

fn main() {
    // 可変の借用を許可するために 's' は可変として宣言する必要がある
    let mut s = String::from("hello");

    // 借用ルールの強制の例(エラーを見るには行のコメントを解除):
    // let r1 = &s; // 不変の借用 - OK
    // let r2 = &s; // 別の不変の借用 - OK(複数の不変の借用は許可される)
    // let r3 = &mut s; // エラー!不変の借用がアクティブな間は `s` を可変として借用できません
    //                  // Rust は強制します:複数のリーダー(&T)か単一のライター(&mut T)のいずれかであり、両方は不可
    // println!("{}, {}", r1, r2); // r1/r2 を使用するとアクティブなままになり、エラーを引き起こす
    //                             // この println がなければ、Rust の NLL(Non-Lexical Lifetimes)は r1/r2 を早期に解放し、
    //                             // ここで &mut s を有効にする

    // 他の借用がアクティブでないため、ここでは可変の借用が許可される
    change(&mut s);
    println!("{}", s); // 出力: hello, world
}

複合データ型

Rustは、複数の値をより複雑な型にグループ化する方法を提供します。

構造体 (Structs)

構造体(structuresの略)を使用すると、関連するデータフィールドを単一の名前の下にグループ化することにより、カスタムデータ型を定義できます。

// User という名前の構造体を定義
struct User {
    active: bool,
    username: String, // 所有される String 型を使用
    email: String,
    sign_in_count: u64,
}

fn main() {
    // User 構造体のインスタンスを作成
    // インスタンスはすべてのフィールドに値を提供する必要がある
    let mut user1 = User {
        email: String::from("someone@example.com"),
        username: String::from("someusername123"),
        active: true,
        sign_in_count: 1,
    };

    // ドット表記を使用して構造体フィールドにアクセス
    // フィールド値を変更するにはインスタンスが可変である必要がある
    user1.email = String::from("anotheremail@example.com");

    println!("User email: {}", user1.email);

    // User インスタンスを作成するためのヘルパー関数を使用
    let user2 = build_user(String::from("user2@test.com"), String::from("user2"));
    println!("User 2 active status: {}", user2.active);

    // 構造体更新構文:既存のインスタンスから一部のフィールドを使用して
    // 残りのフィールド用に新しいインスタンスを作成する。
    let user3 = User {
        email: String::from("user3@domain.com"),
        username: String::from("user3"),
        ..user2 // user2 から 'active' と 'sign_in_count' の値を取る
    };
    println!("User 3 sign in count: {}", user3.sign_in_count);
}

// User インスタンスを返す関数
fn build_user(email: String, username: String) -> User {
    User {
        email, // フィールド初期化省略記法:パラメータ名がフィールド名と一致する場合
        username,
        active: true,
        sign_in_count: 1,
    }
}

Rustはまた、タプル構造体(名前付きタプル、例:struct Color(i32, i32, i32);)と、フィールドがなく、型にトレイトを実装する必要があるがデータを格納する必要がない場合に役立つユニット様構造体(例:struct AlwaysEqual;)もサポートしています。

列挙型 (Enums)

列挙型(enumerations)を使用すると、可能なバリアントを列挙することによって型を定義できます。列挙型の値は、その可能なバリアントのうちの1つにしかなれません。

// IPアドレスの種類を定義する単純な列挙型
enum IpAddrKind {
    V4, // バリアント 1
    V6, // バリアント 2
}

// 列挙型のバリアントは関連データも保持できる
enum IpAddr {
    V4(u8, u8, u8, u8), // V4 バリアントは4つの u8 値を保持
    V6(String),         // V6 バリアントは String を保持
}

// Rustの標準ライブラリで非常に一般的で重要な列挙型: Option<T>
// 存在するかもしれないし、存在しないかもしれない値の概念をエンコードする。
// enum Option<T> {
//     Some(T), // T 型の値が存在することを表す
//     None,    // 値が存在しないことを表す
// }

// もう一つの重要な標準ライブラリ列挙型: Result<T, E>
// 成功(Ok)または失敗(Err)する可能性のある操作に使用される。
// enum Result<T, E> {
//     Ok(T),   // 成功を表し、T 型の値を含む
//     Err(E),  // 失敗を表し、E 型のエラー値を含む
// }

fn main() {
    let four = IpAddrKind::V4;
    let six = IpAddrKind::V6;

    // 関連データを持つ IpAddr 列挙型のインスタンスを作成
    let home = IpAddr::V4(127, 0, 0, 1);
    let loopback = IpAddr::V6(String::from("::1"));

    // Option<T> の例
    let some_number: Option<i32> = Some(5);
    let no_number: Option<i32> = None;

    // 'match' 制御フロー演算子は列挙型を扱うのに最適です。
    // 列挙型のバリアントに基づいて異なるコードを実行できます。
    match some_number {
        Some(i) => println!("Got a number: {}", i), // Some の場合、内部値を i に束縛
        None => println!("Got nothing."),           // None の場合
    }

    // 'match' は網羅的でなければなりません:すべての可能なバリアントを処理する必要があります。
    // アンダースコア '_' は、明示的にリストされていないバリアントをキャッチするための
    // ワイルドカードパターンとして使用できます。
}

Option<T>Result<T, E>は、潜在的に欠落している値やエラーを堅牢に処理するためのRustのアプローチの中心です。

Cargo入門:Rustビルドツール兼パッケージマネージャ

CargoはRustエコシステムの不可欠な部分であり、Rustプロジェクトのビルド、テスト、管理のプロセスを効率化します。これは開発者がしばしば賞賛する機能の1つです。

  • Cargo.toml: これはRustプロジェクトのマニフェストファイルです。TOML(Tom's Obvious, Minimal Language)形式で書かれています。プロジェクトに関する必須のメタデータ(名前、バージョン、作者など)と、決定的に重要な依存関係(プロジェクトが依存する他の外部クレート)のリストが含まれています。
    [package]
    name = "my_project"
    version = "0.1.0"
    edition = "2021" # 使用するRustエディションを指定(言語機能に影響)
    
    # 依存関係は以下にリストされます
    [dependencies]
    # 例:乱数生成のために 'rand' クレートを追加
    # rand = "0.8.5"
    # ビルドすると、Cargo は 'rand' とその依存関係をダウンロードしてコンパイルします。
    
  • cargo new <project_name>: 新しいバイナリ(実行可能)アプリケーションプロジェクト構造を作成します。
  • cargo new --lib <library_name>: 新しいライブラリ(他のプログラムで使用されることを意図したクレート)プロジェクト構造を作成します。
  • cargo build: プロジェクトとその依存関係をコンパイルします。デフォルトでは、最適化されていないデバッグビルドを作成します。出力はtarget/debug/ディレクトリに置かれます。
  • cargo build --release: 配布やパフォーマンステストに適した、最適化を有効にしてプロジェクトをコンパイルします。出力はtarget/release/ディレクトリに置かれます。
  • cargo run: バイナリプロジェクトをコンパイル(必要な場合)して実行します。
  • cargo check: 最終的な実行可能ファイルを実際に生成せずに、コードのコンパイルエラーを迅速にチェックします。これは通常cargo buildよりもはるかに高速で、開発中の迅速なフィードバックに役立ちます。
  • cargo test: プロジェクト内で定義されたテスト(通常はsrcディレクトリまたは別のtestsディレクトリに配置)を実行します。

Cargo.tomlファイルに依存関係を追加し、cargo buildcargo runのようなコマンドを実行すると、Cargoは中央リポジトリcrates.ioから必要なクレート(およびその依存関係)をダウンロードし、すべてをまとめてコンパイルする処理を自動的に行います。

Rustの旅における次のステップ

この入門ガイドでは、あなたが始めるための絶対的な基本をカバーしました。Rust言語は、学習を進めるにつれて探求すべき、さらに多くの強力な機能を提供します:

  • エラーハンドリング: Result列挙型の習得、?演算子を使用したエラー伝播、カスタムエラー型の定義。
  • コレクション: Vec<T>(動的配列/ベクター)、HashMap<K, V>(ハッシュマップ)、HashSet<T>などの一般的なデータ構造の効果的な使用。
  • ジェネリクス: 型パラメータ(<T>)を使用して、異なるデータ型に対して操作できる柔軟で再利用可能なコードの記述。
  • トレイト: 型が実装できる共有の振る舞いやインターフェースの定義(他の言語のインターフェースに似ているが、より強力)。
  • ライフタイム: Rustが参照が常に有効であることをどのように保証するか、時には明示的なライフタイムアノテーション('a)が必要になることの理解。
  • 並行性: スレッド、メッセージパッシングのためのチャネル、MutexArcによる共有状態、そして非同期プログラミングのためのRustの強力なasync/await構文の探求。
  • マクロ: 高度なコンパイル時コード生成のための独自のマクロの記述方法の学習。
  • モジュール: 大規模なプロジェクトを論理的な単位に整理し、アイテムの可視性(public/private)を制御する。
  • テスト: Rustの組み込みテストフレームワークを使用した効果的なユニットテスト、結合テスト、ドキュメンテーションテストの記述。

結論

Rustはユニークで魅力的な提案を提示します:C++のような低レベル言語に期待される生のパフォーマンスと、特にメモリ管理と並行性に関する一般的なバグのクラス全体を排除する強力なコンパイル時の安全性保証の組み合わせです。学習曲線には所有権や借用のような新しい概念を内在化すること(そして時には厳格なコンパイラに耳を傾けること)が含まれますが、その見返りは、非常に信頼性が高く、効率的で、長期的にはしばしば保守が容易なソフトウェアです。Cargoによる優れたツールと支援的なコミュニティにより、Rustは学ぶ価値のある言語です。

小さく始め、Cargoを使って実験し、コンパイラの役立つ(時には詳細すぎる)エラーメッセージをガイダンスとして受け入れれば、この強力でますます人気が高まっている言語を習得する道を着実に歩むことになるでしょう。楽しいRustプログラミングを!

関連記事