2

Whenever I try to persist an object to the DB using insert or upsert, Xcode complains Cannot use mutating member on immutable value.

I understand why Xcode is complaining, but do I really have to make a copy of the function parameter just to save? It seems so hacky. Perhaps there's a better way?

static func saveWorkoutTemplate(wt: WorkoutTemplate) {
    guard let dbQueue = openDB() else { return }
    
    var w = wt //lame hack
    
    try! dbQueue.write { db in
        try! w.upsert(db) // this works
        //try! wt.upsert(db) // error: Cannot use mutating member on immutable value: 'wt' is a 'let' constant            
    }
}
Legion
  • 3,922
  • 8
  • 51
  • 95
  • A function parameter is always immutable unless it is declared as `inout`. – Joakim Danielson Apr 20 '23 at 19:01
  • 1
    @JoakimDanielson I'm aware. But I don't need whatever changes `upsert` is making to persist outside of the function. Also, WorkoutTemplate is a struct, so it's a copy anyway. Philosophically I don't understand what purpose making it immutable serves. Regardless, are my only options making a copy of a copy or inout? – Legion Apr 20 '23 at 19:10
  • 1
    You may also reuse the name on the parameter for the local variable: `var wt = wt`. This allows you to not "burn" a variable identifier. – Gwendal Roué Apr 21 '23 at 06:53
  • @GwendalRoué Thanks, that's a bit better. I think I might try moving the save function off of the persistance layer and directly onto the objects. They already have upsert/insert from the protocols, might as well have save as well. Eliminates having to pass them as parameters then and the whole mutability of parameters issue. – Legion Apr 21 '23 at 10:45
  • 1
    I advise not to perform transactions (`dbQueue.write`) from models (`WorkoutTemplate`). Models are the correct place to manage [transaction boundaries](https://swiftpackageindex.com/groue/grdb.swift/documentation/grdb/transactions). I mean, you can. But when your app eventually needs you to think for good about data consistency, because well data is important, you'll have a bad refactoring time. – Gwendal Roué Apr 21 '23 at 11:21
  • 1
    I mean (about moving the save function directly onto the objects) : if it was a good idea, maybe the lib would have shipped those methods in the first place ;-) – Gwendal Roué Apr 21 '23 at 11:48
  • @GwendalRoué You may be right. Unfortunately I don't really understand the argument. Sometimes I can't see the problem with a design even when warned about it, I have to learn via experience. Nothing internalizes a lesson better than spending a few hours refactoring your design when the problem becomes obvious. Thanks again for the advice. BTW, are you the guy who made GRDB? – Legion Apr 21 '23 at 13:54
  • 1
    Oh, sure, do enjoy your developer life, and perform as many experiments as possible! :-) And yes, I'm the author of GRDB :-) – Gwendal Roué Apr 21 '23 at 14:57
  • @GwendalRoué Is this the best place to ask GRDB questions? I ask because I may have some questions about using the library that aren't a good fit for Stackoverflow. – Legion Apr 21 '23 at 20:35
  • Check the doc, existing issues, and discussions: maybe your questions are already answered – Gwendal Roué Apr 22 '23 at 09:05

1 Answers1

1

Yes, the thing you've marked "lame hack" is correct. It's not a hack. It's being explicit about what you want to happen. You're making a local copy that does not modify the thing that was passed in.

But I don't need whatever changes upsert is making to persist outside of the function.

And that's exactly why you need to make a copy. Most large types employ copy-on-write to ensure that the copy is as cheap as possible. In this case, the optimizer will likely eliminate the copy entirely. But you will still be clear about what's going on.

Rob Napier
  • 286,113
  • 34
  • 456
  • 610
  • I guess it's a bit verbose to my tastes. But as I alluded to Gwendal Roue above, if I haven't been bitten by a problem, then these kinds of rules can seem overly restrictive or academic if I understand them at all. I can't say I've ever encountered "unexpected mutations" in my variables. Who knows, perhaps the kinds of programs I write aren't prone to that issue? Mostly consisting of forms to capture data that's persisted to a DB with little to no concurrency. – Legion Apr 21 '23 at 20:52
  • I often find that if I need this, it's a signal that I may be using the API incorrectly (as I think is the case here and Gwendal seems to be suggesting). Sometimes it suggests the API itself is badly designed, but usually I've found it's pointing to my misunderstanding. Swift's choices are definitely anchored in the belief that "unexpected mutations" are a very common problem. I've definitely encountered it many times in many languages. Same with Swift memory management, which assumes that "use after free" and the like are common mistakes. But I'm sure there are folks who never do that either. – Rob Napier Apr 21 '23 at 21:38