Swift and mutating struct

后端 未结 8 1689
鱼传尺愫
鱼传尺愫 2020-12-04 06:54

There is something that I don\'t entirely understand when it comes to mutating value types in Swift.

As the \"The Swift Programming Language\" iBook states:

相关标签:
8条回答
  • 2020-12-04 07:17

    A structure is an aggregation of fields; if a particular structure instance is mutable, its fields will be mutable; if an instance is immutable, its fields will be immutable. A structure type must thus be prepared for the possibility that the fields of any particular instance may be mutable or immutable.

    In order for a structure method to mutate the fields of the underlying struct, those fields have to be mutable. If a method that mutates fields of the underlying struct is invoked upon an immutable structure, it would try to mutate immutable fields. Since nothing good could come of that, such invocation needs to be forbidden.

    To achieve that, Swift divides structure methods into two categories: those that modify the underlying structure, and thus may only be invoked on mutable structure instances, and those that do not modify the underlying structure and should thus be invokable on both mutable and immutable instances. The latter usage is probably the more frequent, and is thus the default.

    By comparison, .NET presently (still!) offers no means of distinguishing structure methods that modify the structure from those that don't. Instead, invoking a structure method on an immutable structure instance will cause the compiler to make a mutable copy of the structure instance, let the method do whatever it wants with it, and discard the copy when the method is done. This has the effect of forcing the compiler to waste time copying the structure whether or not the method modifies it, even though adding the copy operation will almost never turn what would be semantically-incorrect code into semantically-correct code; it will merely cause code that is semantically wrong in one way (modifying an "immutable" value) to be wrong in a different way (allowing code to think it's modifying a structure, but discarding the attempted changes). Allowing struct methods to indicate whether they will modify the underlying structure can eliminate the need for a useless copy operation, and also ensures that attempted erroneous usage will get flagged.

    0 讨论(0)
  • 2020-12-04 07:22

    One more variant

    struct MyStruct {
        var myVar = "myVar"
        let myLet = "myLet"
    }
    
    func testMutateString() {
        //given
        let myStruct = MyStruct()
        
        //Change var
        //when
        var myStructCopy = myStruct
        myStructCopy.myVar = "myVar changed 1"
        
        //then
        XCTAssert(myStructCopy.myVar == "myVar changed 1")
        
        //Change let
        //when
        withUnsafeMutableBytes(of: &myStructCopy) { bytes in
            let offset = MemoryLayout.offset(of: \MyStruct.myLet)!
            let rawPointerToValue = bytes.baseAddress! + offset
            let pointerToValue = rawPointerToValue.assumingMemoryBound(to: String.self)
            pointerToValue.pointee = "myLet changed"
        }
        
        //then
        XCTAssert(myStructCopy.myLet == "myLet changed")
    }
    

    [Swift types]

    0 讨论(0)
  • 2020-12-04 07:27

    Swift structs can be instantiated as either constants (via let) or variables (via var)

    Consider Swift's Array struct (yes it's a struct).

    var petNames: [String] = ["Ruff", "Garfield", "Nemo"]
    petNames.append("Harvey") // ["Ruff", "Garfield", "Nemo", "Harvey"]
    
    let planetNames: [String] = ["Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune"]
    planetNames.append("Pluto") //Error, sorry Pluto. No can do
    

    Why didn't the append work with the planet names? Because append is marked with the mutating keyword. And since planetNames was declared using let, all methods thus marked are off limits.

    In your example the compiler can tell you're modifying the struct by assigning to one or more of its properties outside of an init. If you change your code a bit you'll see that x and y aren't always accessible outside the struct. Notice the let on the first line.

    let p = Point(x: 1, y: 2)
    p.x = 3 //error
    p.moveToX(5, andY: 5) //error
    
    0 讨论(0)
  • 2020-12-04 07:29

    SWIFT : Use of mutating function in Structs

    Swift programmers developed Structs in such a way that properties of it can not be modified within Struct methods. For example, Check the code given below

    struct City
    {
      var population : Int 
      func changePopulation(newpopulation : Int)
      {
          population = newpopulation //error: cannot modify property "popultion"
      }
    }
      var mycity = City(population : 1000)
      mycity.changePopulation(newpopulation : 2000)
    

    On executing the above code we get an error because we are trying to assign a new value to the property population of the Struct City. By default Structs properties can’t be mutated inside its own methods. This is how Apple Developers have built it, so that the Structs will have a static nature by default.

    How do we solve it? What’s the alternative?

    Mutating Keyword :

    Declaring function as mutating inside Struct allows us to alter properties in Structs. Line No : 5, of the above code changes to like this,

    mutating changePopulation(newpopulation : Int)
    

    Now we can assign the value of the newpopulation to property population to within the scope of the method.

    Note :

    let mycity = City(1000)     
    mycity.changePopulation(newpopulation : 2000)   //error: cannot modify property "popultion"
    

    If we use let instead of var for Struct objects, then we can’t mutate the value of any properties, Also this is the reason why we get an error when we try to invoke mutating function using let instance. So it is better to use var whenever you are changing value of a property.

    Would love to hear your comments and thoughts…..

    0 讨论(0)
  • 2020-12-04 07:32

    Caution: layman's terms ahead.

    This explanation isn't rigorously correct at the most nitty-gritty code level. However it has been reviewed by a guy who actually works on Swift and he said it's good enough as a basic explanation.

    So I want to try to simply and directly answer the question of "why".

    To be precise: why do we have to mark struct functions as mutating when we can change struct parameters without any modifying keywords?

    So, big picture, it has a lot to do with the philosophy that keeps Swift swift.

    You could kind of think of it like the problem of managing actual physical addresses. When you change your address, if there are a lot of people who have your current one, you have to notify all of them that you've moved. But if no one has your current address, you can just move wherever you want, and no one needs to know.

    In this situation, Swift is kind of like the post office. If lots of people with lots of contacts move around a lot, it has a really high overhead. It has to pay a big staff of people to handle all those notifications, and the process takes up a lot of time and effort. That's why Swift's ideal state is for everyone in its town to have as few contacts as possible. Then it doesn't need a big staff for handling address changes, and it can do everything else faster and better.

    This is also why Swift-folks are all raving on about value types vs. reference types. By nature, reference types rack up "contacts" all over the place, and value types usually don't need more than a couple. Value types are "Swift"-er.

    So back to small picture: structs. Structs are a big deal in Swift because they can do most of the things objects can do, but they're value types.

    Let's continue the physical address analogy by imagining a misterStruct that lives in someObjectVille. The analogy gets a little wonked up here, but I think it still is helpful.

    So to model changing a variable on a struct, let's say misterStruct has green hair, and gets an order to switch to blue hair. The analogy gets wonked, like I said, but sort of what happens is that instead of changing misterStruct's hair, the old person moves out and a new person with blue hair moves in, and that new person begins calling themselves misterStruct. No one needs to get a change of address notification, but if anyone looks at that address, they'll see a guy with blue hair.

    Now let's model what happens when you call a function on a struct. In this case, it's like misterStruct gets an order such as changeYourHairBlue(). So the post office delivers the instruction to misterStruct "go change your hair to blue and tell me when you're done."

    If he's following the same routine as before, if he's doing what he did when the variable was changed directly, what misterStruct will do is move out of his own house and call in a new person with blue hair. But that's the problem.

    The order was "go change your hair to blue and tell me when you're done," but it's the green guy who got that order. After the blue guy moves in, a "job complete" notification still has to be sent back. But the blue guy knows nothing about it.

    [To really wonk up this analogy something awful, what technically happened to green-haired guy was that after he moved out, he immediately committed suicide. So he can't notify anyone that the task is complete either!]

    To avoid this problem, in cases like this only, Swift has to go in directly to the house at that address and actually change the current inhabitant's hair. That is a completely different process than just sending in a new guy.

    And that's why Swift wants us to use the mutating keyword!

    The end result looks the same to anything that has to refer to the struct: the inhabitant of the house now has blue hair. But the processes for achieving it are actually completely different. It looks like it's doing the same thing, but it's doing a very different thing. It's doing a thing that Swift structs in general never do.

    So to give the poor compiler a little help, and not make it have to figure out whether a function mutates the struct or not, on its own, for every single struct function ever, we are asked to have pity and use the mutating keyword.

    In essence, to help Swift stay swift, we all must do our part. :)

    0 讨论(0)
  • 2020-12-04 07:32

    Consider an analogy with C++. A struct method in Swift being mutating/not-mutating is analogous to a method in C++ being non-const/const. A method marked const in C++ similarly cannot mutate the struct.

    You can change a var from outside a struct, but you cannot change it from its own methods.

    In C++, you can also "change a var from outside the struct" -- but only if you have a non-const struct variable. If you have a const struct variable, you cannot assign to a var, and you also cannot call a non-const method. Similarly, in Swift, you can change a property of a struct only if the struct variable is not a constant. If you have a struct constant, you cannot assign to a property, and you also cannot call a mutating method.

    0 讨论(0)
提交回复
热议问题