cannot use mutating member on immutable value: 'XXX' is a 'let' constant - swift

i am new to swift .i am trying with several way but i failed when try as suggest error then anther error occur . what is going on exactly here i don't know
public struct ThermometerStruct {
var temperature: Double = 0.0
public mutating func registerTemperature(temperature: Double) {
self.temperature = temperature
}
}
let thermometerStruct = ThermometerStruct()
thermometerStruct.registerTemperature(temperature : 56.0)
ERROR at line 14, col 5: cannot use mutating member on immutable value: 'thermometerStruct' is a 'let' constant
thermometerStruct.registerTemperature(temperature : 56.0)
^~~~~~~~~~~~~~~~~
INFO at line 13, col 5: change 'let' to 'var' to make it mutable
let thermometerStruct = ThermometerStruct()
^~~
var

The error-message already tells you, what you need to change. Replace let with var.
structs ar value types. This means, if any property of a struct is modified, the instance needs to be declared as var.
When your struct has a method, that is modifying one of the struct's properties, this method is mutable. mutable methods can only be called on instances you have declared as var.

In case you're using lazy var in structs and you're passing it to a function or having a let refference of the object, accessing the lazy var is implicitly mutating to the object.
so you need make a var out of it.
struct Test {
lazy var a: String = ""
}
...
func something(test: Test) {
let a = test.a //throws compile error
var test = test
let a = test.a //OK
}

Related

Accessing a lazy property on a struct mutates the struct

I have a lazy property in a struct and every time I access it, it mutates the struct.
var numbers = [1,2,3]
struct MyStruct {
lazy var items = numbers
}
class MyClass {
var myStructPropery: MyStruct = MyStruct() {
didSet {
print(myStructPropery)
}
}
}
var myClass = MyClass()
myClass.myStructPropery.items
myClass.myStructPropery.items
myClass.myStructPropery.items
Result:
Print (didSet) will be called every time.
The ideal behaviour should be first time mutation only (since that's how lazy variables behave). Is this a bug in Swift or am I doing something wrong?
What you are seeing is is that the observed property items has notified its observer that it has been mutated because it was accessed, and a lazy variable is by definition mutating since it will be set at a later time. Therefore didSet gets called to handle this.
The lazy variable is actually only set once even though it signals that it has mutated and the property myStructPropery is only mutated once when the variable is first set but is the same instance after that.
Here is how we can verify this, first change the lazy var declaration so it's more like how we usually declare such a variable
lazy var items: [Int] = { numbers }()
and then add a print statement
lazy var items: [Int] = {
print("inside lazy")
return numbers
}()
If we now run the test code
var myClass = MyClass()
myClass.myStructProperty.items
myClass.myStructProperty.items
myClass.myStructProperty.items
we see that "inside lazy" only prints once. To verify that the property myStructProperty isn't changed we can make the struct conform to Equatable and perform a simple check inside didSet
didSet {
if oldValue != myStructProperty {
print(myStructProperty)
}
}
Now running the test we see that the print inside didSet is never executed so myStructProperty is never changed.
I have no idea if this behaviour is a bug but personally it feels like it might be complicate for the property observer to stop observing a lazy property once it was accessed or for a lazy var to not be defined as mutating once it is set.
I started debugging this with following set up -
var numbers = [1,2,3]
struct MyStruct {
lazy var items = numbers
}
class MyClass {
var myStructPropery: MyStruct = MyStruct() {
didSet {
// Changed this to make sure we are not invoking getter here
print("myStructPropery setter called")
}
}
}
let myClass = MyClass()
myClass.myStructPropery.items
myClass.myStructPropery.items
myClass.myStructPropery.items
I can reproduce the problem on Xcode 12.5 using Swift 5.4.
Attempt 1 : Turn var numbers into let numbers - Does NOT work.
let numbers = [1,2,3]
Attempt 2 : Assign the value inline without using an extra variable - Does NOT work.
struct MyStruct {
lazy var items = [1,2,3]
}
Attempt 3 : Assign the value inline using the full blown getter syntax - Does NOT work.
struct MyStruct {
lazy var items: [Int] = {
return [1,2,3]
}()
}
At this point, we are out of options to try. Even though we can clearly see that return [1,2,3] in the last attempt is executed exactly once, the MyClass.myStructPropery.modify is called repeatedly on access to items.
Maybe Swift Forums is a better place to discuss this.

Swift for var list.enumerated()

I have an array of objects whose type is a struct with mutating functions. So I got this code:
for (index, object) in objects.enumerated() {
otherArray[index] = object.someMutatingFunction(...)
}
This leads me to this error Cannot use mutating member on immutable value of type 'Blabla' which I can fix by adding var:
for var (index, object) in objects.enumerated() {
otherArray[index] = object.someMutatingFunction(...)
}
But then I get another warning Variable 'index' was never mutated; consider changing to 'let' constant which I don't know how to fix elegantly. The only idea is too add a new var variable. Is there anything else I can do to prevent this warning?
Prefix the object variable with the var keyword:
struct S {
mutating func f() { }
}
let array = [S(), S()]
for (index, var object) in array.enumerated() {
object.f()
}
Note as Hamish points out in the comment to this answer that the elements of the array will not be modified. Only the local copy of object inside the scope of the for loop can be modified.
If you want to modify array you have to declare it var outside the scope of the for loop, then assign to array indices.

Specify a settable property/variable in a protocol

I would like my protocol to declare that there is a read/write property available. I have attempted it, but this does not work:
protocol EdibleThing {
var eaten: Bool { get set }
}
class Pickle: EdibleThing { var eaten = false }
class RusticGrapefruit: EdibleThing { var eaten = false }
class Jar {
let contents: [EdibleThing] = [Pickle(), RusticGrapefruit()]
var nextItem: EdibleThing {
return contents.last ?? Pickle() // Lazy pickle generation technology
}
func eat() {
let food = nextItem
food.eaten = true // (!) ERROR: Cannot assign to result of this expression
}
}
What am I doing wrong? I think I've declared that the protocol has a get/set var called eaten, so why can't I set it?
The protocol might be implemented by either classes and structs - that prevents you from changing the internal status of an instance of a class or struct implementing that protocol using an immutable variable.
To fix the problem you have to either declare the food variable as mutable:
func eat() {
var food = nextItem
food.eaten = true // (!) ERROR: Cannot assign to result of this expression
}
or declare the EdibleThing protocol to be implementable by classes only:
protocol EdibleThing : class {
var eaten: Bool { get set }
}
Note that this happens because food is a variable of EdibleThing type - the compiler doesn't know if the actual instance is a value or reference type, so it raises an error. If you make it a variable of a class type, like this:
let food: Pickle = nextItem as! Pickle
the compiler knows without any ambiguity that it's a reference type, and in that case it allows the assignment. But I guess that breaks your app logic... so consider it just as an example
You're mutating food.
Replace let food = nextItem with var food = nextItem
The problem is that you can't mutate a property on a value type defined by let.
Even though both of RusticGrapefruit and Pickle are class implementations (reference types), the protocol could be assigned to a value type like a struct. The compiler detects a potential problem and stops us.
Two solutions:
Change let to var (in my case, this would mean changing a lot of code that refers to objects of this type. Also, I like the semantic value and possible compiler optimizations from let)
Declare the protocol as only valid for classes: protocol EdibleThing: class { }

Swift dictionary member compile error

When I make a dictionary a member, assignment doesn't compile:
struct MyClass {
var lists = [String:Int]();
init() {}
func add() {
// this compiles
var x = [String:Int]();
x["y"] = 3;
// this gets the compiler error 'cannot assign to the result of this expression'
self.lists["y"] = 3;
}
What is it about membership that breaks the compilation? I don't get this error if I put that line in init() FWIW.
You need to add mutating from the function declaration like this because properties are readonly if you don't specify that keyword in struct:
mutating func add()

Optional chaining and Array in swift

Let's take these two simple classes to show my problem:
class Object{
var name:String?
// keep it simple... and useless
}
class TestClass {
var objects:AnyObject[]?
func initializeObjects (){
objects?.insert(Object(), atIndex:0) // Error
objects?.insert(Object(), atIndex:1) // Error
objects?.insert(Object(), atIndex:2) // Error
}
}
With this implementation I get 3 errors Could not find member 'insert' where I try to add object into the objects array.
Now, if I remove the optional from objects definition and the optional chain in initializeObjects it works with no problem (here the working code)
class Object{
var name:String?
}
class TestClass {
var objects:AnyObject[] = AnyObject[]() // REMOVE optional and initialize an empty array
func initializeObjects (){
objects.insert(Object(), atIndex:0) // Remove Opt chaining
objects.insert(Object(), atIndex:1) // Remove Opt chaining
objects.insert(Object(), atIndex:2) // Remove Opt chaining
}
}
I can't understand what is wrong in the first implementation.
I thought it checks with objects? if objects is not nil and at this point it adds an element using insert:atIndex:. But I'm probably wrong -.-
Arrays in Swift are structs and structs are value types.
Optionals in Swift are actually enums (Optional<T> or ImplicitlyUnwrappedOptional<T>).
When you are unwrapping an optional (implicitly or explicitly) of a value type, what you get is actually a constant copy of the struct. And you can't call mutating methods on a constant struct.
Executing objects?.insert(Object(), atIndex:0) basically means this:
if let tmp = objects {
tmp.insert(Object(), atIndex:0)
}
As a workaround, you need to assign the unwrapped value to a variable and then assign the variable back to your optional property. That's how value types work.
This is reproducible for any struct, not only Arrays:
struct S {
var value: Int = 0
}
var varS: S = S()
varS.value = 10 //can be called
let constS: S = S()
constS.value = 10 //cannot be called - constant!
var optionalS: S? = S()
optionalS?.value = 10 //cannot be called, unwrapping makes a constant copy!
//workaround
if optionalS {
var tmpS = optionalS!
tmpS.value = 10
optionalS = tmpS
}
Some relevant discussion here: https://devforums.apple.com/thread/233111?tstart=60