I'm trying to do something like this:
typealias HumanId = Int
typealias RobotId = Int
func getHuman(at index: HumanId) -> Human
func getRobot(at index: RobotId) -> Robot
but as it is now I can call getHuman
with RobotId
just fine: getHuman(at: RobotId(0))
.
How do I make this typesafe?
I understand that I can do something like:
struct HumanId { let id: Int }
struct RobotId { let id: Int }
...and some extra things to make these structs function as indices, but that would lead to some code duplication, and since I'm having more than 2 of these id-types I would like to shorten this somehow, with typealiases and generics perhaps in order to make them unique?
You could leverage Swift generics to achieve your goal. Introduce a generic Index
type like this:
struct Index<T>: RawRepresentable {
let rawValue: Int
init(rawValue: Int) { self.rawValue = rawValue }
init(_ rawValue: Int) { self.rawValue = rawValue }
}
and then use it like this:
func getHuman(at index: Index<Human>) -> Human { ... }
func getRobot(at index: Index<Robot>) -> Robot { ... }
getHuman(at: Index(1))
getRobot(at: Index(2))
You could even use the ExpressibleByIntegerLiteral
protocol to provide some syntax sugar when using literal indices:
extension Index: ExpressibleByIntegerLiteral {
public init(integerLiteral value: Int) { self.rawValue = value }
}
For instance:
getHuman(at: 1)
getRobot(at: 2)
But the following code will not build, so the solution is still typesafe-ish:
let someIndex = 123
getHuman(at: someIndex)
error: cannot convert value of type 'Int' to expected argument type 'Index<Human>'
As suggested in the comments, we could also add Comparable
conformance as well (e.g., so you can use the Index
struct as the index in a type conforming to the standard Collection
protocol):
extension Index: Comparable {
static func < (lhs: Index, rhs: Index) -> Bool {
lhs.rawValue < rhs.rawValue
}
}
Example:
Index<Human>(1) < Index<Human>(2) // true