Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

Is it possible to limit init values for ExpressibleByIntegerLiteral struct?

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):

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

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.

>Solution :

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

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
}
Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading