チュートリアル

Rust基礎:構造体

Rust入門構造体
広告エリア

構造体の定義

// 構造体の定義
struct User {
    name: String,
    email: String,
    age: u32,
    active: bool,
}

// インスタンス化
let user = User {
    name: String::from("太郎"),
    email: String::from("taro@example.com"),
    age: 25,
    active: true,
};

// フィールドへのアクセス
println!("{}", user.name);

可変な構造体

let mut user = User {
    name: String::from("太郎"),
    email: String::from("taro@example.com"),
    age: 25,
    active: true,
};

user.age = 26;

フィールド初期化省略記法

fn create_user(name: String, email: String) -> User {
    User {
        name,    // name: name の省略
        email,   // email: email の省略
        age: 0,
        active: true,
    }
}

構造体更新記法

let user1 = User {
    name: String::from("太郎"),
    email: String::from("taro@example.com"),
    age: 25,
    active: true,
};

// user1 の一部を変更してuser2を作成
let user2 = User {
    email: String::from("taro2@example.com"),
    ..user1  // 残りのフィールドはuser1から
};

タプル構造体

struct Color(u8, u8, u8);
struct Point(f64, f64, f64);

let black = Color(0, 0, 0);
let origin = Point(0.0, 0.0, 0.0);

println!("R: {}", black.0);
println!("x: {}", origin.0);

ユニット構造体

struct AlwaysEqual;

let subject = AlwaysEqual;

メソッド

struct Rectangle {
    width: u32,
    height: u32,
}

impl Rectangle {
    // メソッド
    fn area(&self) -> u32 {
        self.width * self.height
    }

    fn perimeter(&self) -> u32 {
        2 * (self.width + self.height)
    }

    // 可変参照を取るメソッド
    fn double(&mut self) {
        self.width *= 2;
        self.height *= 2;
    }

    // 所有権を取るメソッド
    fn into_square(self) -> Rectangle {
        let side = self.width.max(self.height);
        Rectangle { width: side, height: side }
    }
}

let mut rect = Rectangle { width: 10, height: 5 };
println!("面積: {}", rect.area());
println!("周囲: {}", rect.perimeter());

rect.double();
println!("2倍後の面積: {}", rect.area());

関連関数(コンストラクタ)

impl Rectangle {
    // 関連関数(selfを取らない)
    fn new(width: u32, height: u32) -> Rectangle {
        Rectangle { width, height }
    }

    fn square(size: u32) -> Rectangle {
        Rectangle { width: size, height: size }
    }
}

let rect = Rectangle::new(10, 5);
let square = Rectangle::square(10);

複数のimplブロック

impl Rectangle {
    fn area(&self) -> u32 {
        self.width * self.height
    }
}

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

列挙型(enum)

enum Direction {
    North,
    South,
    East,
    West,
}

let dir = Direction::North;

match dir {
    Direction::North => println!("北"),
    Direction::South => println!("南"),
    Direction::East => println!("東"),
    Direction::West => println!("西"),
}

データを持つ列挙型

enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
    ChangeColor(u8, u8, u8),
}

let msg = Message::Move { x: 10, y: 20 };

match msg {
    Message::Quit => println!("終了"),
    Message::Move { x, y } => println!("移動: ({}, {})", x, y),
    Message::Write(text) => println!("書き込み: {}", text),
    Message::ChangeColor(r, g, b) => println!("色: ({}, {}, {})", r, g, b),
}

列挙型のメソッド

impl Message {
    fn call(&self) {
        match self {
            Message::Quit => println!("終了します"),
            Message::Move { x, y } => println!("({}, {})に移動", x, y),
            Message::Write(text) => println!("{}", text),
            Message::ChangeColor(r, g, b) => println!("色を変更: RGB({},{},{})", r, g, b),
        }
    }
}

Option と Result

// Option<T>
enum Option<T> {
    Some(T),
    None,
}

fn find_user(id: u32) -> Option<String> {
    if id == 1 {
        Some(String::from("太郎"))
    } else {
        None
    }
}

match find_user(1) {
    Some(name) => println!("見つかりました: {}", name),
    None => println!("見つかりませんでした"),
}

// Result<T, E>
enum Result<T, E> {
    Ok(T),
    Err(E),
}

fn divide(a: i32, b: i32) -> Result<i32, String> {
    if b == 0 {
        Err(String::from("ゼロ除算"))
    } else {
        Ok(a / b)
    }
}

match divide(10, 2) {
    Ok(result) => println!("結果: {}", result),
    Err(e) => println!("エラー: {}", e),
}

Option/Result のメソッド

let some_value: Option<i32> = Some(5);

// unwrap系
let value = some_value.unwrap();  // Noneならパニック
let value = some_value.unwrap_or(0);  // Noneなら0
let value = some_value.unwrap_or_default();  // Noneならデフォルト値

// map
let doubled = some_value.map(|x| x * 2);  // Some(10)

// and_then(フラットマップ)
let result = some_value.and_then(|x| {
    if x > 0 { Some(x * 2) } else { None }
});

// is_some / is_none
if some_value.is_some() {
    println!("値があります");
}

派生属性

#[derive(Debug, Clone, PartialEq)]
struct Point {
    x: f64,
    y: f64,
}

let p1 = Point { x: 1.0, y: 2.0 };
let p2 = p1.clone();

println!("{:?}", p1);  // Debug
println!("{}", p1 == p2);  // PartialEq

実践例:銀行口座

#[derive(Debug)]
struct BankAccount {
    account_number: String,
    owner: String,
    balance: f64,
}

#[derive(Debug)]
enum TransactionError {
    InsufficientFunds,
    InvalidAmount,
}

impl BankAccount {
    fn new(owner: &str, initial_balance: f64) -> Self {
        static mut COUNTER: u32 = 1000;
        let account_number = unsafe {
            COUNTER += 1;
            format!("ACC-{}", COUNTER)
        };

        BankAccount {
            account_number,
            owner: owner.to_string(),
            balance: initial_balance,
        }
    }

    fn deposit(&mut self, amount: f64) -> Result<f64, TransactionError> {
        if amount <= 0.0 {
            return Err(TransactionError::InvalidAmount);
        }
        self.balance += amount;
        Ok(self.balance)
    }

    fn withdraw(&mut self, amount: f64) -> Result<f64, TransactionError> {
        if amount <= 0.0 {
            return Err(TransactionError::InvalidAmount);
        }
        if amount > self.balance {
            return Err(TransactionError::InsufficientFunds);
        }
        self.balance -= amount;
        Ok(self.balance)
    }
}

fn main() {
    let mut account = BankAccount::new("山田太郎", 10000.0);
    println!("{:?}", account);

    match account.deposit(5000.0) {
        Ok(balance) => println!("入金後残高: {}", balance),
        Err(e) => println!("エラー: {:?}", e),
    }

    match account.withdraw(3000.0) {
        Ok(balance) => println!("出金後残高: {}", balance),
        Err(e) => println!("エラー: {:?}", e),
    }
}

まとめ

  • structで名前付きフィールドの構造体
  • タプル構造体で位置ベースのフィールド
  • implでメソッドと関連関数を定義
  • enumで列挙型を定義
  • Option<T>で値の有無を表現
  • Result<T, E>で成功/失敗を表現
  • #[derive(...)]で自動実装

次回はトレイトについて学びます。

広告エリア