Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How Memory safety is preserved because the two stored properties don’t interact in any way in Swift?

Tags:

swift

import UIKit

var robGlobal = Player(name: "Rob", health: 10, energy: 10)
var matt = Player(name: "Matt", health: 5, energy: 10)


struct Player {
    var name: String
    var health: Int
    var energy: Int
    
    static let maxHealth = 10
    mutating func restoreHealth() {
        health = Player.maxHealth
    }
    
    func balance(_ x: inout Int, _ y: inout Int) {
        let sum = x + y
        x = sum / 2
        y = sum - x
    }
    
}

class ViewController: UIViewController {
    
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        
        //MARK1: -Conflicting Access to self in Methods
        
        robGlobal.shareHealth(with: &matt)
        
        robGlobal.shareHealth(with: &robGlobal) // NOT OK in compile time Error: //Overlapping accesses to 'robGlobal', but modification requires exclusive access; consider copying to a local variable
        
        var robNewLocal = Player(name: "Rob", health: 10, energy: 10)
        
        robNewLocal.shareHealth(with: &robNewLocal) // NOT OK in compile time
        
        
        //MARK2: -Conflicting Access to properties in Methods
        
        robGlobal.someFunction()  // OK
        
        robGlobal.anotherFunction() // NOT OK in Run Time Error: Thread 1: Simultaneous accesses to 0x108cc4050, but modification requires exclusive access
        
    }
}


extension Player {
    mutating func shareHealth(with teammate: inout Player) {
        balance(&teammate.health, &health)
        
    }
    func someFunction() {
        var robLocal = Player(name: "Rob", health: 10, energy: 10)
        balance(&robLocal.health, &robLocal.energy)  // OK
    }
    
    func anotherFunction() {
        balance(&robGlobal.health, &robGlobal.energy)  // NOT OK in Run Time
    }
}

I am trying to understand memory safety in Swift, I am confused between two scenarios.

As I marked MARK1: -Conflicting Access to self in Methods section makes sense. Two write access makes overlap each other. Already compiler gives me error before run :

Overlapping accesses to 'robGlobal', but modification requires exclusive access; consider copying to a local variable


QuestionOne: Okay but what about MARK2 section? Swift compiler makes some optimization to solve this write access here for struct?

Apple explain indicate for solution as below but it is not clear for me.

The compiler can prove that memory safety is preserved because the two stored properties don’t interact in any way.

QuestionTwo: By making global in here run time problem solves. But I wonder how ? Also why it doesn't work for MARK1 section and why ?

PS: Apple Document is here

like image 323
DrainOpener Avatar asked Nov 07 '22 02:11

DrainOpener


1 Answers

With robGlobal.someFunction(), you're calling shareHealth on a separate local instance of robLocal. Obviously there are no memory safety issues in that case.

But with robGlobal.anotherFunction(), you're then calling an instance method of the robGlobal, but then passing a yet another reference to the same robGlobal to shareHealth, hence the memory safety violation.

But that is good. This is functionally equivalent to one of the other attempts that the memory safety checks protected you against:

robGlobal.shareHealth(with: &robGlobal) 

The whole idea is to share health with another player. Setting aside the memory safety checks, what does it even mean to share one player’s health with itself? E.g. your current health is 11. You then “share” it with yourself. So, is your health now 5? Or 6? And does it make sense that your health was 11 and now that after you've shared it with yourself that it is now a smaller value? The whole idea of sharing with yourself doesn't make sense. The “memory safety” checks are protecting you from this sort of misuse.


Personally, I would

  • make balance a static method because all it is doing it “balancing” two integer values, not doing anything with the current instance;

  • this should also probably be private as there is no need to expose this function; and

  • since you seem to want to “share health” to balance heath between two players as well as balance the health and energy for a given player, I would write those two methods.

Thus, we end up with something like:

struct Player {
    var name: String
    var health: Int
    var energy: Int

    static let maxHealth = 10
}

// MARK: - Interface

extension Player {
    mutating func restoreHealth() {
        health = Player.maxHealth
    }

    mutating func shareHealth(with teammate: inout Player) {
        Self.balance(&teammate.health, &health)
    }

    mutating func balanceHealthAndEnergy() {
        Self.balance(&health, &energy)
    }
}

// MARK: - Private utility methods

private extension Player {
    static func balance(_ x: inout Int, _ y: inout Int) {
        let sum = x + y
        x = sum / 2
        y = sum - x
    }
}

Then you can do something like:

func someFunction() {
    var rob = Player(name: "Rob", health: 20, energy: 10)
    rob.balanceHealthAndEnergy()
}

func anotherFunction() {
    var rob = Player(name: "Rob", health: 10, energy: 10)
    var matt = Player(name: "Matt", health: 5, energy: 10)

    rob.shareHealth(with: &matt)
}

This new interface (the shareHealth and balanceHeathAndEnergy) streamline the interface. You can call them on local vars or properties (or globals, if you really want to; but having globals is generally a bad idea). But you would never shareHealth with yourself, and now that balance is private, it minimizes the sort of misuse outlined in your question.

FWIW, I would not make these test methods instance methods of Player as they are not doing anything with the current instance. You could make them static methods. Or you could just make them methods in your model (or tests or whatever). But they do not make sense as Player instance methods.

like image 92
Rob Avatar answered Nov 27 '22 02:11

Rob