Swift で値クラス的なのを実現したい!

Swift で API/DB から取得した値の ID 型指定はたぶん通常 StringInt などを指定するのが一般的なのかな?と思う。 メソッド等に ID を渡すときに、プリミティブ型だと渡したい ID を取り違えたときにスッとコンパイル通ってしまって困る。

Scala には値クラスっていうのが存在して、ある型に別名を付けて、それと区別するみたいなことが出来る(理解が曖昧かつ雑)。

値クラスと汎用トレイト | Scala Documentation

要は、以下のようなことがしたい。

struct UserID: AnyVal {
  let value: String
}
struct MediaID: AnyVal {
  let value: String
}

var userID = "1234"
let mediaID = "4321"

// ↓ここでコンパイルエラーが出て欲しい
userID = mediaID

なんかそういうことするの難しそうで、とりあえずメソッド等の定義に使えればいいやということで、typealias でお茶を濁していた。

だけどメソッドの引数に指定した UserID にミスって MediaID を渡してしまってもコンパイル通ってしまう。 テストも人間が書くものなので100%信用できるわけではない。

ということで、いろいろ調べてそれっぽいことが出来そうなコードを書いてみた。 さっきそれっぽいところまで行ったので、まだ開発中のアプリで使ったりはしていない。

struct UserID: ExpressibleByStringLiteral, CustomStringConvertible, CustomDebugStringConvertible, RawRepresentable, Hashable, Equatable, Codable {
    private(set) var rawValue: String

    var description: String { return rawValue }
    var debugDescription: String { return "\(String(describing: type(of: self)))(\(rawValue))" }

    init(stringLiteral value: String) {
        self.rawValue = value
    }

    init?(rawValue: String) {
        self.rawValue = rawValue
    }
}

struct MediaID: ExpressibleByStringLiteral, CustomStringConvertible, CustomDebugStringConvertible, RawRepresentable, Hashable, Equatable, Codable {
    private(set) var rawValue: String

    var description: String { return rawValue }
    var debugDescription: String { return "\(String(describing: type(of: self)))(\(rawValue))" }

    init(stringLiteral value: String) {
        self.rawValue = value
    }

    init?(rawValue: String) {
        self.rawValue = rawValue
    }
}

var userID: UserID = "1"
print(userID) // => 1

userID = "2"
print(userID) // => 2
print(userID.debugDescription) // => UserID(2)

// ↓Error🎉
// let mediaID: MediaID = "2"
// userID = mediaID

print(userID == "0") // => false
print(userID == "1") // => false
print(userID == "2") // => true

let string: String = userID.rawValue
print(string) // => 2

// Equatable 追加すれば以下も可能
// print(userID == UserID(rawValue: "2")) // => true

一応出来たぞ〜〜〜って感じでこれ以上書くことが無いんだけど、いい方法知ってたりしたら教えてください!!!!!!!!!!!!!!!!!!!!!!