Fussing about so called opaque and boxed types, any help will be appreciated

I'm new to swift, having some experiences in other language like c, java, C#, python.
I have problem to understand the swift keyword some and any.
I do read the docs here Documentation

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.

But I still can not make the codes to work like below:
protocol Shape{}
struct Squre: Shape {}
struct Circle: Shape {}

/* normally I use protocol this way, it's just interface for other languages
*/
func creatShape(_ seed:Int) -> Shape {
if seed % 2 == 0 {return Circle()}
else {return Squre()}
}

/*
goes alright, I don't know why need keyword "any" for, why not just use the first one
*/
func creatAnyShape(_ seed:Int) -> any Shape {
if seed % 2 == 0 {return Circle()}
else {return Squre()}
}

/*oops,the some keywords goes wrong, how can we make it work then?
error mesages here:
function declares an opaque return type,
but the return statements in its body do not have matching underlying types
*/
func creatSomeShape(_ seed:Int) -> some Shape{
if seed % 2 == 0 {return Circle()}
else {return Squre()}
}

let seed=Int.random(in: 1..<11)
var shape1 = creatAnyShape(seed)
print(seed,shape1)

Hi Michael!

Your description of some types was on point; in fact, you say yourself that an opaque type: “hides its return value’s type information.” It is precisely because opaque types hide the return type that your example fails: your function tries to return values of two different types.

The any type works because it’s a concrete type that can box / store any type of a given protocol. So when you return values of different types, the compiler implicitly boxes the underlying type in an any Shape container.

As to the difference between Shape and any Shape, the latter is encouraged because it is easier to distinguish from the Shape protocol. For instance, you can write func f<T: Shape>(x: T, y: some Shape). In all these uses, Shape refers to the protocol itself and not to the concrete container type any Shape. This is an important distinction because any types’ added dynamism comes with a performance penalty, which is why opaque types are instead suggested as the go-to abstraction mechanism. In other words, any Shape combats overuse of container types and encourages users to think about what abstraction they actually need.

In your case, a performant solution would be to create a type such as the following. Of course, if this is too much work, any are still a good choice.

enum Either<A: Shape, B: Shape>: Shape {
  case a(A), b(B)

  // Implement all of Shape’s requirements using a 
  // switch and forwarding to the underlying A or B 
  // shape types…
}

I hope this helps clear things up! Please let me know if you have any other questions.

Thank you very much for help.
So we really need to make another structure, enum or class to server as the base type for it, it's really awkward and full of complexity.
I already have shape Type (shape protocol or interface), I can do things like the creatShape method does ,it's elegant, why bother it to create another base type (enum, struct, class) conform to shape protocol . If I need to create a base type in the first place, why should I bother to create a shape protocol for abstract, it's nonsense, either one will do the work but absolutely not using both at once!
protocol and interface is much loose than the enum, struct or class for base type, I think swift should support the creatSomeShape method.

It's such a pity, so I have no choice but stick with the first method anyway!

1 Like

I'm sorry if I didn't make it clear enought in my first post but any Shape is perfectly fine to use in your code. Though creating the Either type might be a little more efficient, any Shape is also quite fast. So if you don't, for instance, call creatSomeShape/creatAnyShape thousands of times a second using any Shape is still a good choice. Did you run into any other limitations using creatAnyShape which make you feel that creatSomeShape should be supported?

Thanks a lot again.

  1. keyword "any" in the creatAnyShape only bring performance overhead, and I use typeof or print to show the type info, they are both not hiding type info. All return the concrete type info not abstrct Shape.
    So there is no need for it, just use plain protocol Shape is enough, why bother invent another keyword.

  2. keyword "some" in the creatSomeShape, I support it works accturally not.
    And when I use typeof or print to show the type info, it will show the Shape protocol info not concrete type info like Squre or Circle as the creatShape method does! So I want keyword "some" to work in the creatSomeShape method!

  3. I'm also think about this: when I using Shape as func return value Type I can return multi different concrete Type which Conform to protocol Shape, when I using "some" Shape as func return value Type Then I must return different concrete Type with a common base concrete type which Conform to protocol Shape.

That is ridiculous, so we should use keyword "hidingTypeByBase" instead of "some" ,otherwise the meaning, the logic and the designing philosophy is not right. And the keyword "some" requirement for a common concrete base type really break the the original protocol or interface rule and benifit for abstract.
We should favor Composition over Inheritance.

Re 1: any Shape and Shape mean the same thing, any Shape is just the newer syntax. It was thought an explicit keyword, as a counterpart to some, would help users see that there may some cost or different behavior to the existential box. It was going to be required in Swift 6 mode, but that plan has been dropped and so both syntaxes will live together for a while longer.

Re 2: some was introduced to allow easy erasure of a single, complex generic type that conformed to a particular protocol, in order to allow SwiftUI to return some View rather than the lower performance any View or the unutterable generics it produces like_ConditionalView<Text, _ConditionalView<Text, Text>> (and that’s a simple view!). You can think of it more specifically like “some particular”, but I can see how the implied single underlying type would be difficult for non native speakers to pick up.

More modern Swift has added the ability to look dynamically unbox any Shape into generic T: Shape APIs, so it’s not as bad to work with the existential boxes anymore, but it ultimately depends on your use case whether the performance penalty will matter.

1 Like

According to your help , I make keyword some to work anyway.
But the real type info hiding said by the swift document is not provided.
I can use typeof or the print to see the Concrete type BaseShape and wrapped inner concrete type (Circle or Square) not the Shape Protocol I think.
Am I doing it in a wrong way? Or the keyword "some" can not hiding concrete type info?

protocol Shape{
func drawing() -> String
}
struct Squre: Shape {
func drawing() -> String{"I'm a squre!"}
}
struct Circle: Shape {
func drawing() -> String{"I'm a circle!"}
}

/* normally I use protocol this way, it's just interface for other languages
*/
func creatShape(_ seed:Int) -> Shape {
if seed % 2 == 0 {return Circle()}
else {return Squre()}
}

/*
goes alright, I don't know why need keyword "any" for, why not just use the first one
*/
func creatAnyShape(_ seed:Int) -> any Shape {
if seed % 2 == 0 {return Circle()}
else {return Squre()}
}

/*oops,the some keywords goes wrong, how can we make it work then?
error mesages here:
function declares an opaque return type,
but the return statements in its body do not have matching underlying types
*/
private struct BaseShape:Shape{
private var wrappedShape:Shape
init(_ shape:Shape){
wrappedShape=shape
}
func drawing() -> String {
wrappedShape.drawing()
}
}

// I return BaseShape than Circle or Square instead, it works now!
func creatSomeShape(_ seed:Int) -> some Shape{
if seed % 2 == 0 {return BaseShape(Circle())}
else {return BaseShape(Squre())}
}

let seed=Int.random(in: 1..<11)
var shape1 = creatShape(seed)
var shape2 = creatAnyShape(seed)
var shape3 = creatSomeShape(seed)
print(seed,shape1,shape2,shape3)

Hi @michael2024, welcome to the Swift Forums!

BaseShape doesn't help you here because its single stored property:

private var wrappedShape:Shape

means the same thing as:

private var wrappedShape:any Shape // <--

So you still have an existential (any) type, just wrapped in a struct. That's not a solution to your original problem.

Your creat…Shape function is essentially a "factory" function — a concept that exists in other languages but doesn't exist as a language feature in Swift. There are two Swift ways of doing this, both of which are not completely satisfactory:

1. Return an existential from creatShape:

func creatShape(_ seed:Int) -> any Shape { }

In that case, you can "open" the existential when using the resulting value:

func useShape(_ shape: some Shape) { ]

let newShape = creatShape(0)
useShape(newShape)

Inside the useShape function shape is concrete, not existential.

2. Make creatShape generic:

func creatShape<T: Shape>(_ seed:Int) -> some T { }

In this case, when you create a Shape, you can get a concrete-but-opaque type:

func useShape(_ shape: some Shape) { ]

let newShape = creatShape(0)
useShape(newShape)

In this case, the compiler knows the type of newShape, but your code doesn't. You can still call a function like useShape, but there's no existential "overhead" to newShape once it's created.

What's interesting here is the anti-symmetry of opaque types described by some …. On a function parameter, it makes the parameter type opaque, opening an existential type from the calling site if necessary. On a function return, it makes the return type opaque, avoiding what would otherwise need to be an existential type.

You also have a third option here:

3. Don't use a factory function.
This isn't a solution to your actual problem, but in many cases in Swift it's useful to see if you can come up with a different approach that doesn't need a single factory function that returns values of all your related types.

2 Likes

Thanks a lot for your help, But the second method with "some" still doesn't work anyway.

func creatSomeShape<T:Shape>(_ seed:Int) -> some T{  
    if seed % 2 == 0 {return Circle()}
    else {return Squre()}
}

It doesn't work in my XCode 14.2, Error was pointing to the keyword "some":
an 'opaque' type must specify only 'Any', 'AnyObject', protocols, and/or a base class

It kind of seems like we can only use keyword "some" with Classes but not Structures (not tested just guess), I wonder how SwiftUI play with var body: some View , All the stuff there are Structures .

No matter the type, some can only have one underlying representation. SwiftUI works because body is a @ViewBuilder, which means what looks like conditional types is really building a single underlying value, such as _ConditionalView<Text, Text>, which returned as some View.

1 Like

I ask ChatGPT and Gemini for help, they can't fix it.
I finally get the point why swift invent so many keywords around protocol while other language don't bother to. Cause swift don't take protocol as a type, while interface is a type in other language.

When you want to use some keywords with return value type for a func, It only works for class, and you should use a base class to conform the protocol. The most worst thing, doing this doesn't hiding the real type for you. So I would Say, "some" keyword only work for propety type or parameter type.

This one has never supposed to work. If you return some type from the function, you have to be clear which exact type it returns. This method is just confusing for Swift types system - it can return any shape, but the function requires other way around. some keyword just allows to hide exact type, so you can change it later freely. But you are trying to get any and some work in the same way, while they are different things with different behaviour.


Official docs on the matter a bit incomplete, I think, so it is better to read proposal which has introduced some keyword: swift-evolution/proposals/0244-opaque-result-types.md at main · apple/swift-evolution · GitHub

There is also a nice starting post (and thread in general) that follows opaque types - Improving the UI of generics - and get even more understanding.

Finally, the most recent post - Swift for C++ Practitioners, Part 5: Type erasure & metatypes | Doug's Compiler Corner - which has only Swift code in it, so even if you have no notion of C++, that is not an issue. I have found it being extremely descriptive on the any and some keywords and being a great explanation just for Swift users.

These links will definitely be helpful to understand what is going on here and when to use what.

1 Like

Sorry, I over-collapsed my approach, and it doesn't work in this case because the generic type is dependent on a non-type value (an Int).

I have an alternative to try, and I'll report back here if I can get it to work.

IAC, my general recommendation is #3 (in my earlier post). That is, although your intention is reasonable, it doesn't quite align with current Swift's language capabilities, so rearchitecting at a higher level might be a less painful approach.