What is the `some` keyword in Swift(UI)?
some View
is an opaque result type as introduced by SE-0244 and is available in Swift 5.1 with Xcode 11. You can think of this as being a "reverse" generic placeholder.
Unlike a regular generic placeholder which is satisfied by the caller:
protocol P {}
struct S1 : P {}
struct S2 : P {}
func foo<T : P>(_ x: T) {}
foo(S1()) // Caller chooses T == S1.
foo(S2()) // Caller chooses T == S2.
An opaque result type is an implicit generic placeholder satisfied by the implementation, so you can think of this:
func bar() -> some P {
return S1() // Implementation chooses S1 for the opaque result.
}
as looking like this:
func bar() -> <Output : P> Output {
return S1() // Implementation chooses Output == S1.
}
In fact, the eventual goal with this feature is to allow reverse generics in this more explicit form, which would also let you add constraints, e.g -> <T : Collection> T where T.Element == Int
. See this post for more info.
The main thing to take away from this is that a function returning some P
is one that returns a value of a specific single concrete type that conforms to P
. Attempting to return different conforming types within the function yields a compiler error:
// error: Function declares an opaque return type, but the return
// statements in its body do not have matching underlying types.
func bar(_ x: Int) -> some P {
if x > 10 {
return S1()
} else {
return S2()
}
}
As the implicit generic placeholder cannot be satisfied by multiple types.
This is in contrast to a function returning P
, which can be used to represent both S1
and S2
because it represents an arbitrary P
conforming value:
func baz(_ x: Int) -> P {
if x > 10 {
return S1()
} else {
return S2()
}
}
Okay, so what benefits do opaque result types -> some P
have over protocol return types -> P
?
1. Opaque result types can be used with PATs
A major current limitation of protocols is that PATs (protocols with associated types) cannot be used as actual types. Although this is a restriction that will likely be lifted in a future version of the language, because opaque result types are effectively just generic placeholders, they can be used with PATs today.
This means you can do things like:
func giveMeACollection() -> some Collection {
return [1, 2, 3]
}
let collection = giveMeACollection()
print(collection.count) // 3
2. Opaque result types have identity
Because opaque result types enforce a single concrete type is returned, the compiler knows that two calls to the same function must return two values of the same type.
This means you can do things like:
// foo() -> <Output : Equatable> Output {
func foo() -> some Equatable {
return 5 // The opaque result type is inferred to be Int.
}
let x = foo()
let y = foo()
print(x == y) // Legal both x and y have the return type of foo.
This is legal because the compiler knows that both x
and y
have the same concrete type. This is an important requirement for ==
, where both parameters of type Self
.
protocol Equatable {
static func == (lhs: Self, rhs: Self) -> Bool
}
This means that it expects two values that are both the same type as the concrete conforming type. Even if Equatable
were usable as a type, you wouldn't be able to compare two arbitrary Equatable
conforming values with each other, for example:
func foo(_ x: Int) -> Equatable { // Assume this is legal.
if x > 10 {
return 0
} else {
return "hello world"
}
}
let x = foo(20)
let y = foo(5)
print(x == y) // Illegal.
As the compiler cannot prove that two arbitrary Equatable
values have the same underlying concrete type.
In a similar manner, if we introduced another opaque type returning function:
// foo() -> <Output1 : Equatable> Output1 {
func foo() -> some Equatable {
return 5 // The opaque result type is inferred to be Int.
}
// bar() -> <Output2 : Equatable> Output2 {
func bar() -> some Equatable {
return "" // The opaque result type is inferred to be String.
}
let x = foo()
let y = bar()
print(x == y) // Illegal, the return type of foo != return type of bar.
The example becomes illegal because although both foo
and bar
return some Equatable
, their "reverse" generic placeholders Output1
and Output2
could be satisfied by different types.
3. Opaque result types compose with generic placeholders
Unlike regular protocol-typed values, opaque result types compose well with regular generic placeholders, for example:
protocol P {
var i: Int { get }
}
struct S : P {
var i: Int
}
func makeP() -> some P { // Opaque result type inferred to be S.
return S(i: .random(in: 0 ..< 10))
}
func bar<T : P>(_ x: T, _ y: T) -> T {
return x.i < y.i ? x : y
}
let p1 = makeP()
let p2 = makeP()
print(bar(p1, p2)) // Legal, T is inferred to be the return type of makeP.
This wouldn't have worked if makeP
had just returned P
, as two P
values may have different underlying concrete types, for example:
struct T : P {
var i: Int
}
func makeP() -> P {
if .random() { // 50:50 chance of picking each branch.
return S(i: 0)
} else {
return T(i: 1)
}
}
let p1 = makeP()
let p2 = makeP()
print(bar(p1, p2)) // Illegal.
Why use an opaque result type over the concrete type?
At this point you may be thinking to yourself, why not just write the code as:
func makeP() -> S {
return S(i: 0)
}
Well, the use of an opaque result type allows you to make the type S
an implementation detail by exposing only the interface provided by P
, giving you flexibility of changing the concrete type later down the line without breaking any code that depends on the function.
For example, you could replace:
func makeP() -> some P {
return S(i: 0)
}
with:
func makeP() -> some P {
return T(i: 1)
}
without breaking any code that calls makeP()
.
See the Opaque Types section of the language guide and the Swift evolution proposal for further information on this feature.
The other answer does a good job of explaining the technical aspect of the new some
keyword but this answer will try to easily explain why.
Let's say I have a protocol Animal and I want to compare if two animals are siblings:
protocol Animal {
func isSibling(_ animal: Self) -> Bool
}
This way it only makes sense to compare if two animals are siblings if they are the same type of animal.
Now let me just create an example of an animal just for reference
class Dog: Animal {
func isSibling(_ animal: Dog) -> Bool {
return true // doesn't really matter implementation of this
}
}
The way without some T
Now let's say I have a function that returns an animal from a 'family'.
func animalFromAnimalFamily() -> Animal {
return myDog // myDog is just some random variable of type `Dog`
}
Note: this function won't actually compile. This because before the 'some' feature was added you cannot return a protocol type if the protocol uses 'Self' or generics. But let's say you can... pretending this upcasts myDog to abstract type Animal, let's see what happens
Now the issue comes is if I try to do this:
let animal1: Animal = animalFromAnimalFamily()
let animal2: Animal = animalFromAnimalFamily()
animal1.isSibling(animal2) // error
This will throw an error.
Why? Well the reason is, when you call animal1.isSibling(animal2)
Swift doesn't know if the animals are dogs, cats, or whatever. As far as Swift knows, animal1
and animal2
could be unrelated animal species. Since we can't compare animals of different types (see above). This will error
How some T
solves this problem
Let's rewrite the previous function:
func animalFromAnimalFamily() -> some Animal {
return myDog
}
let animal1 = animalFromAnimalFamily()
let animal2 = animalFromAnimalFamily()
animal1.isSibling(animal2)
animal1
and animal2
are not Animal
, but they are class that implements Animal.
What this lets you do now is when you call animal1.isSibling(animal2)
, Swift knows that animal1
and animal2
are the same type.
So the way I like to think about it:
some T
lets Swift know the what implementation ofT
is being used but the user of the class doesn't.
(Self-promotion disclaimer) I've written a blog post that goes a little more into depth (same example as here) on this new feature
Hamish's answer is pretty awesome and answers the question from a technical perspective. I would like to add some thoughts on why the keyword some
is used in this particular place in Apple's SwiftUI tutorials and why it's a good practice to follow.
some
is Not a Requirement!
First of all, you don't need to declare the body
's return type as an opaque type. You can always return the concrete type instead of using the some View
.
struct ContentView: View {
var body: Text {
Text("Hello World")
}
}
This will compile as well. When you look into the View
's interface, you'll see that the return type of body
is an associated type:
public protocol View : _View {
/// The type of view representing the body of this view.
///
/// When you create a custom view, Swift infers this type from your
/// implementation of the required `body` property.
associatedtype Body : View
/// Declares the content and behavior of this view.
var body: Self.Body { get }
}
This means that you specify this type by annotating the body
property with a particular type of your choice. The only requirement is that this type needs to implement the View
protocol itself.
That can either be a specific type that implements View
, for example
Text
Image
Circle
- …
or an opaque type that implements View
, i.e.
some View
Generic Views
The problem arises when we try to use a stack view as the body
's return type, like VStack
or HStack
:
struct ContentView: View {
var body: VStack {
VStack {
Text("Hello World")
Image(systemName: "video.fill")
}
}
}
This won't compile and you'll get the error:
Reference to generic type 'VStack' requires arguments in <...>
That's because stack views in SwiftUI are generic types! (And the same is true for Lists and other container view types.)
That makes a lot of sense because you can plug in any number of views of any type (as long as it conforms to the View
protocol). The concrete type of the VStack
in the body above is actually
VStack<TupleView<(Text, Image)>>
When we later decide to add a view to the stack, its concrete type changes. If we add a second text after the first one, we get
VStack<TupleView<(Text, Text, Image)>>
Even if we make a minor change, something as subtle as adding a spacer between the text and the image, the stack's type changes:
VStack<TupleView<(Text, _ModifiedContent<Spacer, _FrameLayout>, Image)>>
From what I can tell, that's the reason why Apple recommends in their tutorials to always use some View
, the most general opaque type which all views satisfy, as the body
's return type. You can change the implementation / the layout of your custom view without manually changing the return type every time.
Supplement:
If you want to get a more intuitive understanding of opaque result types, I recently published an article that might be worth reading: