Why can't I change the value of an opaque type?

213 Views Asked by At

Is there a reason the compiler won't let me change the value of an opaque type, but will allow it with an existential? Is there a reason for it, or is it a limitation that will be removed in the future?

enter image description here

1

There are 1 best solutions below

0
On

From The Swift Programming Language on Opaque and Boxed Types:

A function or method that returns an opaque type hides its return value’s type information. Instead of providing a concrete type as the function’s return type, the return value is described in terms of the protocols it supports. Opaque types preserve type identity — the compiler has access to the type information, but clients of the module don’t.

A boxed protocol type can store an instance of any type that conforms to the given protocol. Boxed protocol types don’t preserve type identity — the value’s specific type isn’t known until runtime, and it can change over time as different values are stored.

In Swift, x: some P means that x has a single, concrete type which the compiler knows about, but that anyone outside of the original expression that defines x can't get access to; the type is known statically at compile-time, but hidden, and the only information you can use about the type is its conformance to P.

In contrast, x: any P means that x can have any type (so long as that type conforms to P), and the type is dynamic and only known at runtime.

The latter means that reassignment to x: any P can safely be done, so long as the new value conforms to P; in your example, anyEquatable initially has a value of 1: Int, and is later reassigned a value of "one": String; this does not break the contract of anyEquatable claiming that it can take on any value so long as the value conforms to Equatable.

When it comes to opaque types, however, consider the following:

var x: Bool = true
x = 1.2 // ❌ Cannot assign value of type 'Double' to type 'Bool'

Swift, being a strongly-, statically-typed language, doesn't allow you to assign values to variables which don't satisfy their type. This needs to remain true even when the type is opaque:

var x: some Equatable /* Bool */ = true
x = 1.2 // ❌ Cannot assign value of type 'Double' to type 'some Equatable'

Even though the type of x is hidden outside of the original expression defining x, the compiler still knows the type, and still knows that assignments to it may not be valid.

This holds for your example, too:

var someEquatable: some Equatable /* Int */ = 1
someEquatable = 2 // ❌ Cannot assign value of type 'Int' to type 'some Equatable'

Even though the compiler knows that the type of someEquatable is Int, it still doesn't allow you to assign to it because any expression outside of the original definition of someEquatable can't know what the type is, so theoretically can't assign to it validly.


From a language design perspective, the compiler could theoretically still allow you to assign someEquatable = 2, because it knows the assignment is valid, but this would lead to an inconsistent experience: some some Ps you could assign to, but others you couldn't, with no way of knowing why or any recourse to solve it. (And because the types are intentionally hidden, the compiler wouldn't necessarily be able to explain to you why an assignment is invalid.)

Right now, the rules are applied consistently, which means that even on the very next line inside the same scope, you can't reassign to a some P type after it's been defined.