Why isn’t UnsignedInteger.Magnitude constrained to equal Self?

The UnsignedInteger protocol is declared like this:

public protocol UnsignedInteger: BinaryInteger { }

And its documentation says:

  /// Every unsigned integer is its own magnitude, so for any value `x`,
  /// `x == x.magnitude`.

So, why isn’t the protocol instead declared:

public protocol UnsignedInteger: BinaryInteger where Magnitude == Self { }

In order to guarantee that relationship?

• • •

I was just writing some generic code similar to this:

func foo<T: UnsignedInteger>(_ x: T) -> T {
  let y = x.magnitude
  return x + y
  // error: Binary operator '+' cannot be applied to operands of type 'T' and 'T.Magnitude'

And got the error shown in the comment. Thus my question: why isn’t UnsignedInteger.Magnitude constrained to equal Self?


Would it be ABI-compatible to add the constraint “where Magnitude == Self” to the declaration of the UnsignedInteger protocol?

• • •

To clarify my use-case, in a context constrained to T: FixedWidthInteger & UnsignedInteger, I am working with the result of a call to multipliedFullWidth(by:). That function returns a tuple of (high: T, low: T.Magnitude).

In order to perform further calculations, I currently need to write T(low) (or to avoid extra checks, T(truncatingIfNeeded: low)).

But, because T conforms to UnsignedInteger, we (as programmers) know that low is actually already of type T, since a correctly-implemented UnsignedInteger type must use Self as Magnitude.

Unfortunately the compiler does not know this, so the redundant type conversion is necessary. Can we fix this?

It's not API-compatible: someone might have implemented BadUInt64 with a Magnitude of UInt64.

(It's also not ABI-compatible; it will break any generic that adds that extra condition itself. But in general, if something's not API-compatible, you can assume it's not ABI-compatible, though there are a few rare cases where you can get away with it.)

My best guess for why this is: it's possible UnsignedInteger predates the ability to have protocol-level where clauses, and it didn't get fixed in time for ABI stability. But I didn't go back through the history or anything.


Sure, but going by existing Swift Evolution precedent, since BadUInt64 violates the documented semantics of the protocol, it is an invalid conformance.

All correct conformances would keep working, so formalizing the existing documented semantics in a constraint should be considered source-compatible.

Dang. Is there any way to patch that up so the runtime still handles the generics you describe the way it does now, but the compiler learns the new constraint as well?

That is, when compiling code, a generic declaration which constrains UnsignedInteger.Magnitude == Self would compile exactly as it does today, and the runtime would continue treating it the same way.

But code which does not explicitly include that constraint, would be able to take advantage of the compiler’s newly-gained knowledge that Magnitude == Self, so it could emit code that benefits from knowing the types are the same.


  /// Every unsigned integer is its own magnitude, so for any value `x`,
  /// `x == x.magnitude`.

If you interpret this to mean "x.magnitude is interchangeable with x", then yes, it'd be an invalid conformance. But if you interpret it as "x.magnitude has the same value as x", then it'd be okay.

Unfortunately, I'm not sure of all the implications of making such a change even if it was considered a bug fix. The function mangling thing was the simplest thing I could think of, but there's backwards- and forwards-compatibility concerns here. Someone more runtime-savvy than me would have to think this through.

1 Like

If x is of a class type, the requirement x == x.magnitude would be satisfied in all respects if x.magnitude is of a subclass. But Magnitude == Self would cause such a valid conformance not to compile.