Why does a for loop range need a signed integer?

This doesn't seem right. Is it a bug?

Error: Protocol 'Sequence' requires that 'N.Stride' conform to 'SignedInteger'

class Foo<N: UnsignedInteger> {
    let n: N
    init(n: N) { self.n = n }
    func bar() {
        for i in 1...n {  // Error here
            print("i = \(n)")
        }
    }
}
1 Like

That error message is not helpful. Probably worth filing a bug about the diagnostic alone.

After all, the operator itself works just fine. You can write let range = 1...n and it will compile.

The problem is that ranges only conform to Sequence when their bounds are strideable with a signed-integer stride.

I am not sure why it’s implemented that way. It seems like an unnecessary restriction.

It's worth noting that the error message does have an attached note to this effect:

note: requirement from conditional conformance of 'ClosedRange<N>' to 'Sequence'
extension ClosedRange : Sequence where Bound : Strideable, Bound.Stride : SignedInteger {
          ^

I do agree though that it's not clear from the error message where the Sequence requirement came from.

2 Likes

To stride through a range, one needs to use advanced(by:), which takes a signed integer value. This poses no practical restriction: semantically, all strideable numeric types (whether signed or unsigned) must have a signed Stride because the type must be able to represent both positive and negative distances (since the distance from a larger positive number to a smaller positive number is negative). And indeed, all concrete unsigned integer types in the standard library have signed Stride types.

If I recall, the reason that the protocol itself, UnsignedInteger, doesn't happen to require a signed Stride is that the ability to express such a constraint in Swift was not worked out in time for this to be changed before ABI stability. So, it is just a matter of writing class Foo<N: UnsignedInteger where N.Stride: SignedInteger> { ... } here and similar boilerplate elsewhere.

3 Likes