如何在Swift中实现类型安全的索引?

huangapple go评论67阅读模式
英文:

How to do type-safe indices in Swift?

问题

I understand you want a translation for the code-related portion. Here it is:

我试图做类似于这样的事情:

    typealias HumanId = Int 
    typealias RobotId = Int
    
    func getHuman(at index: HumanId) -> Human
    func getRobot(at index: RobotId) -> Robot

但是现在我可以使用`RobotId`正常调用`getHuman`:`getHuman(at: RobotId(0))`。

如何使这个类型安全?
我明白我可以这样做:

    struct HumanId { let id: Int }
    struct RobotId { let id: Int }

...并且一些额外的东西让这些结构体作为索引运作,但这将导致一些代码重复,而且由于我有不止2种这样的id类型,我希望以某种方式缩短它,也许使用类型别名和泛型来使它们唯一?
英文:

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?

答案1

得分: 6

你可以利用Swift的泛型来实现你的目标。定义一个泛型的Index类型,代码如下:

struct Index<T>: RawRepresentable {
    let rawValue: Int
    init(rawValue: Int) { self.rawValue = rawValue }
    init(_ rawValue: Int) { self.rawValue = rawValue }
}

然后可以像这样使用它:

func getHuman(at index: Index<Human>) -> Human { ... }
func getRobot(at index: Index<Robot>) -> Robot { ... }

getHuman(at: Index(1))
getRobot(at: Index(2))

文字索引

你甚至可以使用ExpressibleByIntegerLiteral协议为文字索引提供一些语法糖:

extension Index: ExpressibleByIntegerLiteral {
    public init(integerLiteral value: Int) { self.rawValue = value }
}

例如:

getHuman(at: 1)
getRobot(at: 2)

但是下面的代码将无法构建,所以解决方案仍然是类型安全的:

let someIndex = 123
getHuman(at: someIndex)

错误: 无法将类型为'int'的值转换为预期的参数类型'Index<Human>'

可比较的索引

如评论中建议的,我们还可以添加Comparable遵守(例如,这样你可以将Index结构体用作遵守标准Collection协议的类型的索引):

extension Index: Comparable {
    static func < (lhs: Index, rhs: Index) -> Bool {
        lhs.rawValue < rhs.rawValue
    }
}

示例:

Index<Human>(1) < Index<Human>(2) // true
英文:

You could leverage Swift generics to achieve your goal. Introduce a generic Index type like this:

struct Index&lt;T&gt;: 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&lt;Human&gt;) -&gt; Human { ... }
func getRobot(at index: Index&lt;Robot&gt;) -&gt; Robot { ... }

getHuman(at: Index(1))
getRobot(at: Index(2))

Literal Indices

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 &#39;Int&#39; to expected argument type &#39;Index&lt;Human&gt;&#39;

Comparable Indices

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 &lt; (lhs: Index, rhs: Index) -&gt; Bool {
        lhs.rawValue &lt; rhs.rawValue
    }
}

Example:

Index&lt;Human&gt;(1) &lt; Index&lt;Human&gt;(2) // true

huangapple
  • 本文由 发表于 2023年5月15日 06:10:58
  • 转载请务必保留本文链接:https://go.coder-hub.com/76249890.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定