1

Consider the following code:

struct Card {
    var name0: String
    var name1: String
}

var cards = [Card]()
cards.append(Card(name0: "hello", name1: "world"))

// Need to perform array index access, 
// every time I want to mutate a struct property :(
cards[0].name0 = "good"
cards[0].name1 = "bye"
// ...
// ...

// "good bye"
print(cards[0].name0 + " " + cards[0].name1)

Instead of having to perform multiple array index accessing every time I want to mutate a property in struct, is there a technique to avoid such repeating array index accessing operation?

// Ok. This is an invalid Swift statement.
var referenceToCardStruct = &(cards[0])

referenceToCardStruct.name0 = "good"
referenceToCardStruct.name1 = "bye"
// ...
// ...
Cheok Yan Cheng
  • 47,586
  • 132
  • 466
  • 875

5 Answers5

3
struct Card {
    var name0: String
    var name1: String
}

var cards = [Card]()

// every time I want to mutate a struct property :(
cards[0].name0 = "good"
cards[0].name1 = "bye"

Instead of having to perform multiple array index accessing every time I want to mutate a property in struct, is there a technique to avoid such repeating array index accessing operation?

No. When you have an array of struct, then in order to make a change to a struct within the array, you must refer to that struct by index.

If you don't want to see the repeated use of the index, you can hide it in a function using inout:

func mutate(card: inout Card) {
    card.name0 = "good"
    card.name1 = "bye"
}
for index in cards.indices {
    mutate(card:&cards[index])
}

Some day, Swift may include for inout which will allow you to cycle through an array of struct and mutate each struct instance directly. But that day is not yet here.

In answer to the implied question whether it is worth switching to a class just to avoid this repeated use of the index, my answer would be No. There is a good reason for using structs — they are much easier to reason about than classes, and are one of Swift's best features — and I would keep using them if that reason matters to you.

Community
  • 1
  • 1
matt
  • 515,959
  • 87
  • 875
  • 1,141
  • Just to give an example, my FreeCell app uses structs at every level of the game layout: Card, Deck, Foundation, Column, FreeCell, and Layout are all structs. And I'm not sorry! See my parallel question https://stackoverflow.com/questions/54014345/how-do-people-deal-with-iterating-a-swift-struct-value-type-property, written at the very start of the development process for that app. – matt Jul 06 '19 at 18:19
3

There are a lot of good answers here, and you should not think of value types as "a limitation." The behavior of value types is very intentional and is an important feature. Generally, I'd recommend inout for this problem, like matt suggests.

But it is also certainly possible to get the syntax you're describing. You just need a computed variable (which can be a local variable).

let index = 0 // Just to show it can be externally configurable

var referenceToCardStruct: Card {
    get { cards[index] }
    set { cards[index] = newValue }
}
referenceToCardStruct.name0 = "good"
referenceToCardStruct.name1 = "bye"

print(cards[0].name0 + " " + cards[0].name1)
Rob Napier
  • 286,113
  • 34
  • 456
  • 610
2

struct is a value type you can't get a reference to it's object with assignment , you should go that way , use a mutating method like https://stackoverflow.com/a/52497495/5820010 or use a class instead

Shehata Gamal
  • 98,760
  • 8
  • 65
  • 87
  • Thanks. Is it worth to switch over to `class` for this type of limitation? I can appreciate the value offered by struct according to https://developer.apple.com/videos/play/wwdc2015/414/ But, if Swift allows more flexibility when dealing with struct (Like creating a local reference for my case), it will be even better. – Cheok Yan Cheng Jul 06 '19 at 17:42
  • i prefer to declare a `class` when knowing that i'll mutate some value – Shehata Gamal Jul 06 '19 at 18:09
2

If you don't want to repeat the index, then create a variable from the value you want.

var cards = [Card]()
cards.append(Card(name0: "hello", name1: "world"))

var card = cards[0]
card.name0 = "good"
card.name1 = "bye"
// ...
// ...
cards[0] = card // update the array with the updated card

// "good bye"
print(card.name0 + " " + card.name1)
rmaddy
  • 314,917
  • 42
  • 532
  • 579
  • isn't that operation has a bad performance issue of copying given struct may contain many properties ? – Shehata Gamal Jul 06 '19 at 17:12
  • 3
    @Sh_Khan It's only a single copy and the OP's goal is not performance (which is trivial in this case), it's eliminating the index. – rmaddy Jul 06 '19 at 17:15
1

I think the mutating method is the way to go, as Sh_Khan points out.

In your case, I would do something like:

  1> struct Card { 
  2.     var name0: String 
  3.     var name1: String 
  4. } 
  5.  
  6. var cards = [Card]()
  7. cards.append(Card(name0: "hello", name1: "world")) 
cards: [Card] = 1 value {
  [0] = {
    name0 = "hello"
    name1 = "world"
  }
}
  8> extension Card { 
  9.     mutating func setNames(name0: String, name1: String) { 
 10.         self.name0 = name0
 11.         self.name1 = name1 
 12.     } 
 13. } 
 14> cards[0].setNames(name0: "x", name1: "y") 
 15> cards
$R0: [Card] = 1 value {
  [0] = {
    name0 = "x"
    name1 = "y"
  }
}

Another approach is a wholesale update of cards[0].

Kind of makes you wish for record updating syntax (a la Haskell or Elm) or dictionary merging-type functionality. But look on the bright side. Maybe Swift's lack of making this easy is testament to the fact that it has static-typing-and-value-semantics-while-allowing-mutation and that combination of features, I think, makes the mutating func or full array element update all but required. I'm not sure Swift has a syntactic feature for updating multiple properties in one shot (without writing your own function or method).

Ray Toal
  • 86,166
  • 18
  • 182
  • 232