0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Effective-Rustlings-jp】Day 6:newtypeパターンを活用しよう

Last updated at Posted at 2025-01-06

はじめに

こんにちは。細々とプログラミングをしているsotanengelです。
この記事は以下の記事の連載です。

他の連載記事 (詳細)

また本記事はEffective Rust(David Drysdale (著), 中田 秀基 (翻訳))を参考に作成されております。とてもいい書籍ですので興味を持った方は、ぜひ読んでみてください!

今日の内容

概要

Rustでは既存の型を一つだけ持つタプル構造体のことをnewtypeパターンと呼んでいます。
うまく活用することでコンパイル時にエラーを発見することができます。

newtypeパターンを使って意図しないバグをコンパイル時に発見しよう

問(リンク)

メートルと重さを管理する独自の型を実装し、既存の型(f64)を使う場合よりも安全なコードを書いてみましょう。

コード (詳細)
// TODO: 長さを表すメートルの型を定義しましょう。

// TODO: 重さを表すキログラムの型を定義しましょう。

// f64 から Meters への変換
impl From<f64> for Meters {
    fn from(value: f64) -> Self {
        Meters(value)
    }
}

// f64 から Kilograms への変換
impl From<f64> for Kilograms {
    fn from(value: f64) -> Self {
        Kilograms(value)
    }
}

// メートルをスケーリングする関数
fn scale_meters(m: Meters, factor: f64) -> Meters {
    Meters(m.0 * factor)
}

// キログラムをグラムに変換する関数
fn convert_to_grams(k: Kilograms) -> f64 {
    k.0 * 1000.0
}

fn main() {
    let height = Meters(1.75);
    let weight = Kilograms(68.0);

    let double_height = scale_meters(height, 2.0);
    println!("Double height: {} m", double_height.0);

    let weight_in_grams = convert_to_grams(weight);
    println!("Weight in grams: {} g", weight_in_grams);
}

/// テストモジュール
/// コンパイルエラーを期待するテスト
///
/// ```compile_fail
/// let m = Meters::from(3.0);
/// // Kilograms 型を要求している関数に Meters を渡す -> エラー
/// let _ = convert_to_grams(m);
/// ```
fn compile_fail_example() {}

解答(リンク)

コード参照。

コード (詳細)
// 長さを表すメートルの型
#[repr(transparent)] // この属性を追加することでメモリ表現を内部の型(f64)と同じにすることができ、効率化になる。
struct Meters(f64);

// 重さを表すキログラムの型
#[repr(transparent)]
struct Kilograms(f64);

// f64 から Meters への変換
impl From<f64> for Meters {
    fn from(value: f64) -> Self {
        Meters(value)
    }
}

// f64 から Kilograms への変換
impl From<f64> for Kilograms {
    fn from(value: f64) -> Self {
        Kilograms(value)
    }
}

// メートルをスケーリングする関数
fn scale_meters(m: Meters, factor: f64) -> Meters {
    Meters(m.0 * factor)
}

// キログラムをグラムに変換する関数
fn convert_to_grams(k: Kilograms) -> f64 {
    k.0 * 1000.0
}

fn main() {
    let height = Meters(1.75);
    let weight = Kilograms(68.0);

    let double_height = scale_meters(height, 2.0);
    println!("Double height: {} m", double_height.0);

    let weight_in_grams = convert_to_grams(weight);
    println!("Weight in grams: {} g", weight_in_grams);
}

/// テストモジュール
/// コンパイルエラーを期待するテスト
///
/// ```compile_fail
/// let m = Meters::from(3.0);
/// // Kilograms 型を要求している関数に Meters を渡す -> エラー
/// let _ = convert_to_grams(m);
/// ```
#[allow(dead_code)]
fn compile_fail_example() {}

トレイト孤児ルールを回避しよう

問(リンク)

Rustではある型にトレイトを実装するためには以下の条件を満たす必要がある。

  • そのクレートでそのトレイトを定義している
  • そのクレートでその型を定義している

そこでnewtypeを使うことでトレイト孤児ルールを回避しましょう。

コード (詳細)
struct MyVec(Vec<i32>);

trait Sum {
    fn sum(&self) -> i32;
}

// TODO: 自分で作成したnewTypeにSumトレイトを実装しましょう。

fn main() {
    let numbers = MyVec(vec![1, 2, 3, 4]);
    println!("Sum: {}", numbers.sum());
}

解答(リンク)

コード参照。

コード (詳細)
struct MyVec(Vec<i32>);

trait Sum {
    fn sum(&self) -> i32;
}

// 自分で作成したnewTypeのため、Sumを実装することができる。
impl Sum for MyVec {
    fn sum(&self) -> i32 {
        self.0.iter().sum()
    }
}

fn main() {
    let numbers = MyVec(vec![1, 2, 3, 4]);
    println!("Sum: {}", numbers.sum());
}

さいごに

もしも本リポジトリで不備などあれば、リポジトリのissueやPRなどでご指摘いただければと思います。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?