Obtuse is fundamentally unsafe
Always try to make what users want to write be the best way to write something.
The challenge with making "safe" APIs that are also harder to use is that while a little push can work, if they're too much harder to use it's counter-productive: instead of hinting that the problem domain is more complex than beginners assumed, and provoking them into thinking about it more deeply, it can end up just pushing them to StackOverflow or ChatGPT where they'll just copy-paste something they don't understand either, which will probably be wrong in even more subtle and nefarious ways.
This is essentially a leaky abstraction problem.
If a solution can be found that's safe but also intuitive and sufficiently simple, that's much more likely to succeed.
With that in mind, I'm thinking Swift (the language and/or compiler) needs to be smarter, not the programmer. e.g. what if:
foo.value += 1
…were automatically translated to:
foo.withLock {
$0 += 1
}
By which I mean more like @autoclosure
parameters, not merely a _modify
property accessor. It should permit arbitrarily many references to and uses of the value (subject to the normal rules that preclude ambiguous 'simultaneous' mutation), e.g.:
foo.value = abs(foo.value) + calculateDelta(from: foo.value)
I posit that folks who find this too magical may be biased by convention; that new programmers will learn this just fine and not find it at all "magical" (in the bad sense) that this works as they intended. If you've worked with beginner programmers you'll know that they are in fact already surprised that it could ever not work the way they intended.
There's plenty of leeway there for the compiler to restrict what can be done inside that [figurative] implicit closure, if necessary to prevent abuse or likely mistakes. e.g. it could prevent use of any other locks (in order to avoid acquisition ordering problems - or better yet, simply ensure that lock acquisition order is globally consistent).
Or it could prevent blocking or suspension points, requiring more explicit syntax for their use in order to better convey the author's intention through the code. e.g.:
foo.value = await next(after: foo.value)
// ❌ Suspension points are not allowed while directly modifying
// a `Lock`'s value, as it's not clear that the author meant to
// suspend while holding the lock.
//
// foo.value = await next(after: foo.value)
// ^^^^^
//
// FixIt 1: Perform the suspending operations separately
// (if correct to do so)
//
// let next = await next(after: foo.value)
// foo.value = next
//
// FixIt 2: Use an explicit critical section
//
// await foo.withLock {
// $0 = await next(after: $0)
// }
I think it's reasonably intuitive - and at the very least reasonable to expect beginners to learn - that individual statements are generally "atomic" but multiple statements are not, unless you explicitly make them so.
A [well-written] context-specific compiler error (or warning) is going to be much more successful than just missing APIs (or API documentation, or long-forgotten Swift Forums threads, or buried in an otherwise well-meaning Evolution Proposal, etc).