Why Constant Constraints the Property from a Structure Instance But Not the Class Instance

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

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.

Structure instance property value

structs are Value types, while classes are References.

While you can set properties of a constant reference type, you can not change the reference itself.

let someVideoMode = VideoMode()
someVideoMode = VideoMode()

would cause an error.

You can find a detailed description in the documentation you actually got this sample from.

Confusion about constant class instance in Swift

The class instance is not constant. Just the reference is.

This means you cannot do this:

let myClassInstance = MyClass()
myClassInstance = MyClass()

Why is constant instance of a value type can NOT change its properties while constant instance of a reference type can?

why is constant instance of a value type can NOT change its properties

Because value type is treated as an indivisible unit: it gets copied on assignment, passing it as a parameter behaves like a copy, and so using const-ness locks down the entire struct. In a sense, rangeOfFourItems variable represents the structure itself, not a pointer or a reference to it.

while constant instance of a reference type can?

This is not entirely correct to say that declaring a const variable of reference type makes the instance constant as well. Only the reference is constant, not the instance.

If you think about it, that is the only way this could be meaningfully implemented, because multiple variables can reference the same by-reference instance. If one of these variables is constant and the other one is not constant, assigning a non-const reference to a constant variable could not possibly lock down the referenced object, which would lock out the non-const reference as well:

var a = ByRefType()
let b = a; // b is a constant reference to the same instance as "a"
a.property = newValue; // Prohibiting this assignment would be inconsistent

Of course the constant variable itself (e.g. b above) could not be re-assigned, unlike the non-constant variable a.

Why I can change/reassigned a constant value that Instantiated from a class

As @Wain has said – it's due to the nature of reference types. The instance being a let constant only means you cannot assign a new reference to it – but says nothing about the actual mutability of the instance itself.

If you change your class to a struct, you'll see how the behaviour differs with value types, as changing a property changes the actual value of your Person – therefore you are unable to do so if it's a let constant. However I somewhat doubt you'll want to make your Person a struct, as two people with the same name shouldn't be considered to be the same person.

If you only wish your properties to be assigned upon initialisation (and then read-only for the lifetime of the instance), then I would recommend making them let constants (instead of making their setters private). This will ensure that you cannot even change their value from within your class, once assigned.

The rule is as long you give a property a value before the super.init() call – you can make it a let constant (in this case, you just have to assign them in the initialiser before using self).

class Person {
let firstName: String
let lastName: String

init(firstName: String, lastName: String) {
self.firstName = firstName
self.lastName = lastName
}

...

Setting Part of Swift Computed Structure

The way to understand this is to consider a similar but simpler case without your extra complications. Let's just talk about this:

struct S {
var name = "matt"
}
var s = S()
s.name = "mogelbuster"

How does that work? What happens when we set s.name? First, pull out the current value of s; then, we set its name property; then, we set the value of s to this new struct. (You can easily confirm that last part by putting a setter observer on s.)

Setting a struct's property by way of a reference, then involves getting the reference's value (the struct), setting the property, and setting the reference's value with the new struct. That's because a struct is a value type. It cannot be mutated in place, so setting a property by way of a reference to a struct involves setting into the reference.

What you are doing with testPoint is merely a computed-variable version of the same process. The mere act of speaking of testPoint.x means that we must get testBacking, to find out what it is. Thus, the getter is called. Then you set into testPoint.x, thus calling the setter to write the new value back into testBacking.


Note that the same thing would not be true if we were working with a class. Here's a variation on your original example:

    class PointHolder {
var point = CGPoint(x:3, y:5)
var x : CGFloat {
get { return point.x }
set { point.x = newValue }
}
}
var testBacking = PointHolder()
var testPoint:PointHolder {
get {
print("getter called")
return testBacking
} set {
print("setter called with newValue = \(newValue)")
testBacking = newValue
}
}
testPoint.x = 10 // getter called, but _not_ setter

In the last line, the testPoint setter is not called — because a class instance is a reference type and is mutable in place. testBacking is changed without setting, because what we got from testBacking when we said testPoint.x, and the getter was called, is a reference; a change to this changes the thing testBacking points to without setting into it.

How to mute the warning of never mutated in Swift?

Because actually you don't change the Person object,
With let you can change the properties of the object. But you can't change the object it self.

So change your code to what the warning lead you.


And of course you can try before asking this question.

Why does a property observer run when a member of the existing value is changed?

Swift needs to treat the mutation of myValue.msgStr as having value semantics; meaning that a property observer on myValue needs to be triggered. This is because:

  1. myValue is a protocol-typed property (which also just happens to be optional). This protocol isn't class-bound, so conforming types could be both value and reference types.

  2. The myStr property requirement has an implicitly mutating setter because of both (1) and the fact that it hasn't been marked nonmutating. Therefore the protocol-typed value may well be mutated on mutating though its myStr requirement.

Consider that the protocol could have been adopted by a value type:

struct S : MyProtocol {
var msgStr: String?
}

In which case a mutation of msgStr is semantically equivalent to re-assigning an S value with the mutated value of msgStr back to myValue (see this Q&A for more info).

Or a default implementation could have re-assigned to self:

protocol MyProtocol {
init()
var msgStr: String? { get set }
}

extension MyProtocol {
var msgStr: String? {
get { return nil }
set { self = type(of: self).init() }
}
}

class MyClass : MyProtocol {
required init() {}
}

class MyWrapperClass {

// consider writing an initialiser rather than using an IUO as a workaround.
var myValue: MyProtocol! {
didSet {
print("In MyWrapperClass didSet")
}
}
}

In which case the mutation of myValue.myStr re-assigns a completely new instance to myValue.

If MyProtocol had been class-bound:

protocol MyProtocol : class {
var msgStr: String? { get set }
}

or if the msgStr requirement had specified that the setter must be non-mutating:

protocol MyProtocol {
var msgStr: String? { get nonmutating set }
}

then Swift would treat the mutation of myValue.msgStr as having reference semantics; that is, a property observer on myValue won't get triggered.

This is because Swift knows that the property value cannot change:

  1. In the first case, only classes can conform, and property setters on classes cannot mutate self (as this is an immutable reference to the instance).

  2. In the second case, the msgStr requirement can only either be satisfied by a property in a class (and such properties don't mutate the reference) or by a computed property in a value type where the setter is non-mutating (and must therefore have reference semantics).

Alternatively, if myValue had just been typed as MyClass!, you would also get reference semantics because Swift knows you're dealing with a class:

class MyClass {
var msgStr: String? {
didSet {
print("In MyClass didSet")
}
}
}

class MyWrapperClass {
var myValue: MyClass! {
didSet {
print("In MyWrapperClass didSet")
}
}
}

let wrapperObj = MyWrapperClass()
wrapperObj.myValue = MyClass() // Line1
wrapperObj.myValue.msgStr = "Some other string" // Line2

// In MyWrapperClass didSet
// In MyClass didSet

Reference a constant field of another class when using a generic property named after it

While Rufu's Answer resolve the problem, it does not explain why the problem exists.

The problem exists because your property:

public IArbitraryQualifier ArbitraryQualifier { get; }

Is the same name as your concrete class:

public class ArbitraryQualifier

Since the code is contained within the Person class, the compiler will resolve names to (locally=this) Locally-Scoped variables (var... etc), Then Local Fields/Properties, then to Accessible Named classes within the current namespace, then up the namespace chain.

Renaming the property name on the local/this class also solves the problem.

public interface IArbitraryQualifier
{
int Qualification
{
get;
}
}

public class ArbitraryQualifier : IArbitraryQualifier
{
public const int MIN_QUALITY = 1;
public int Qualification
{
get;
}
}

public class Person
{
public IArbitraryQualifier ArbitraryQualifier2
{
get;
}

public bool AmIARealBoy
{
get
{
return this.ArbitraryQualifier2.Qualification >= ArbitraryQualifier.MIN_QUALITY;
}
}
}


Related Topics



Leave a reply



Submit