Swift pass struct by reference?
Asked Answered
H

3

25

I've looked to similar questions but I haven't seen an answer that I am satisfied with.

Is it possible or advisable to pass structs by reference? If so how?

Here is a code as a reference for examples:

struct MyData {
    var contentId: Int = 0
    var authorId: Int = 0
    var image: UIImage = UIImage(named: "myimage")
}

As you see my main reason of doing this is because not having my image multiplying all over the place.

Habitation answered 18/7, 2015 at 20:47 Comment(2)
Luckily your image is a reference type and you will incur about the same cost from copying the struct by value as you would by passing around the reference to the UIImage.Sociometry
this would be a good answer with a sourceHabitation
S
62

Structs can be passed by reference using the inout keyword and the & operator.

struct Test {
    var val1:Int
    let val2:String

    init(v1: Int, v2: String) {
        val1 = v1
        val2 = v2
    }
}

var myTest = Test(v1: 42, v2: "fred")

func change(test: inout Test) {
    // you can mutate "var" members of the struct
    test.val1 = 24

    // or replace the struct entirely
    test = Test(v1: 10, v2: "joe")
}
change(test: &myTest)
myTest // shows val1=10, val2=joe in the playground

This practice is discouraged unless you can prove it's the only way to get the performance you need in a critical situation.

Note that you won't save the burden of copying the UIImage by doing this. When you put a reference type as a member of a struct, you still only copy the reference when you pass it by value. You are not copying the contents of the image.

Another important thing to know about struct performance is copy-on-write. Many built in types like Array are value types, and yet they're very performant. When you pass around a struct in Swift, you don't undergo the burden of copying it until you mutate it.

Check out the WWDC video on value types to learn more.

Sociometry answered 18/7, 2015 at 21:3 Comment(4)
Has this feature changed in Swift 4 by any chance? I've just tested my own code as well as copied this, but the values in the struct remained the same as they were prior to being passed into the function both times.Electricity
Note that this is not passing by reference. The inout keyword makes a copy of the value when it's passed in and then the original value is replaced by the copy when the value is returned. Passing by reference would involve no copying.Wehner
Can you clarify what you mean by "when the value is returned"?Sociometry
custom (user defined) struct is not copy-on-write, you must implement it by yourself.Grasshopper
H
5

First of all, if you pass a struct it gets normally passed by value. Even though all properties of the struct get copied only the pointer to properties which are reference types like UIImage gets duplicated (only 8 bytes):

var data1 = MyData()
var data2 = data1
// both data1 and data2 point to the same image

Also the compiler optimizes the code so structs get internally passed by reference and copied if needed.

As an example of passing a struct by reference in top level code you can use inout parameters: So an inout parameter can be passed by reference but Swift's general way of implementing inout is that the parameter gets passes by value and after the function returns it gets reassigned.

Although the compiler optimizes the function call so you could get a true reference in some situations. This optimization only affects variables which aren't computed or have a property observer like willSet and didSet (and probably other cases).

So you should only rely on the current instance which is not in the scope of the function:

struct Test {
    var value = 0
}

// case 1
var myTest = Test()

// case 2
var myTest = Test() {
didSet { print("I was set") }
}

// case 3
var myTest: Test {
get{ return Test() }
set{ print("I set myself") }
}

func mutateTest(inout t: Test) {
    // directly referencing to myTest

    myTest.value // value = 0 in all cases

    t.value = 42
    myTest.value // value = 42 in case 1 ; value = 0 in case 2 and 3

    t = Test()   // nothing gets printed
    myTest.value // value = 0 in all cases

    t = Test()   // nothing gets printed
    t.value = 3  // value = 3 in case 1 ; value = 0 in case 2 and 3
}

changeTest(&myTest)
//case 2: "I was set", case 3 "I set myself" get printed now

myTest.value // value = 3 in all cases

As you can see myTest and t are only equivalent in case 1 ("true" reference semantics). So this makes a huge difference if you also reference myTest in the function itself. But as long as you don't do this you are good to go.

Hermeneutics answered 19/7, 2015 at 11:40 Comment(0)
B
3

Structs are never passed by reference. Period. Calling an inout with a struct passes in a mutable copy and the copy overwrites the call site on return. You can find details in this piece: https://chris.eidhof.nl/post/structs-and-mutation-in-swift/

Bricebriceno answered 21/6, 2021 at 13:37 Comment(1)
You are right. While technically the effect is the same, it's a very important destinction. Structs are value types. Period. They are never passed by a reference.Louiselouisette

© 2022 - 2024 — McMap. All rights reserved.