416

The new SwiftUI tutorial has the following code:

struct ContentView: View {
    var body: some View {
        Text("Hello World")
    }
}

The second line the word some, and on their site is highlighted as if it were a keyword.

Swift 5.1 does not appear to have some as a keyword, and I don't see what else the word some could be doing there, since it goes where the type usually goes. Is there a new, unannounced version of Swift? Is it a function that's being used on a type in a way I didn't know about?

What does the keyword some do?

hippietrail
  • 15,848
  • 18
  • 99
  • 158
Half
  • 5,078
  • 2
  • 10
  • 20
  • For those who were dizzy by the subject, here a very decrypting and step by step article thanks to Vadim Bulavin. https://www.vadimbulavin.com/opaque-return-types-and-the-some-keyword-in-swift/ – Luc-Olivier Jun 01 '20 at 21:07

16 Answers16

519

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.

Hamish
  • 78,605
  • 19
  • 187
  • 280
  • 1
    @ielyamani Yup, though personally I prefer to keep the `return` statement for functions. Perhaps it's just inertia, but something about it looks odd without the `return` statement to me. I do like omitting `return` from computed properties though! – Hamish Jun 04 '19 at 12:03
  • @RamazanPolat Almost the same in Python: https://www.python.org/dev/peps/pep-0484/#generics – marvi Jun 04 '19 at 12:25
  • 1
    Tl;dr: the return type of `func foo() -> T` is a concrete Equatable type chosen by the caller, whereas the return type of `func foo() -> some Equatable` is a concrete Equatable type chosen by the implementer. – BallpointBen Jun 04 '19 at 21:15
  • I think the question is not why `func makeP() -> some P` is better than `func makeP() -> S` but rather why it is better than `func makeP() -> P`. You have provided an example that gets at the difference. But it might be nice to say more about the positive advantages here. – matt Jun 05 '19 at 01:53
  • I would also like to suggest tentatively that the names given to this construct are unfortunately. I like the `some` keyword just fine; what I don't like is the description of this as an "opaque" or "reverse generic". What `some P` means is "a specific P-adopter". So this is a way of returning a protocol adopter type, _qua_ protocol adopter. I have no good name for that, but "opaque" and "generic" don't capture it (in my current opinion). – matt Jun 05 '19 at 01:59
  • 4
    But what is the difference between: `func makeP() -> some P` and `func makeP() -> P`? I have read proposal, and can not see this difference for their samples too. – Artem Jun 05 '19 at 02:01
  • @matt, you don't have to use `generics` in Python, it's just for IDE and linters to see bugs in compile time. In fact, you don't even need to provide any type information in Python. Readibility counts more than most people think. – ramazan polat Jun 05 '19 at 04:26
  • 1
    @Artem they are a few reasons. For a large amount `-> some P` will act the same however if I have `func f() -> some P` and `func g() -> some P` these are not the necessarily the same. 1) for performance reasons swift will not necessarily need to upcast and do dynamic code gen for the types 2) **Swift knows what subclass of P they return**. An example of this is if I have a generic `func f(a: T, b: T)` I can call this with instances returned from `f()` because swift knows they are the same – Downgoat Jun 05 '19 at 06:11
  • 3
    [Here's an example where `some P` would be needed](https://tio.run/##fY7LjoJAEEX3/RV3JyQzYzSuyIwLv4CAiesWi9BJQ3W6i9HE@O2IiI@ZBbWoRaXOuTccTSmrrnOehQu2SHFW6KdsmwIF10572hmpolOCnGwZ43ONDbNVF6UKq0NAlkxS2RPBGZ6k9Q3Et4TLS5FPK/JJxXyOHbf2gIYFlf4lSGUCTAj9hylHgA6YBa4J6UzdMzxpoTQa3I/0UZ5F8c383iUN39u@5TrSCbYf2Pc7HiH99a/vfqAtCdwCP6@k@2n55/TU38RuMZjdMu66Kw) – Downgoat Jun 05 '19 at 06:19
  • @matt Thanks for the feedback! I'll try and edit things to make the differences between `P` and `some P` a little more explicit. Regarding the mental model for how to think of opaque result types, I still think "reverse generics" is the best way to think of it, especially considering that [the eventual goal is to allow for explicit reverse generics](https://forums.swift.org/t/improving-the-ui-of-generics/22814), e.g `-> T` with `some P` being a syntactic sugared equivalent. – Hamish Jun 05 '19 at 11:09
  • 1
    Furthermore, `some P` will likely eventually be allowed for parameters, where `func foo(x: some P)` will be syntactic sugar for `func foo(x: T)`. – Hamish Jun 05 '19 at 11:09
  • *giving you flexibility of changing the concrete type later down the line without breaking any code that depends on the function* => I think this was not demonstrated. Instead, in the foo/bar example both `foo` and `bar` should return `Int` as `some Equatable` to demonstrate that even though the concrete types are the same, they cannot be compared to each other. – Matthieu M. Jun 05 '19 at 12:36
  • 7
    Swifts type handling is a mess. Is this specificity really something that cant be handled at compile time? See C# for reference it handles all these cases implicitly through simple syntax. Swifts need to have pointlessly explicit almost cargo-cultist syntax is really obfuscating the language. Can you also explain the design rationale for this please? (If you have a link to the proposal in github that would be good too) Edit: Just noticed it linked at the top. – SacredGeometry Jun 06 '19 at 06:17
  • You said “giving you flexibility of changing the concrete type later down the line without breaking any code that depends on the function.”, however I don’t think that is the case. If I understand this correctly, the compiler will infer the actual type, let’s say an Apple. If you have some other code that compares such output value with another Apple (assume this second type is fixed), this will compile correctly. But if you then change the implementation so it returns an Orange, it will break because you can’t compare an Apple to an Orange. – Zmaster Jun 10 '19 at 22:57
  • 2
    @Zmaster The compiler will treat two opaque return types as being different even if the implementation for both returns the same concrete type. In other words, the specific concrete type chosen is hidden from the caller. (I have been meaning to expand on the latter half of my answer to make things like this a bit more explicit, but haven't got round to it yet). – Hamish Jun 10 '19 at 23:14
  • This is a truly great answer and brings incredible clarity to `some`! Thanks @Hamish! – Andrew Paul Simmons Jun 15 '19 at 12:02
  • does that mean it is `some`what similar to the C++ `auto`keyword? Or is it more similar to Javas `? implements IMyInterface` ? – J. Coenen Jul 11 '19 at 07:39
  • 1
    *“... is available in Swift 5.1 with Xcode 11 ...”* – perhaps (for the sake of completeness) mention that it also requires runtime support and requires at least macOS 10.15/iOS 13/... – Martin R Feb 27 '20 at 13:46
  • I assume that the result is best used with interference. Or is there some way to say that a specific variable is of type `some P`? E.g. `var p: some P`. Problem is obviously to have a protocol with associated type stored. – Wizard of Kneup May 21 '20 at 06:39
  • 1
    Can i use `body: T` instead of `some View`? or any other way to substitute the `some View`? – Li Jin Jul 13 '21 at 16:05
  • So going by your explanation, we should only return some concrete type for `var body: some View {}` right? Why then can we return different concrete types within the `body` for example, `var body: some View { RoundedRectangle(cornerRadius: 35.0) Text("Hello world") }`? Is this some kind of syntactic sugar where perhaps they're embedded in a ZStack automatically or something? – Fourth Feb 16 '23 at 02:41
90

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 of T 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

Jakub Truhlář
  • 20,070
  • 9
  • 74
  • 84
Downgoat
  • 13,771
  • 5
  • 46
  • 69
  • 3
    So your idea is that the caller can take advantage of the fact that two calls to the function return the same type even though the caller doesn’t know what type it is? – matt Jun 05 '19 at 07:43
  • 2
    @matt essentially yup. Same concept when used with fields, etc— the caller is given the guarantee that the return type will always be the same type but doesn't reveal exactly what the type is. – Downgoat Jun 05 '19 at 07:49
  • 1
    @Downgoat thank you very much for perfect post and answer. As I understood `some` in return type works as constraint to body of function. So `some` requires to return only one concrete type in whole function body. For example: if there is `return randomDog` then all other returns must work only with `Dog`. All benefits come from this constraint: availability of `animal1.isSibling(animal2)` and benefit of compilation of `func animalFromAnimalFamily() -> some Animal` (because now `Self` becomes defined under the hood). Is it correct? – Artem Jun 05 '19 at 08:30
  • 6
    This line was all I did need, animal1 and animal2 are not Animal, but they are class that implements Animal, now all makes sense! – aross Jun 05 '19 at 18:55
  • 1
    Your example is weird. If the method ‘aminalFromAnimalFamiky’ is supposed to create animals from one family, why would it result a more generic Animal?) You created the problem, and you solved it )) – rommex Nov 27 '21 at 12:18
56

I think what all the answers so far are missing is that some is useful primarily in something like a DSL (domain-specific language) such as SwiftUI or a library/framework, which will have users (other programmers) different from yourself.

You would probably never use some in your normal app code, except perhaps insofar as it can wrap a generic protocol so that it can be used as a type (instead of just as a type constraint). What some does is to let the compiler keep a knowledge of what specific type something is, while putting a supertype facade in front of it.

Thus in SwiftUI, where you are the user, all you need to know is that something is a some View, while behind the scenes all sort of hanky-panky can go on from which you are shielded. This object is in fact a very specific type, but you'll never need to hear about what it is. Yet, unlike a protocol, it is a full-fledged type, because wherever it appears it is merely a facade for some specific full-fledged type.

In a future version of SwiftUI, where you are expecting a some View, the developers could change the underlying type of that particular object. But that won't break your code, because your code never mentioned the underlying type in the first place.

Thus, some in effect makes a protocol more like a superclass. It is almost a real object type, though not quite (for example, a protocol's method declaration cannot return a some).

So if you were going to use some for anything, it would most likely be if you were writing a DSL or framework/library for use by others, and you wanted to mask underlying type details. This would make your code simpler for others to use, and would allow you to change the implementation details without breaking their code.

However, you might also use it in your own code as a way of shielding one region of your code from the implementation details buried in another region of your code.

matt
  • 515,959
  • 87
  • 875
  • 1,141
  • 3
    I feel like this answer (and your comment in Downgoat's answer) is the real answer. Short version - "some" just means a given function always returns a single specific concrete type (which you don't care about, but conforms to a protocol you do). The examples in other answers do a disservice, the sibling example only works when the "some Animal" being compared originated from the same method to create it. – Travis Nov 07 '20 at 06:02
48

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:

What’s this “some” in SwiftUI?

Mischa
  • 15,816
  • 8
  • 59
  • 117
31

The some keyword from Swift 5.1 (swift-evolution proposal) is used in conjunction with a Protocol as a return type.

Xcode 11 release notes present it like that:

Functions can now hide their concrete return type by declaring what protocols it conforms to, instead of specifying the exact return type:

func makeACollection() -> some Collection {
    return [1, 2, 3]
}

Code that calls the function can use the interface of the protocol, but doesn’t have visibility into the underlying type. (SE-0244, 40538331)

In the example above, you don't need to tell that you're going to return an Array. That allows you to even return a generic type that just conforms to Collection.


Note also this possible error that you may face:

'some' return types are only available in iOS 13.0.0 or newer

It means that you're supposed to use availability to avoid some on iOS 12 and before:

@available(iOS 13.0, *)
func makeACollection() -> some Collection {
    ...
}
Cœur
  • 37,241
  • 25
  • 195
  • 267
  • 1
    You are supposed to use availability to avoid `some` on iOS 12 and before. As long as you do, you should be fine. The issue is only that the compiler doesn’t warn you to do this. – matt Jun 09 '19 at 02:21
  • 2
    Cœur, just as you point out, the concise Apple description explains it all: *Functions can now hide their concrete return type by declaring what protocols it conforms to, instead of specifying the exact return type.* And then code calling the function can use the protocol interface. Neat and then some. – Fattie Jul 16 '19 at 09:44
  • This (hiding the concrete return type) is already possible without using the keyword "some". It doesn't explain the effect of adding "some" into the method signature. – Vince O'Sullivan Jul 30 '19 at 09:26
  • @VinceO'Sullivan It is not possible to remove the `some` keyword in this given code sample in Swift 5.0 or Swift 4.2. Error will be: "_Protocol 'Collection' can only be used as a generic constraint because it has Self or associated type requirements_" – Cœur Jul 30 '19 at 09:55
9

I'll try to answer this with very basic practical example (what is this an opaque result type about)

Assuming you have protocol with associated type, and two structs implementing it:

protocol ProtocolWithAssociatedType {
    associatedtype SomeType
}

struct First: ProtocolWithAssociatedType {
    typealias SomeType = Int
}

struct Second: ProtocolWithAssociatedType {
    typealias SomeType = String
}

Before Swift 5.1, below is illegal because of ProtocolWithAssociatedType can only be used as a generic constraint error:

func create() -> ProtocolWithAssociatedType {
    return First()
}

But in Swift 5.1 this is fine (some added):

func create() -> some ProtocolWithAssociatedType {
    return First()
}

Above is practical usage, extensively used in SwiftUI for some View.

But there is one important limitation - returning type needs to be know at compile time, so below again won't work giving Function declares an opaque return type, but the return statements in its body do not have matching underlying types error:

func create() -> some ProtocolWithAssociatedType {
    if (1...2).randomElement() == 1 {
        return First()
    } else {
        return Second()
    }
}
zalogatomek
  • 666
  • 1
  • 9
  • 19
4

'some' means opaque type. In SwiftUI, View is declared as a protocol

@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
public protocol 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 }
}

When you create your view as Struct, you conform to the View protocol and tell that the var body will return something which will be confirming to View Protocol. Its like a generic Protocol abstraction where you don't have to Define the concrete Type.

vrat2801
  • 538
  • 2
  • 13
4

to simplify, if you know the difference between

var x = 5

vs

int x =5

Then you'll know some. The compiler knows it, and you know it. Minimal effort for saying you comply to something without specifying the specifics (the generic types that it uses)

Isaac L
  • 101
  • 1
  • 7
3

Prerequisite

This issue is really meant to be helpful for protocols with associated types. As an example, the Equatable has an associated type (Self)

variable type - not allowed ❌

var x: Equatable = 10 // Int
var y: Equatable = "10" // String

It's because the compiler can't know if the associate type of x's matches with the associated type of y. So it just forbids it. Compiler just doesn't want to be in situation where you'd try something like:

if x == y { print("Equal") }

Although both are Equatable. One is an Int while the other is a String.

return type - not allowed ❌

func x() -> Equatable {
   return 10 // Use of protocol 'Equatable' as a type must be written 'any Equatable'
}

generic-constraint - allowed ✅

It allows you to only do this as a generic constraint:

func compare<T: Equatable>(_ x: T, _ y: T) -> Bool {
    return x == y
}

The is allowed because both x,y are of a single generic type that happens to also be Equatable. Meaning you can't ever be in a situation where x is an Int, while y is a String. Both have to be of same concrete types.

some reduces some of the previous syntax limitations

variable return type - allowed ✅

The following isn't allowed.

var x: Equatable = 10

However, with the addition of some, the following is allowed:

var x: some Equatable = 10

But this itself isn't super useful. You still can't nor should ever do something like:

var x: some Equatable = 10
var y: some Equatable = 10

if x == y {
    print("equal") // ERROR: Cannot convert value of type 'some Equatable' (type of 'y') to expected argument type 'some Equatable' (type of 'x')
}

return type - allowed ✅ &

let y = 8
func foo() -> some Equatable { // THIS is where `some` really shines
    if y > 10 {
        return 10
    } else {
        return 9
    }
}

But it comes with certain necessary and good protections

let y = 8
func foo() -> some Equatable { // ERROR: Function declares an opaque return type 'some Equatable', but the return statements in its body do not have matching underlying types
    if y > 10 {
        return 10
    } else {
        return "ten" 
    }
}

meaning it won't allow you to have different return types such as: 10 and "ten" together. You can return anything — as long as all the things you return are of the same concrete type.

some isn't actually necessary.

It's just very helpful to avoid complex syntax.

See Mischa's fantastic answer

Extra note

It's a bit beyond the scope of this question, but I'd like to show how some and any work in tandem:

var x: some Equatable = 10
var y: some Equatable = "ten"

func handle(thing: any Equatable) {
    print(thing)
}

handle(thing: x) // 10
handle(thing: y) // "ten"

But also:

var x: some Equatable = 10
var y: some Equatable = "ten"

func handle(thing1: any Equatable, thing2: any Equatable ) {
    print(thing1 == thing2) //  ❌
}

handle(thing1: x, thing2: y)
handle(thing1: y, thing2: y)

The error you get is

ERROR: Binary operator '==' cannot be applied to two 'any Equatable' operands

It's because at this point, the associated type which is what drives the logic for == operator isn't defined. It's only defined once it knows it's an Int or a String, but that's not known. So it just errors out.

Caleb
  • 124,013
  • 19
  • 183
  • 272
mfaani
  • 33,269
  • 19
  • 164
  • 293
  • 1
    @Caleb great catch. Thanks. Reworded it – mfaani Aug 14 '23 at 19:58
  • 1
    IMO it's not great to rely on other answers for the same reason that we don't allow link-only answers: the thing you're pointing to may change. It's fine to reference "Mischa's fantastic answer," but I think you should still give at least a brief explanation of your own; if you can't justify the "some isn't actually necessary" idea in your own words, consider just removing it. (BTW, most language features aren't "actually necessary," but they still improve the language. Classes, for example, aren't "actually necessary," but nobody feels the need to point that out.) – Caleb Aug 14 '23 at 20:09
0

For those who were dizzy by the subject, here a very decrypting and step by step article thanks to Vadim Bulavin.

https://www.vadimbulavin.com/opaque-return-types-and-the-some-keyword-in-swift/

Luc-Olivier
  • 3,715
  • 2
  • 29
  • 29
0

Simple way to understand, like kindOf in Objc

Jadian
  • 4,144
  • 2
  • 13
  • 10
0

In my understanding (maybe wrong)

Call that I had

Protocol View{}

 class Button: View { // subclass of View } 

 //this class not a subclass of View
 class ButtonBuilder<T> where T:View { //using T as View here   } 

Then

var body: View = Button() // ok
var body: View = ButtonBilder() //not ok
var body: some View = ButtonBilder() //ok

So

some Protocol

Can treament generic class which using that Protocol as generic in their own code as SubClass of the Protocol

dellos
  • 327
  • 5
  • 15
0

You can assume like generic in swift.

Divesh singh
  • 409
  • 4
  • 12
0

Above post by Mischa (sorry, I cannot directly add comment yet) states that some is optional, unless you use generic types as VStack, etc. And that's because some being the most general opaque type which all views satisfy. So using it here helps resolve the compile error.

It seems some is very close to what Combine's eraseToAnyPublisher() method does.

0

Opaque return types

If you look at my example, you will see that some Gesture means that myGesture property will always be implementing the Gesture protocol, however, the concrete implementation type doesn't need to be known by the caller (it's hidden). The same is true about body property – instead of providing a concrete type, the return value is described in terms of the protocols it supports, i.e. View.

Here's the code:

import SwiftUI

struct ContentView: View {
    
    @State private var rotate: Angle = .zero
    
    var myGesture: some Gesture {
        RotationGesture()
            .onChanged { rotate = $0 }
            .onEnded { angle in rotate = angle }
    }
    
    var body: some View {
        Rectangle()
            .frame(width: 200, height: 200)
            .foregroundColor(.blue)
            .rotationEffect(rotate)
            .gesture(myGesture)
    }
}

In addition to the above, all SwiftUI modifiers applied to the Rectangle also use the some keyword when returning a value. For instance:

func foregroundColor(_ color: Color?) -> some View
Andy Jazz
  • 49,178
  • 17
  • 136
  • 220
0

The "some" keyword is used to specify an opaque return type that is generally a protocol.

This means the return type can be any type that conforms to the protocol but it must always return the same type across calls.

This is different than returning just the protocol or using generics where it could return different types across different calls.

Here's a code example to clarify the differences:

protocol Animal {
    init() // required to make createSpecificAnimal work
    func makeSound()
}

struct Lion: Animal {
    func makeSound() { print("roar") }
}

struct Dog: Animal {
    func makeSound() { print("bark") }
}

// 1 - Function decides what type of Animal to return everytime this
// function is called and can choose different Animals on each call because
// return type is Animal.
func createRandomAnimal() -> Animal {
    return Bool.random() ? Lion() : Dog()
}
let animal1 = createRandomAnimal()
animal1.makeSound() // May "roar" or "bark"
let animal2 = createRandomAnimal()
animal2.makeSound() // May "roar" or "bark"

// 2 - Function decides what type of Animal to return BUT CAN'T return a
// Lion OR a Dog like above because "some Animal" is an opaque return type
// and can't return 2 different types across different calls. It must
// choose a type and stick with it everytime this function is called. This
// function has decided the Animal that will always return from this function
// will be a Lion.
func createAnimal() -> some Animal { // <- OPAQUE RETURN TYPE
    return Lion()
}
let animal3 = createAnimal()
animal3.makeSound() // Will always "roar"
let animal4 = createAnimal()
animal4.makeSound() // Will always "roar"
    
// 3 - Caller decides what type of Animal to return. If the caller passes 
// Lion.self the return type will be Lion. If the caller passes Dog.self the 
// return type will be Dog.
func createSpecificAnimal<T: Animal>(_: T.Type) -> T {
    return T()
}
let animal5 = createSpecificAnimal(Lion.self)
animal5.makeSound() // Will always "roar"
let animal6 = createSpecificAnimal(Dog.self)
animal6.makeSound() // Will always "bark"
TenaciousJay
  • 6,750
  • 3
  • 45
  • 48