[Rust] TypedIdでidに型を割り当てる

2024.01.26

Introduction

RustでDBなどから取得したデータをモデル化して使う場合、
下記のようにidをDBの形式に合わせて持たせることが多いです。

struct Foo {
    id : u32,
    bar : String
}

これはこれでよいのですが、例えばFoo構造体のidとBar構造体のidは
同じu32型であっても別のものとして扱いたいところです。
そんなときに使えるのが、今回紹介するTypedIdです。

TypedIdは汎用的なラッパーであり、idに対して単一の型を導入できます。
TypedIdのインスタンスには、対象の構造体にそのid型を関連付ける
ためのgeneric parameterがあります。

ではTypedIdを使ってみましょう。

Environments

  • MacBook Pro (13-inch, M1, 2020)
  • OS : MacOS 13.5.2
  • Rust : 1.75.0

Try

Setup

Cargoでプロジェクトを作成したあと、
TypedId crateを追加します。

% cargo new typedid-example
% cd /path/your/typedid-example
% cargo add typed_id paste

pasteはマクロ内で新しい識別子を生成するcrateで、
後述するid_type!を使うときに必要です。

実装してみる

src/main.rsでTypedIdをつかってみましょう。
下記のように、User型、Customer型を定義し、
そのidとしてUserId型、CustomerId型を使っています。
id型はTypedIdをつかって実際の型(u32)と、どの構造体の型なのかを
指定しています。

use typed_id::TypedId;

#[derive(Debug)]
pub struct User {
    id: UserId,
    name: String
}

#[derive(Debug)]
pub struct Customer {
    id: CustomerId,
    name: String
}

pub type UserId = TypedId<u32, User>;
pub type CustomerId = TypedId<u32, Customer>;

構造体のインスタンスを作成してassertで比較してみます。
UserとCustomerのidはおなじ値でも、
比較すると違うものとして扱われます。

fn main() {

    let id = 10;

let user = User { 
        id: id.into(), 
        name: String::from("taro")
    };
    let customer = Customer { 
        id: id.into(), 
        name: String::from("customer")
    };

    assert_eq!(id, *user.id);
    assert_eq!(id, *customer.id);
    assert_eq!(*user.id,*customer.id);
}

id_type!マクロを使えば、各Id型をtypeで定義する必要はありません。

use typed_id::id_type;

pub struct Order { id: OrderId }

//pub type OrderId = TypedId<u32, Order> と同じ意味
id_type!(u32, Order);

References