Typealiases prevent casting when concrete type differs

Hello all, pardon my beginner language,
I'd like to know if anybody knows a clean solution for the following problem (possibly avoiding reduce(...) or any further iteration over the array ). And would also like to know the reasons (if there are any) for this behaviour:

Defining a simple typealias on String named Identifier, and assigning a [String] var to [Identifier] var works smoothly.
When reversed() is applied on first array tho, the product is a ReversedCollection<[String]> and such type cannot be assigned to a [Identifier] var anymore, receiving following error from compiler:

Cannot convert value of type 'ReversedCollection<[String]>' to specified type '[Identifier]' (aka 'Array')

TLDR; Isn't this behaviour un-coherent? Is there a practical reason for the compiler to refuse this? Is there a clean workaround?

typealias Identifier = String

/// compiles
let arr3 = [String]()
let arr4: [Identifier] = arr3

/// does not compile
let arr1 = [String]().reversed()
let arr2: [Identifier] = arr1 // << error

Thank you for your time.

This is because, by default, reversed returns ReversedCollection<BaseType>.

Array itself is a struct, ReversedCollection is a different struct. That’s why you can’t just assign arr1, which is ReversedCollection<Array<String>>, to arr2, which wants Array<String>.

You can fix this in 2 ways:

  • Create an Array from arr1

    let arr1 = [String]().reversed()
    let arr2: [Identifier] = Array(arr1)
    
  • Use another overload of reversed to have it returned Array

    // All of these are equivalent, choose one
    let arr1 = [String]().reversed() as Array
    let arr1: Array = [String]().reversed()
    let arr1: [String] = [String]().reversed()
    
    // Now `arr1` has type `Array<String>`, we can just assign to `arr2`
    let arr2: [Identifier] = arr1
    

The second method should be faster (or at least, it can’t be slower) than the first one.

Note that sometimes I use Array, sometimes I use [String]. That’s because [String] is just a shorthand for Array<String>, and Swift is usually smart enough to infer what is in <>.

Identifier doesn’t play important role here, type alias is just a way to use different name to refer to the same thing.
Wherever you use Identifier, it’s the same as using String.

2 Likes

Try this :

    let arr1 = [String]().reversed()
    
    let arr2 = [Identifier](arr1)

This works:

let foo0 = [String]()
let bar0: [String] = foo0.reversed() // Ok

let foo1: [String] = ["A", "B"].reversed()
let bar1: [String] = foo1 // Ok

let foo2: [String] = ["A", "B"]
let bar2: [String] = foo2.reversed() // Ok

but yeah, this doesn't:

let foo3 = [String]().reversed()
let bar3: [Identifier] = foo3 // error

although you can do this instead:

let bar3: [String] = unsafeBitCast(foo3, to: [String].self)

Array conforms to RandomAccessCollection and ReversedCollection also conforms to that (when its base does).

Did you mis-reply to me, or did I have incorrect explanation?

This is very likely not a correct way to do it. It only works because Array and ReversedCollection has similar memory layout. Relying on it is a very bad idea, hence the name unsafe.

Furthermore, you lose the “reversed-ness” of the collection, try to print the content of foo3 and bar3 out when they’re not empty.

2 Likes

I meant in that specific example, it's probably fine because the array is empty. Anyway, I am not suggesting to actually use it, just pointing it out that it works.

Thank you, exactly what i was looking for.
I see, i guess that is a standard library "issue" from my point of view then.

This issue is mostly design decision.

Reversing a collection incurs some cost. You need to allocate memory (unless you do it in-place), then copy data, element-by-element.

This could be a performance hit if you do it frequently, or of the array is complex to copy.

ReversedCollection doesn’t do any of that, it’s a very lightweight object if your goal is only to traverse the collection (i.e. use them in for loop) from back to front, which is very common. You’d lose some convenience of Array, like 0-based Int subscription, but for said use-case, that’s not a problem.

If you don’t really need Array, just want some collection to traverse, I’d suggest you left out the type of arr2, and have Swift choose (infer) what’s best for you (the same goes for other variables as well).

3 Likes

Yes yes, thanks, but i know all this, that's why i said "issue". Api-design wise i would have performed the cast internally to the reversed() function and returned the original type. If i ask for a reversed apple why does it return a reversed orange, forcing me to know the implementation details and class hierarchy of unexpected objects and performing a "risky" cast externally (since use cases for using the ReversedCollection are close to zero)? When in the end if i need performances i quite sure traverse the array from the end or use a reversed iterator rather than relying on implementation details of a function.

Initially i thought the typealias was involved in the problem, hence my confusion. Thanks again for clarifying that!

Terms of Service

Privacy Policy

Cookie Policy