Swift structs are immutable, so how come I can change them? [duplicate]
Asked Answered
W

1

0

Structs are immutable meaning that they can't change. I must clearly misunderstand this whole concept as it seems that sometimes I do get to change the struct, and othertimes I don't.

Consider this code:

struct SomeStruct: View {
    var someProperty = ""
    
    var someComputedProperty: String {
        get { someProperty }
        set { someProperty = "a" } // WORKD
    }
    
    func someFunc() {
        someProperty = "b" // ERROR; BUT WORKS WITH mutating func
    }
    
    var body: some View {
        Button("SomeButton", action: {
            someProperty = "c" // ERROR
        })
    }
}

struct SomeOtherStruct {
    func someOtherFunct() {
        var someStruct = SomeStruct()
        
        someStruct.someProperty = "THIS WORKS"
    }
}

The only place where it's not actually allowed to change the struct is in the Button closure. Why is it sometimes allowed, and othertimes not? Or have I simply missunderstood what immutability actually implies?

Warrigal answered 26/12, 2023 at 21:34 Comment(2)
You should look at the Apple SwiftUI tutorials. SwiftUI has property wrappers with their own storage that allow you to mutate the View. That is the main reason why mutating views should Never be held in a variable that isn’t a ViewBuilder. When structs are mutated/changed a copy is made of the original version with the new value.Frugal
You have a mistake, someProperty should be let.Flied
U
1

Structs aren't immutable. However, what they are something called value types which means they have implicit copying when passed to functions. If you pass a struct to a function and perform an action on the struct inside the function that mutates it, the outer version of the struct remains unchanged since the one passed to the function was in fact a copy.

Consider the following:

struct Foo {
    var value: Int
}

func bar(_ f: Foo) {
    f.value += 1
}

var f = Foo(value: 1)
bar(f)
// f.value is still 1

In order to mutate a struct inside a function you must explicitly pass it as inout:

func baz(_ f: inout Foo) {
    f.value += 1
}

baz(&f)
// f.value is now 2

Edit: note that I haven't used Swift UI and I don't really know how the behaviour of structs applies to that particular framework. This answer is about structs in Swift in the general sense.

Unusual answered 26/12, 2023 at 21:40 Comment(5)
You still aren't mutating the struct. Structs are immutable. In your second code block you are passing a reference to the variable that holds the value and that variable's value is changed. bar would need to be a mutating func. When you call a mutating func on a struct, Swift creates a copy of the struct and assigns that to the variable that held the original value.Dorsy
@Dorsy I can’t find any mention of that in language docs. Is it mentioned in the language specs somewhere?Unusual
@Dorsy sorry, you're right. I just read this: docs.swift.org/swift-book/documentation/…Unusual
Don't worry. The whole subject is pretty complex, and finding out how it is actually implemented would involve digging into compiler internals. What matters is the semantics which we need to rely on. For example consider this snippet semantically it doesn't matter if the copy is made when a1 is assigned to a2 or only when a1 is mutated. As an optimisation, however, I believe it is that latter that actually occursDorsy
It seems reasonable to assume the compiler will only make a single copy at least. I suppose it doesn’t really matter if copying happens when assigning or when mutating.Unusual

© 2022 - 2024 — McMap. All rights reserved.