404 Not Found

404 Not Found


nginx

Rustの構造体(Struct):定義、インスタンス化、およびメソッドの実装

構造体(Struct)は、Rustでカスタムデータ型を定義するための中心的なツールであり、関連するデータを意味のある全体としてまとめるものです。

変数が「単一のデータ」であるとするなら、構造体は「関連するデータの集合」です。これは表のようなもので、各行がインスタンス、各列がフィールドに相当します。


1. 学習内容


2. 概念図

100%
flowchart LR
    subgraph "struct Definition"
        DEF["struct User"]
        F1["username: String"]
        F2["email: String"]
        F3["active: bool"]
        F4["sign_in_count: u64"]
    end
    subgraph "Instantiation"
        INS["let user = User { ... }"]
        V1["username: 'alice'"]
        V2["email: 'alice@example.com'"]
        V3["active: true"]
        V4["sign_in_count: 1"]
    end
    F1 -.->|"Field Mapping"| V1
    F2 -.->|"Field Mapping"| V2
    F3 -.->|"Field Mapping"| V3
    F4 -.->|"Field Mapping"| V4

3. 学生管理システムの物語

(1) 課題:変動要因が散在する生徒への対応

マリアは、自分のクラスの生徒に関する情報を管理する必要がある教師です。各生徒には、以下の情報があります:

彼女は最初、こう書いた:

RUST
let name1 = "Xiao Ming";
let age1 = 18;
let grade1 = "Senior Year";
let gpa1 = 3.8;

let name2 = "Xiao Hong";
let age2 = 17;
let grade2 = "11th Grade";
let gpa2 = 3.5;

学生数が40人に増えると、こうしたばらばらに分散した変数は完全に管理不能になります。成績証明書を印刷するには40行のコードを記述する必要があり、たった1つのフィールドを変更するだけでも、4か所を修正しなければなりません。

(2) Rustの構造体によるアプローチ

RUST
struct Student {
    name: String,
    age: u8,
    grade: String,
    gpa: f64,
}

fn main() {
    let student1 = Student {
        name: String::from("Xiao Ming"),
        age: 18,
        grade: String::from("Senior 3"),
        gpa: 3.8,
    };

    let student2 = Student {
        name: String::from("Xiao Hong"),
        age: 17,
        grade: String::from("Senior 2"),
        gpa: 3.5,
    };

    println!("{}: {} years old, Grade: {}, GPA: {:.1}",
        student1.name, student1.age, student1.grade, student1.gpa);
    println!("{}: {} years old, Grade: {}, GPA: {:.1}",
        student2.name, student2.age, student2.grade, student2.gpa);
}

structは「テンプレート」のようなもので、「学生」というオブジェクトがどのような情報を含むべきかを定義します。各インスタンスは、記入済みのフォームのようなものです。フィールドへのアクセスは.を介して行われ、これは明確かつ安全です。


4. 構造の種類

TEXT
graph TB
    A[struct Type] --> B[Naming Structures: Fields have names and types]
    A --> C[Tuple Structure: The field has a type but no name]
    A --> D[Unit Structure: No fields]
    B --> E[struct User { name: String, age: u8 }]
    C --> F[struct Color(i32, i32, i32)]
    D --> G[struct EmptyMarker]
タイプ 定義 ユースケース
命名規則 struct S { f1: T, f2: T } ほとんどの場合、フィールド名はそれ自体が説明となっている
タプルの構造 struct S(T1, T2) 単一の概念(例:RGB色、座標)をカプセル化する
ユニットの構造 struct S; 型マーカー、特性の実装用プレースホルダー

(2) 手法と関連関数

最初の引数 呼び出し規約 所有権 代表的な用途
メソッド &self obj.method() 借用 インスタンスデータの読み取り
メソッド &mut self obj.method() 変数の借用 インスタンスデータの変更
メソッド self obj.method() 消費量 インスタンスの変換/破棄
関連関数 なし Type::function() コンストラクタ、ファクトリメソッド

(3) よく使われる派生形質のクイックリファレンス

特性 機能 自動適用条件
Debug {:?} 書式付き印刷 すべてのフィールドをデバッグ #[derive(Debug)]
Clone .clone() ディープコピー すべてのフィールドを複製 #[derive(Clone)]
Copy 割り当て時の自動コピー クローン + すべてのフィールドのコピー #[derive(Copy, Clone)]
PartialEq == / != の比較 すべてのフィールドで PartialEq を実装 #[derive(PartialEq)]
Hash HashMapのキーとして使用 すべてのフィールドがHashを実装 #[derive(Hash)]
Default デフォルト値 すべてのフィールドにデフォルト値が設定されています #[derive(Default)]

5. 構造の例

(1) ▶ サンプル:名前付き構造体の完全な使い方(難易度 ⭐)

RUST
// ============================================
// Naming Structures: Definition, Instantiation, Field Access
// ============================================

#[derive(Debug)]
struct Rectangle {
    width: u32,
    height: u32,
}

fn main() {
    // Create an Instance
    let rect = Rectangle {
        width: 30,
        height: 50,
    };

    // Access Fields
    println!("Width: {}, Height: {}", rect.width, rect.height);

    // Variable Instances
    let mut mutable_rect = Rectangle {
        width: 10,
        height: 20,
    };
    mutable_rect.width = 15;  // ✅ Can be modified
    println!("Width after modification: {}", mutable_rect.width);

    // Use Debug Print
    println!("rect: {:?}", rect);
    println!("rect (pretty): {:#?}", rect);
}

出力:

TEXT
Width: 30, Height: 50
Width after modification: 15
rect: Rectangle { width: 30, height: 50 }
rect (pretty): Rectangle {
    width: 30,
    height: 50,
}

#[derive(Debug)] 構造体を {:?} または {:#?} の書式で出力できるようにします。これは、デバッグ中に最もよく使用される派生トレイトの一つです。


(2) ▶ サンプル:構造体の更新構文とフィールドの省略形(難易度 ⭐⭐)

RUST
// ============================================
// Initializing Field Abbreviations + Update Syntax
// ============================================

#[derive(Debug)]
struct User {
    username: String,
    email: String,
    active: bool,
    sign_in_count: u64,
}

fn build_user(username: String, email: String) -> User {
    User {
        username,   // When field names and variable names are the same, they can be written in shorthand.
        email,      // Equivalent to email: email
        active: true,
        sign_in_count: 1,
    }
}

fn main() {
    let user1 = build_user(
        String::from("alice"),
        String::from("alice@example.com"),
    );

    // Update Syntax: Based on user1 create user2, modify only email and username
    let user2 = User {
        email: String::from("alice_new@example.com"),
        username: String::from("alice_new"),
        ..user1  // Other fields copied from user1 (Note: String fields will move!)
    };
    // println!("user1: {}", user1.username);  // ❌ username was moved to user2

    println!("user2: {:?}", user2);
    println!("user1.active: {}", user1.active);  // ✅ bool is Copy, still valid
}

出力:

TEXT
user2: User { username: "alice_new", email: "alice_new@example.com", active: true, sign_in_count: 1 }
user1.active: true

フィールドの省略形:変数名が構造体のフィールド名と一致する場合、field: field のコロンは省略可能です。更新された構文 .. は、残りのフィールドを別のインスタンスからコピーします。なお、これは Clone ではなく移動セマンティクスを使用することに注意してください。


(3) ▶ サンプル:impl ブロック — 構造体にメソッドを追加する (難易度: ⭐⭐)

RUST
// ============================================
// impl block: Methods (&self) and associated functions (no &self)
// ============================================

#[derive(Debug)]
struct Rectangle {
    width: u32,
    height: u32,
}

impl Rectangle {
    // Methods: &self is a reference to a Rectangle instance
    fn area(&self) -> u32 {
        self.width * self.height
    }

    fn can_hold(&self, other: &Rectangle) -> bool {
        self.width > other.width && self.height > other.height
    }

    // Associative Functions (similar to static methods): no &self
    fn square(size: u32) -> Rectangle {
        Rectangle {
            width: size,
            height: size,
        }
    }
}

fn main() {
    let rect1 = Rectangle {
        width: 30,
        height: 50,
    };
    let rect2 = Rectangle {
        width: 10,
        height: 20,
    };
    let square = Rectangle::square(15);  // Correlation functions are used to :: Call

    println!("rect1 Area: {}", rect1.area());  // 1500
    println!("rect1 Can it accommodate? rect2: {}", rect1.can_hold(&rect2));  // true
    println!("Area of a Square: {}", square.area());  // 225
}

出力:

TEXT
rect1 Area: 1500
rect1 Can it accommodate? rect2: true
Area of a Square: 225

メソッド(Method)の最初のパラメータは &self(または &mut self)であり、. を通じて呼び出されます。関連関数(Associated Function)には &self が存在せず、:: を通じて呼び出されます。その典型的な例が String::from() です。


(4) ▶ サンプル:タプルの構造 (難易度 ⭐⭐)

RUST
// ============================================
// Tuple Structure: Suitable for "it's just a bunch of values" scenarios
// ============================================

#[derive(Debug)]
struct Color(i32, i32, i32);  // RGB Color

#[derive(Debug)]
struct Point(i32, i32, i32);  // 3D Coordinates

fn main() {
    let black = Color(0, 0, 0);
    let origin = Point(0, 0, 0);

    // Fields Accessed via Indexes (tuple-like)
    println!("Black: R={}, G={}, B={}", black.0, black.1, black.2);
    println!("The Beginning: x={}, y={}, z={}", origin.0, origin.1, origin.2);

    // Tuple structures are not the same as tuples!
    // let wrong: Color = origin;  // ❌ Compilation Error: Point cannot be assigned to Color

    // Although their internal structures are the same, they are different types
    // This prevents "mixing up colors and coordinates" bugs
}

出力:

TEXT
Black: R=0, G=0, B=0
The Beginning: x=0, y=0, z=0

タプル構造体にはフィールド名がなく、型のみが指定されます。これはタプルと似ていますが、独自の型名を持っています。Color(0,0,0)Point(0,0,0) は内部的には同一ですが、型が異なります。これにより、混同を防ぐことができます。


(5) ▶ サンプル:総合演習—学生管理システム(難易度 ⭐⭐⭐)

RUST
// ============================================
// Comprehensive Example: Structure-Based Approach, Associated Functions and Update Syntax
// ============================================

#[derive(Debug, Clone)]
struct Student {
    name: String,
    age: u8,
    scores: Vec<u32>,
}

impl Student {
    fn new(name: &str, age: u8) -> Self {
        Student {
            name: name.to_string(),
            age,
            scores: Vec::new(),
        }
    }

    fn add_score(&mut self, score: u32) {
        self.scores.push(score);
    }

    fn average(&self) -> f64 {
        if self.scores.is_empty() {
            return 0.0;
        }
        let sum: u32 = self.scores.iter().sum();
        sum as f64 / self.scores.len() as f64
    }

    fn grade(&self) -> char {
        match self.average() as u32 {
            90..=100 => 'A',
            80..=89  => 'B',
            70..=79  => 'C',
            60..=69  => 'D',
            _        => 'F',
        }
    }

    fn summary(&self) -> String {
        format!("{} ({} years old) Average: {:.1} Level: {}", self.name, self.age, self.average(), self.grade())
    }
}

fn main() {
    let mut alice = Student::new("Alice", 20);
    alice.add_score(95);
    alice.add_score(88);
    alice.add_score(92);
    println!("{}", alice.summary());

    let mut bob = Student::new("Bob", 21);
    bob.add_score(72);
    bob.add_score(65);
    bob.add_score(58);
    println!("{}", bob.summary());

    let mut charlie = Student {
        name: String::from("Charlie"),
        ..alice.clone()
    };
    charlie.name = String::from("Charlie");
    charlie.add_score(100);
    println!("{}", charlie.summary());

    let students = [&alice, &bob, &charlie];
    println!("\n=== Class Rankings ===");
    let mut ranked: Vec<_> = students.iter().collect();
    ranked.sort_by(|a, b| b.average().partial_cmp(&a.average()).unwrap());
    for (i, s) in ranked.iter().enumerate() {
        println!("#{}: {}", i + 1, s.summary());
    }
}

出力:

TEXT
Alice (20 years old) Average: 91.7 Level: A
Bob (21 years old) Average: 65.0 Level: D
Charlie (20 years old) Average: 93.8 Level: A

=== Class Rankings ===
#1: Charlie (20 years old) Average: 93.8 Level: A
#2: Alice (20 years old) Average: 91.7 Level: A
#3: Bob (21 years old) Average: 65.0 Level: D

この例では、関連付け関数 new()、メソッド add_score()/average()/grade()、および更新構文 ..alice.clone()#[derive(Debug, Clone)] を組み合わせて使用しています。構造体は、関連するデータやメソッドを整理するための中心的なツールです。


❓ よくある質問

Q 構造体のフィールドの所有権はどのように扱われますか?
A 構造体自体は所有権のルールに従います。
Q メソッド(&self 付き)とアソシエイト関数(&self なし)は、それぞれどのような場合に使用すべきですか?
A インスタンスデータを操作する必要がある場合はメソッドを使用し、インスタンスデータが必要ない場合はアソシエイト関数を使用します。
Q &self と self の違いは何ですか?
A &self は参照(所有権の移転なし)であるのに対し、self はインスタンスを消費します(所有権の移転あり)。
Q 構造体(struct)は参照を格納できますか?
A はい、ただしその有効期間を指定する必要があります。
Q #[derive(Debug)] はどのような役割を果たしますか?
A これは、構造体に対して Debug トレイトを自動的に実装します。

📖 まとめ


📝 練習問題

  1. 難易度 ⭐: title: Stringauthor: Stringyear: u32 を含む構造体 Book を定義してください。2つのインスタンスを作成し、タイトルと著者を表示してください。
  2. 難易度 ⭐⭐fn perimeter(&self) -> u32 に、周囲長を計算するメソッドを追加してください。また、幅と高さが等しい長方形を作成する関連関数 fn from_width(w: u32) -> Rectangle を追加してください。
  3. 難易度 ⭐⭐⭐: 距離(メートルおよびセンチメートル単位)を表すタプル構造 Distance(f64, f64) を定義してください。メートル単位での合計値を返すメソッド fn to_meters(&self) -> f64 を実装してください(例えば、Distance(1, 50)1.50 を返します)。
Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%