Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why constant constraints the property from a structure instance but not the class instance?

When I trying to change the ID property of the byValueObj instance, I received an error that told me I cannot assign to the property of a constant, even though the property is a variable. However, I can do it on a class instance. I kind of knowing that it maybe has something to do with the by value and by reference mechanism. But I don't have a very clear and correct understanding of it. Can someone explain it for me? Thanks.

struct CreatorValue{
    var ID = 2201
}
class CreatorRefer{
    var ID = 2203
}

let byValueObj = CreatorValue()
let byReferObj = CreatorRefer()

byValueObj.ID = 201 //Error: cannot assign to property: 'byValueObj' is a 'let' constant
byReferObj.ID = 203 //works fine here
like image 683
SLN Avatar asked Jun 24 '16 10:06

SLN


People also ask

How do you change a constant value in Swift?

The value stored in a constant cannot be changed. Xcode is clear about the change we need to make to fix the issue. In Swift, a variable is mutable whereas a constant is immutable. Xcode suggests turning the numberOfWheels constant into a variable by replacing the let keyword with the var keyword.

What is the difference between class and structure in Swift?

struct are value types. It means that if you copy the instance of the structure to another variable, it's just copied to the variable. Classes are reference types. It means that if you assign an instance of the class to a variable, it will hold only the reference to the instance and not the copy.

What is a structure in Swift?

In Swift, a struct is used to store variables of different data types. For example, Suppose we want to store the name and age of a person. We can create two variables: name and age and store value. However, suppose we want to store the same information of multiple people.


2 Answers

Structures in Swift are value types – and, semantically speaking, values (i.e 'instances' of value types) are immutable.

A mutation of a value type, be it through directly changing the value of a property, or through using a mutating method, is equivalent to just assigning a completely new value to the variable that holds it (plus any side effects the mutation triggered). Therefore the variable holding it needs to be a var. And this semantic is nicely showcased by the behaviour of property observers around value types, as iGodric points out.

So what this means is that you can think of this:

struct Foo {
    var bar = 23
    var baz = 59
}

// ...

let foo = Foo()
foo.bar = 7 // illegal

as doing this:

let foo = Foo()

var fooCopy = foo // temporary mutable copy of foo.

fooCopy.bar = 7   // mutate one or more of the of the properties

foo = fooCopy     // re-assign back to the original (illegal as foo is declared as
                  // a let constant)

And as you can clearly see – this code is illegal. You cannot assign fooCopy back to foo – as it's a let constant. Hence, you cannot change the property of a value type that is declared as a let, and would therefore need make it a var.

(It's worth noting that the compiler doesn't actually go through this palaver; it can mutate the properties of structures directly, which can be seen by looking at the SIL generated. This doesn't change the semantics of value types though.)


The reason you can change a mutable property of a let constant class instance, is due to the fact that classes are reference types. Therefore being a let constant only ensures that the reference stays the same. Mutating their properties doesn't in any way affect your reference to them – you're still referring to the same location in memory.

You can think of a reference type like a signpost, therefore code like this:

class Foo {
    var bar = 23
    var baz = 59
}

// ...

let referenceToFoo = Foo()

you can think of the memory representation like this:

|    referenceToFoo     |  --->  | Underlying Foo instance |
| (a reference to 0x2A) |        |<----------------------->|
                                 |0x2A       |0x32         |0x3A
                                 |  bar: Int |  baz : Int  |
                                 |     23    |      59     |

And when you mutate a property:

referenceToFoo.bar = 203

The reference (referenceToFoo) itself isn't affected – you're still pointing to the same location in memory. It's the property of the underlying instance that's changed (meaning the underlying instance was mutated):

|    referenceToFoo     |  --->  | Underlying Foo instance |
| (a reference to 0x2A) |        |<----------------------->|
                                 |0x2A       |0x32         |0x3A
                                 |  bar: Int |  baz : Int  |
                                 |    203    |      59     |

Only when you attempt to assign a new reference to referenceToFoo will the compiler give you an error, as you're attempting to mutate the reference itself:

// attempt to assign a new reference to a new Foo instance to referenceToFoo.
// will produce a compiler error, as referenceToFoo is declared as a let constant.
referenceToFoo = Foo()

You would therefore need to make referenceToFoo a var in order to make this assignment legal.

like image 156
Hamish Avatar answered Oct 18 '22 11:10

Hamish


struct is a value type. If you edit them you are calling the default setter for the property which is nothing but a mutating method, which is nothing but a static method of the struct which has self as the first argument as inout which returns the method (Swift for now has curry syntax for unapplied method calls, but will change that to a flattened one). Just as a side note: When the method is not mutating it will not be inout.

Because inout is working by copy in - copy out, didSet is called, even if nothing changed.

“If you pass a property that has observers to a function as an in-out parameter, the willSet and didSet observers are always called.” Excerpt From: Apple Inc. “The Swift Programming Language (Swift 3).” iBooks. https://itun.es/de/jEUH0.l

Code that a var struct will be copied when a property is mutated:

struct Size {
    var width: Int
    var height: Int
}

struct Rectangle {
    var size: Size
}

var screenResolution = Rectangle.init(size: Size.init(width: 640, height: 480)) {
    didSet {
        print("Did set the var 'screenResolution' to a new value! New value is \(screenResolution)")
    }
}

screenResolution.size.width = 800

Calls the didSet even though we only mutated a the Int in the property of the struct.

If it would be complete new copy then you would expect the mutated struct to be a new copy with new memory allocation. But this is not what happens in the example, see code below.

// Value of a pointer is the address to the thing it points to
internal func pointerValue(of pointer: UnsafeRawPointer) -> Int {
    return unsafeBitCast(pointer, to: Int.self)
}

internal struct MyStruct {
    internal var a: Int
}

internal var struct1: MyStruct = MyStruct.init(a: 1)
pointerValue(of: &struct1) // output: 4405951104

struct1.a = 2
pointerValue(of: &struct1) // output: 4405951104

So the structure is not copied. But because it is inout:

“Write your code using the model given by copy-in copy-out, without depending on the call-by-reference optimization, so that it behaves correctly with or without the optimization.” Excerpt From: Apple Inc. “The Swift Programming Language (Swift 3).” iBooks. https://itun.es/de/jEUH0.l


Code example with inout only:

struct MyType {
    var x: Int
    mutating func m1(y: Int) -> Int {
        x += 1
        return x + y
    }

}

let mytypem1: (inout MyType) -> (Int) -> Int = MyType.m1
var myType = MyType.init(x: 1) // has to be "var"
mytypem1(&myType)(2) // returns 3
like image 34
Binarian Avatar answered Oct 18 '22 12:10

Binarian