英文:
Is it possible to limit init values for ExpressibleByIntegerLiteral struct?
问题
我想实现一个Digit结构体,它应该可以通过整数字面量进行初始化。
像这样:
let digit: Digit = 5
但是Swift不应该允许这样做(或者作为选项,应该引发异常):
let digit: Digit = 15
我写了这段代码,但它看起来不太好:
struct Digit: ExpressibleByIntegerLiteral, Equatable, CustomStringConvertible {
typealias IntegerLiteralType = Int
var description: String {
String(value)
}
var value: IntegerLiteralType {
// Unfortunately, set throws can't be implemented, only get throws.
willSet(newValue) {
if 0...9 ~= value {
self.value = newValue
}
}
}
init(integerLiteral: IntegerLiteralType) {
if 0...9 ~= integerLiteral {
value = integerLiteral
} else {
// This is definitely not good and may cause side effects.
// But init for ExpressibleByIntegerLiteral can't be failable.
value = 0
}
}
init?(from char: Character?) {
guard let char,
let newValue = IntegerLiteralType(String(char)),
0...9 ~= newValue
else {
return nil
}
value = newValue
}
}
在Swift 5.8中,我该如何解决这个问题?我猜在下一个版本中可以使用宏来实现这一点,但不幸的是,我的Swift版本仍然没有宏。
谢谢。
英文:
I want to implement Digit struct which should be initialized by integer literal.
Like:
let digit: Digit = 5
But swift shouldn't allow doing this (or, as an option, an exception should be raised):
let digit: Digit = 15
I wrote this code but it's not looking good:
struct Digit: ExpressibleByIntegerLiteral, Equatable, CustomStringConvertible {
typealias IntegerLiteralType = Int
var description: String {
String(value)
}
var value: IntegerLiteralType {
// Unfortunately, set throws can't be implemented, only get throws.
willSet(newValue) {
if 0...9 ~= value {
self.value = newValue
}
}
}
init(integerLiteral: IntegerLiteralType) {
if 0...9 ~= integerLiteral {
value = integerLiteral
} else {
// This is definitely not good and may cause side effects.
// But init for ExpressibleByIntegerLiteral can't be failable.
value = 0
}
}
init?(from char: Character?) {
guard let char,
let newValue = IntegerLiteralType(String(char)),
0...9 ~= newValue
else {
return nil
}
value = newValue
}
}
How may I overcome this in Swift 5.8? I guess in next version Macros may be used for this but unfortunately I my Swift version still doesn't have them..
Thank you.
答案1
得分: 1
无法为除0-9之外的所有数字生成编译器错误,您最好能够最大限度地扩展会导致编译器错误的数字范围,方法是使用最小的 IntegerLiteralType
,并对其他数字执行 fatalError
。
struct Digit: ExpressibleByIntegerLiteral {
let value: UInt8
init(integerLiteral value: UInt8) {
guard (0...9).contains(value) else {
fatalError("Digit must be between 0 and 9!")
}
self.value = value
}
}
由于我使用了 UInt8
作为 IntegerLiteralType
,因此像这样的情况将无法编译通过:
let x: Digit = 1000
而像这样的情况会触发 fatalError
:
let x: Digit = 10
由于只有10个有效值,您可以考虑的另一种方法是使用枚举。枚举情况不能以数字开头,所以您需要在前面加上一些内容:
enum Digit: UInt8 {
case _0
case _1
case _2
case _3
case _4
case _5
case _6
case _7
case _8
case _9
}
英文:
Producing an compiler error for all numbers except 0-9 is not possible, the best you can do is to maximise the range of numbers that would produce a compiler error, by using the smallest IntegerLiteralType
, and do fatalError
for other numbers.
struct Digit: ExpressibleByIntegerLiteral {
let value: UInt8
init(integerLiteral value: UInt8) {
guard (0...9).contains(value) else {
fatalError("Digit must be between 0 and 9!")
}
self.value = value
}
}
Since I used UInt8
as the IntegerLiteralType
, things like this would not compile:
let x: Digit = 1000
And things like this would fatalError
:
let x: Digit = 10
<hr>
Since there are only 10 valid values for this, an alternative you could consider is using an enum
. Enum cases can't start with digits, so you'd need something in front:
enum Digit: UInt8 {
case _0
case _1
case _2
case _3
case _4
case _5
case _6
case _7
case _8
case _9
}
答案2
得分: 0
由于 Sweeper 的帮助,我使用前置条件编写了以下代码,以便即使在生产环境中也会在不良情况下崩溃:
fileprivate let wrongDigitError = "Fatal error: digit should be between 0 and 9!"
struct Digit: ExpressibleByIntegerLiteral, Equatable, CustomStringConvertible {
typealias IntegerLiteralType = UInt8
var description: String {
String(value)
}
var value: IntegerLiteralType {
willSet(newValue) {
precondition(0...9 ~= value, wrongDigitError)
self.value = newValue
}
}
init(integerLiteral: IntegerLiteralType) {
precondition(0...9 ~= integerLiteral, wrongDigitError)
self.value = integerLiteral
}
init?(from char: Character?) {
guard let char,
let newValue = IntegerLiteralType(String(char)),
0...9 ~= newValue
else {
return nil
}
value = newValue
}
}
请注意,我没有翻译代码,只返回了代码的原文。
英文:
Thanks to Sweeper, I wrote the code this way using precondition so it will crash in bad cases even in production:
fileprivate let wrongDigitError = "Fatal error: digit should be between 0 and 9!"
struct Digit: ExpressibleByIntegerLiteral, Equatable, CustomStringConvertible {
typealias IntegerLiteralType = UInt8
var description: String {
String(value)
}
var value: IntegerLiteralType {
willSet(newValue) {
precondition(0...9 ~= value, wrongDigitError)
self.value = newValue
}
}
init(integerLiteral: IntegerLiteralType) {
precondition(0...9 ~= integerLiteral, wrongDigitError)
self.value = integerLiteral
}
init?(from char: Character?) {
guard let char,
let newValue = IntegerLiteralType(String(char)),
0...9 ~= newValue
else {
return nil
}
value = newValue
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论