Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Conflicting definition of Swift struct and array

Tags:

swift

In Swift Programming Language, it says:

  1. “all of the basic types in Swift—integers, floating-point numbers, Booleans, strings, arrays and dictionaries—are value types, and are implemented as structures behind the scenes.”

  2. structure instances are always passed by value, and class instances are always passed by reference”

  3. “If you assign an Array instance to a constant or variable, or pass an Array instance as an argument to a function or method call, the contents of the array are not copied at the point that the assignment or call takes place. Instead, both arrays share the same sequence of element values. When you modify an element value through one array, the result is observable through the other.”

Clearly, Swift is contradicting itself when it comes to array. Is array a value type or reference type?

like image 342
Boon Avatar asked Jun 27 '14 11:06

Boon


3 Answers

bases on interferece between #2 and #3, I'm trying to find the answer to your question via an example in practice. it is truly based on Swift types only.

I have an Array here, and I fill it up with random numbers.

var arrayOfNumbers: Array<UInt32> = Array()
for _ in 0..100 {
    arrayOfNumbers.append(arc4random())
}

I will check what it returns:

let myReturnedArray: Array<UInt32> = self.myReferenceTest(arrayOfNumbers, referenceArray: &arrayOfNumbers)
    
if arrayOfNumbers === myReturnedArray {
    println("(rS)") // returned value is the same instance
} else {
    println("(rD)") // returned value is a different instance
}

and I have a test method with two parameters, the first is the Array itself, the second one is just the reference of the same Array. I'm trying to do different things in that method to see what will happen.


1st case

func myReferenceTest (directory: Array<UInt32, Inuit referenceArray: Array<UInt32) -> Array<UInt32> {
    if directArray === referenceArray {
        println("(pS)") // the same instance...
    } else {
        println("(pD)") // different instance ...
    }
            
    return directArray
}

that will print "(pS)", so it looks the reference has been passed over as first paramater not the copy of the struct. the consol says "(rS)", the the returned value was the same reference and not a copy.


2nd case

func myReferenceTest(directArray: Array<UInt32>, inout referenceArray: Array<UInt32>) -> Array<UInt32> {
    if directArray === referenceArray {
        println("(pS)")
    } else {
        println("(pD)")
    }
    
    directArray[0] = 12
    
    return directArray
}

it says still the same "pS" and "rS", but if I print the original array's [0] element it is updated to 12, however directArray was not an inout paremeter at all. the reference was passed over, and the reference was returned, and I also called a non-mutating method on the array, and I was able to make certain changes.


3rd case

func myReferenceTest(directArray: Array<UInt32>, inout referenceArray: Array<UInt32>) -> Array<UInt32> {
    if directArray === referenceArray {
        println("(pS)")
    } else {
        println("(pD)")
    }

    var myOtherArray = directArray
    myOtherArray.append(arc4random())
    
    return myOtherArray
}

the console says "pS" but "rD" becase I've called a mutating method in the directArray that caused the array was copied (O(n)), and I made the changes on another instance.


5th case

func myReferenceTest(directArray: Array<UInt32>, inout referenceArray: Array<UInt32>) -> Array<UInt32> {
    if directArray === referenceArray {
        println("(pS)")
    } else {
        println("(pD)")
    }

    var myOtherArray = directArray
    myOtherArray.append(arc4random())
    referenceArray = myOtherArray
    
    return myOtherArray
}

same as the recent one, but it will say on console "pS" and "rS" again. so it seems the reference was returned and not the copy of the myOtherArray.


6th case

func myReferenceTest(directArray: Array<UInt32>, inout referenceArray: Array<UInt32>) -> Array<UInt32> {
    if directArray === referenceArray {
        println("(pS)")
    } else {
        println("(pD)")
    }

    referenceArray.append(arc4random())
    directArray[0] = 12

    return directArray
}

it will show "pS" and "rD" again, and the reference array's first element is 12 and it looks the directArray was copied after I've call a mutating method on the referenceArray. you can check it without doubt: the referenceArray is still identical to my original array but the directArray is different now.

If I don't return the directArray that will be released at all when the method is run out of its scope, becase the copy is created in the method's scope only.


not conclusion but observation instead

it seems the Array is always creates a new instance of itself when you call a mutating method on it, which is in accordance with the Swift documantation about the mutating keyword:

However, if you need to modify the properties of your structure or enumeration within a particular method, you can opt in to mutating behavior for that method. The method can then mutate (that is, change) its properties from within the method, and any changes that it makes are written back to the original structure when the method ends. The method can also assign a completely new instance to its implicit self property, and this new instance will replace the existing one when the method ends.

(source)

that behaviour is pretty much as same as the Swift logic here: the compiler tries to not extend the code with copying of any object until it certainly necessary, which looks – in the case of Array at least – happaning when a mutating method is called on the object.

you will find more information about which methods of which Swift types are mutating in the Swift Standard Library Reference, here.


nevertheless, even if the Array looks to be defined as a struct, it defintely behaves like a class, because they have some class-level privileges:

  • Type casting enables you to check and interpret the type of a class instance at runtime.
  • Reference counting allows more than one reference to a class instance.

I have not found evidence the rest two privileges of classes, but having these two class-level privilages indicates the Array is definitely more than a simple struct, which makes the statement #1 ambiguous.

having those prvileges indicates the reference is being passed (logically) in every case.


the Dictionary is a simpliest case, becase it looks an plain NSMutableDictionary behind the scenes, when I ask the class's name via object_getClassName(...), it clearly refers to an instance of a mutable dictionary from Obj-C, which is defintely not a struct even if the declaration of Dictionary indicates a struct.

it is pontless to go further in guessing of what happens in the engine, the compiler is still Beta so we don't know how the final compiler will work; evenetually many conspiration-theory can be defined about them (or already hve been), I've tried to focus on the facts only which can be provable during identifying them in background, which may be changed in the future, but those are the facts currently.

like image 170
holex Avatar answered Nov 11 '22 14:11

holex


Array are special cases. They are value types with a special behavior.

At the page where you found the point 3) there are enough information to understand why:

The assignment and copy behavior for Swift’s Array type is more complex than for its Dictionary type. Array provides C-like performance when you work with an array’s contents and copies an array’s contents only when copying is necessary

and, immediately after you statement:

Instead, both arrays share the same sequence of element values. When you modify an element value through one array, the result is observable through the other.

For arrays, copying only takes place when you perform an action that has the potential to modify the length of the array.

So, reading these points we understand that:

  • array are struct but with a special behavior, the mainly reason is to conserve C-like performance.
  • the copy is made only when the length of the copied array is changed

Last thing: remember that, when an array is copied, only value types (structures, enumerations, primitive types) are copied. Objects are not copied, only their references (pointers) are copied

like image 33
LombaX Avatar answered Nov 11 '22 14:11

LombaX


Array are just value types. That is correct. And it is very likely to use COW(Copy-On-Write) to avoid high copying cost of brute-force value-type semantics implementation. So it will be copied only when the new instance is going to be mutated.

There was some trouble with this semantics issue in beta period, but it was eliminated in later betas because having a grey-colored behaviour is insane, and now (Swift version 1.0) it is strictly value type semantics.

Your point #3 is just an explanation about the optimisation --- which is an implementation details meaningful only for optimisations. Forget about implementation details when you thinking about semantics. Just use arrays and dictionaries just like any other value-type stuffs. And remember the implementation details only when you need to concern about the performance and optimisation. In most cases, just using is fine in both of semantics or performance perspectives.

BTW, reference type means it has automatic/implicit identity that can be referenced.

like image 1
eonil Avatar answered Nov 11 '22 13:11

eonil