Prevent using integer initializers that can generate runtime errors

It is very easy to use the integer extensions that initialize a type with another that cannot always be represented, but then not fully understand that these initializers aren't always "safe", and that when they fail they generate a runtime error, crashing the process.

For example:

let a = Int32(-1)
let b = UInt32(a)  // "Fatal error: Negative value is not representable"

And:

let a = UInt64(0xFF00000000000000)
let b = UInt32(a)  // "Fatal error: Not enough bits to represent the passed value"

Is there a way to prevent using these initializers at compile time? The sources show these are preconditions and from what I found only -0unchecked disables these, but this sounds like it disables other good things that generally shouldn't be turned off.

Unfortunately I don't think a linter can help since the types need to be fully understood, as these initializers can also be used with imported C types as aliases.

One idea I imagine could be to annotate these functions and provide a compiler flag to prevent using them.

2 Likes

This is one of those things that look easy to do during compilation, but it is not.

func f () -> UInt64 {
    UInt64(7)
}

func g () -> UInt64 {
    h ()
}

func h () -> UInt64 {
    UInt64(0xFF00000000000000)
}

let u = f ()
let v = UInt32(u) // Ok
        
let u = g ()
let v = UInt32(u) // Fatal error: Not enough bits to represent the passed value

Even if the compiler can see the definition of all those functions, solving this problem may still prove hard.

1 Like

I was thinking along the lines of how Rust does it, where (as far as I know) there is no safe way to convert a i32 to u32, but it has try_from which can fail, yet for u32 -> u64 they have from which cannot. There is no need to have special knowledge that a function could fail at runtime, unlike Swift, which has a combination of these functions. It seems like it could be a good fit alongside the strict memory safety proposal potentially.