Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is a 'non-nominal type' in Swift?

Tags:

swift

I see this error when trying to extend unorthodox 'types' like (Int, Int), or Any:

Non-nominal type 'Any' cannot be extended

So what makes a type non-nominal? What's the difference between a non-nominal type like Any or (Int) and a regular nominal type like Int?

like image 903
Joseph Mark Avatar asked Jun 10 '14 01:06

Joseph Mark


1 Answers

The currently accepted answer is incorrect; the actual answer comes from a deep and esoteric part of Swift's type system.

Most types in Swift are nominal types—they're "named" types declared in a particular module as part of user code. Seemingly primitive types like Int and Array are actually structs defined in the Swift module, which is automatically imported into every Swift source file. Even Optional itself is an enum in the Swift module, although the compiler adds a little bit of magic like optional chaining and force-unwrapping.

The few exceptions are called "non-nominal types". There may be others, but the main ones are:

  • Function types, like (Int) -> String
  • Tuple types, like (Int, String)
  • Metatypes, like String.Type (the type of the expression String.self)
  • Existentials, like CustomStringConvertible & Error

Existentials deserve a little more explanation. An existential contains a value whose exact type has been abstracted away, but which is known to conform to a certain set of protocols. For example:

// You can put anything that conforms to `CustomStringConvertible` in `x` let x: CustomStringConvertible  // You can put anything that conforms to both `CustomStringConvertible`  // and `Error` in `y` let y: CustomStringConvertible & Error  // You can put anything in `z`; `Any` is an existential that doesn't // require you to conform to any particular protocols let z: Any 

Non-nominal types all simply combine other types together in some ad-hoc way. (They're sometimes called "structural types" because they define some kind of general structure for other types to slot into.) They don't need to be explicitly defined, and they don't belong to any particular module—FooKit's (Int, String) is the same as BarKit's (Int, String). But all of their behavior is defined by the language—a non-nominal type can't have methods or properties of its own, can't conform to protocols, and therefore can't be extended.

So you can't extend Any because Any is a special thing that's built into the language, just like a function type or a tuple type. It just happens to have a name written in letters, not in punctuation.

(So why can you extend CustomStringConvertible? Because, depending on context, CustomStringConvertible might mean the protocol or it might mean an existential containing a value conforming to the protocol. When you write extension CustomStringConvertible, you're extending the protocol, but when you write let x: CustomStringConvertible, you're declaring a variable whose type is "existential containing a value conforming to the protocol". This is kind of confusing, and some of Swift's maintainers would actually like to require that the existential be written as Any<CustomStringConvertible> to make it more clear. Not terribly likely to happen now that they're trying to preserve source stability, though.)

like image 145
Becca Royal-Gordon Avatar answered Oct 04 '22 23:10

Becca Royal-Gordon