Skip to content

Commit 01c93c9

Browse files
authored
Update discussion of boxed protocol types [SE-0335] (#103)
- In the Protocols chapter, replace the discussion of existentials with a summary of the ways you can use a protocol as a type, linking forward to the full discussions of generics and existentials and opaque types. - Move the discussion of existentials from Protocols to Opaque Types, renaming that chapter to Opaque and Boxed Types. - Add a new Boxed Protocol Type section in the reference, to discuss the `any` keyword. Pitch thread: https://forums.swift.org/t/61665 Fixes: rdar://88208011
2 parents cd96b9c + 4754b42 commit 01c93c9

File tree

5 files changed

+295
-133
lines changed

5 files changed

+295
-133
lines changed

TSPL.docc/GuidedTour/GuidedTour.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1971,11 +1971,11 @@ You can use a protocol name just like any other named type ---
19711971
for example, to create a collection of objects
19721972
that have different types
19731973
but that all conform to a single protocol.
1974-
When you work with values whose type is a protocol type,
1974+
When you work with values whose type is a boxed protocol type,
19751975
methods outside the protocol definition aren't available.
19761976

19771977
```swift
1978-
let protocolValue: ExampleProtocol = a
1978+
let protocolValue: any ExampleProtocol = a
19791979
print(protocolValue.simpleDescription)
19801980
// Prints "A very simple class. Now 100% adjusted."
19811981
// print(protocolValue.anotherProperty) // Uncomment to see the error

TSPL.docc/LanguageGuide/OpaqueTypes.md

Lines changed: 154 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,28 @@
1-
# Opaque Types
1+
# Opaque and Boxed Types
22

33
Hide implementation details about a value's type.
44

5-
A function or method with an opaque return type
6-
hides its return value's type information.
7-
Instead of providing a concrete type as the function's return type,
8-
the return value is described in terms of the protocols it supports.
5+
Swift provides two ways to hide details about a value's type:
6+
opaque types and boxed protocol types.
97
Hiding type information
108
is useful at boundaries between
119
a module and code that calls into the module,
1210
because the underlying type of the return value can remain private.
13-
Unlike returning a value whose type is a protocol type,
14-
opaque types preserve type identity ---
11+
12+
A function or method that returns an opaque type
13+
hides its return value's type information.
14+
Instead of providing a concrete type as the function's return type,
15+
the return value is described in terms of the protocols it supports.
16+
Opaque types preserve type identity ---
1517
the compiler has access to the type information,
1618
but clients of the module don't.
1719

20+
A boxed protocol type can store an instance of any type
21+
that conforms to the given protocol.
22+
Boxed protocol types don't preserve type identity ---
23+
the value's specific type isn't known until runtime,
24+
and it can change over time as different values are stored.
25+
1826
## The Problem That Opaque Types Solve
1927

2028
For example,
@@ -484,24 +492,153 @@ the return value always has the same underlying type of `[T]`,
484492
so it follows the requirement that functions with opaque return types
485493
must return values of only a single type.
486494

487-
## Differences Between Opaque Types and Protocol Types
495+
## Boxed Protocol Types
496+
497+
A boxed protocol type is also sometimes called an *existential type*,
498+
which comes from the phrase
499+
"there exists a type *T* such that *T* conforms to the protocol".
500+
To make a boxed protocol type,
501+
write `any` before the name of a protocol.
502+
Here's an example:
503+
504+
```swift
505+
struct VerticalShapes: Shape {
506+
var shapes: [any Shape]
507+
func draw() -> String {
508+
return shapes.map { $0.draw() }.joined(separator: "\n\n")
509+
}
510+
}
511+
512+
let largeTriangle = Triangle(size: 5)
513+
let largeSquare = Square(size: 5)
514+
let vertical = VerticalShapes(shapes: [largeTriangle, largeSquare])
515+
print(vertical.draw())
516+
```
517+
518+
<!--
519+
- test: `boxed-protocol-types`
520+
521+
```swifttest
522+
>> protocol Shape {
523+
>> func draw() -> String
524+
>> }
525+
>> struct Triangle: Shape {
526+
>> var size: Int
527+
>> func draw() -> String {
528+
>> var result: [String] = []
529+
>> for length in 1...size {
530+
>> result.append(String(repeating: "*", count: length))
531+
>> }
532+
>> return result.joined(separator: "\n")
533+
>> }
534+
>> }
535+
>> struct Square: Shape {
536+
>> var size: Int
537+
>> func draw() -> String {
538+
>> let line = String(repeating: "*", count: size)
539+
>> let result = Array<String>(repeating: line, count: size)
540+
>> return result.joined(separator: "\n")
541+
>> }
542+
>
543+
-> struct VerticalShapes: Shape {
544+
var shapes: [any Shape]
545+
func draw() -> String {
546+
return shapes.map { $0.draw() }.joined(separator: "\n\n")
547+
}
548+
}
549+
->
550+
-> let largeTriangle = Triangle(size: 5)
551+
-> let largeSquare = Square(size: 5)
552+
-> let vertical = VerticalShapes(shapes: [largeTriangle, largeSquare])
553+
-> print(vertical.draw())
554+
<< *
555+
<< **
556+
<< ***
557+
<< ****
558+
<< *****
559+
<<-
560+
<< *****
561+
<< *****
562+
<< *****
563+
<< *****
564+
<< *****
565+
```
566+
-->
567+
568+
In the example above,
569+
`VerticalShapes` declares the type of `shapes` as `[any Shape]` ---
570+
an array of boxed `Shape` elements.
571+
Each element in the array can be a different type,
572+
and each of those types must conform to the `Shape` protocol.
573+
To support this runtime flexibility,
574+
Swift adds a level of indirection when necessary ---
575+
this indirection is called a *box*,
576+
and it has a performance cost.
577+
578+
Within the `VerticalShapes` type,
579+
the code can use methods, properties, and subscripts
580+
that are required by the `Shape` protocol.
581+
For example, the `draw()` method of `VerticalShapes`
582+
calls the `draw()` method on each element of the array.
583+
This method is available because `Shape` requires a `draw()` method.
584+
In contrast,
585+
trying to access the `size` property of the triangle,
586+
or any other properties or methods that aren't required by `Shape`,
587+
produces an error.
588+
589+
Contrast the three types you could use for `shapes`:
590+
591+
- Using generics,
592+
by writing `struct VerticalShapes<S: Shape>` and `var shapes: [S]`,
593+
makes an array whose elements are some specific shape type,
594+
and where the identity of that specific type
595+
is visible to any code that interacts with the array.
596+
597+
- Using an opaque type,
598+
by writing `var shapes: [some Shape]`,
599+
makes an array whose elements are some specific shape type,
600+
and where that specific type's identify is hidden.
601+
602+
- Using a boxed protocol type,
603+
by writing `var shapes: [any Shape]`,
604+
makes an array that can store elements of different types,
605+
and where those types' identities are hidden.
606+
607+
In this case,
608+
a boxed protocol type is the only approach
609+
that lets callers of `VerticalShapes` mix different kinds of shapes together.
610+
611+
You can use an `as` cast
612+
when you know the underlying type of a boxed value.
613+
For example:
614+
615+
```swift
616+
if let downcastTriangle = vertical.shapes[0] as? Triangle {
617+
print(downcastTriangle.size)
618+
}
619+
// Prints "5"
620+
```
621+
622+
For more information, see <doc:TypeCasting#Downcasting>.
623+
624+
## Differences Between Opaque Types and Boxed Protocol Types
488625

489626
Returning an opaque type looks very similar
490-
to using a protocol type as the return type of a function,
627+
to using a boxed protocol type as the return type of a function,
491628
but these two kinds of return type differ in
492629
whether they preserve type identity.
493630
An opaque type refers to one specific type,
494631
although the caller of the function isn't able to see which type;
495-
a protocol type can refer to any type that conforms to the protocol.
632+
a boxed protocol type can refer to any type that conforms to the protocol.
496633
Generally speaking,
497-
protocol types give you more flexibility
634+
boxed protocol types give you more flexibility
498635
about the underlying types of the values they store,
499636
and opaque types let you make stronger guarantees
500637
about those underlying types.
501638

502639
For example,
503640
here's a version of `flip(_:)`
504-
that uses a protocol type as its return type
641+
that uses a boxed protocol type as its return type
505642
instead of an opaque return type:
506643

507644
```swift
@@ -622,19 +759,19 @@ but adding a `Self` requirement to the protocol
622759
doesn't allow for the type erasure that happens
623760
when you use the protocol as a type.
624761

625-
Using a protocol type as the return type for a function
762+
Using a boxed protocol type as the return type for a function
626763
gives you the flexibility to return any type that conforms to the protocol.
627764
However, the cost of that flexibility
628765
is that some operations aren't possible on the returned values.
629766
The example shows how the `==` operator isn't available ---
630767
it depends on specific type information
631-
that isn't preserved by using a protocol type.
768+
that isn't preserved by using a boxed protocol type.
632769

633770
Another problem with this approach is that the shape transformations don't nest.
634771
The result of flipping a triangle is a value of type `Shape`,
635772
and the `protoFlip(_:)` function takes an argument
636773
of some type that conforms to the `Shape` protocol.
637-
However, a value of a protocol type doesn't conform to that protocol;
774+
However, a value of a boxed protocol type doesn't conform to that protocol;
638775
the value returned by `protoFlip(_:)` doesn't conform to `Shape`.
639776
This means code like `protoFlip(protoFlip(smallTriangle))`
640777
that applies multiple transformations is invalid
@@ -644,7 +781,7 @@ In contrast,
644781
opaque types preserve the identity of the underlying type.
645782
Swift can infer associated types,
646783
which lets you use an opaque return value
647-
in places where a protocol type can't be used as a return value.
784+
in places where a boxed protocol type can't be used as a return value.
648785
For example,
649786
here's a version of the `Container` protocol from <doc:Generics>:
650787

@@ -793,3 +930,4 @@ Licensed under Apache License v2.0 with Runtime Library Exception
793930
See https://swift.org/LICENSE.txt for license information
794931
See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
795932
-->
933+

0 commit comments

Comments
 (0)