[swift-evolution] access control proposal
Brent Royal-Gordon
brent at architechies.com
Sat Dec 12 07:39:22 CST 2015
> I would like to hear from the folks who don’t see the need of another access modifier, what solution to this problem they plan to maintain in their own projects. Because this design pattern (queue-controlled ivar) is not uncommon, and the lack of a fourth access control keyword has produced real race conditions that I have spent real time debugging, that is completely avoidable via a keyword like classified/local/secret.
My solution is to be careful when I’m writing code.
I mean, look, you’re right. In your case, three access modifiers isn’t enough to prevent this mistake; you need four. But, given four, you could provide an example that needs five. Given five, you could come up with one for six. Given N access modifiers, you can write a case where you need N + 1.
There will never be enough. At some point, you just have to draw the line and say “this is enough; beyond here, you’re just going to have to take responsibility for your code.” Swift draws the line at a single file, a cohesive unit of code that’s all visible at once (at least with scrolling), but which still can contain things with no direct relationship. Small enough that you ought to be able to keep the outline of it in your head, but large enough that there’s still plenty of flexibility.
This is absolutely a judgement call that reasonable people can disagree on. But personally, my judgement is that `private` is private enough.
ADDENDUM:
Actually, for this case, there *is* a way to achieve better safety without additional access modifiers. Create a new file and call it Synchronized.swift:
// This could be a struct, but that leads to some odd copying semantics.
final class Synchronized<Value> {
private var value: Value
private let queue = dispatch_queue_create(“specialQueue", DISPATCH_QUEUE_SERIAL)
init(value: Value) {
self.value = value
}
mutating func withValue<R>(closure: (inout value: T) -> R) -> R {
var returnValue: R?
dispatch_sync(queue) {
returnValue = closure(value)
}
return returnValue!
}
}
Now in your original file, you write:
final private class Foo { //this class is an implementation detail of Bar and is declared ‘private'
private var _children = Synchronized(value: [] as [Next]) //access only from special queue, as required by $EXTERNAL_REQUIREMENT
func appendChild(n: Next) {
_children.withValue { (inout children: [Next]) in
children.append(n)
}
}
}
final class Bar {
let f = Foo()
init() {
f.appendChild(…)
f._children[0].baz() // error—_children can’t do this directly, you need to call .withValue()
f._children.value[0].baz() // error—value is private and inaccessible from here
}
}
Synchronized can, of course, be modified to support all sorts of other cases: shared queues, parallel read-only access, etc. The general concept of using *another* file’s `private` modifier to protect *this* file’s data can be used in many cases, and I think it’s a pretty good solution to problems like these.
--
Brent Royal-Gordon
Architechies
More information about the swift-evolution
mailing list