[swift-evolution] References in Value Types (Deep-Copy-OnWrite)
Andrew Bennett
cacoyi at gmail.com
Thu Dec 17 07:51:26 CST 2015
This isn't a proposal, more something to get a discussion started.
TL;DR: You can make a struct look like a class, a class look like a struct,
a mutable type appear immutable.
I think that Swift is doing these things as it should. However I think that
there is room for mistakes, and it would be nice to be aware of those
mistakes and guard against them.
Consider the following code:
private class MyClass {
var value: Int
init(value: Int) { self.value = value }
}
struct MyValue {
private let reference: MyClass
init(value: Int) {
self.reference = MyClass(value: value)
}
var value: Int {
get { return self.reference.value }
set { self.reference.value = newValue }
}
}
var myVariable = MyValue(value: 123)
print(myVariable.value) // "123"
let myOtherVariable = myVariable
myVariable.value = 456
print(myOtherVariable.value) // "456"!
Are all of our assumptions correct?
* Is myVariable's a value type? probably
* Is myVariable copy-on-write? perhaps
* Should we be able to write to myVariable.value despite reference being
let? probably
* Is this code unsafe, or misleading? yes. Can we prevent that? maybe
Some other code, this is not perfect but it mostly makes MyValue
copy-on-write:
protocol DeepCopier {
typealias ObjectType: AnyObject
static func deepCopy(that: ObjectType) -> ObjectType
}
struct UniqueReference<Copier: DeepCopier> {
private var _reference: Copier.ObjectType
init(var reference: Copier.ObjectType) {
if isUniquelyReferencedNonObjC(&reference) {
_reference = reference
}
else {
_reference = Copier.deepCopy(reference)
}
}
var reference: Copier.ObjectType { return self._reference }
mutating func update(block: Copier.ObjectType -> Void) {
if !isUniquelyReferencedNonObjC(&self._reference) {
self._reference = Copier.deepCopy(self._reference)
}
block(self._reference)
}
}
private class MyClass: NonObjectiveCBase {
var value: Int
init(value: Int) { self.value = value }
func deepCopy() -> MyClass {
return MyClass(value: self.value)
}
private struct Copier: DeepCopier {
static func deepCopy(that: MyClass) -> MyClass {
return MyClass(value: that.value)
}
}
}
struct MyValue {
private var myReference: UniqueReference<MyClass.Copier>
init(value: Int) {
self.myReference = UniqueReference(reference: MyClass(value: value))
}
var value: Int {
get { return self.myReference.reference.value }
set { self.myReference.update { $0.value = newValue } }
}
}
var myVariable = MyValue(value: 123)
print(myVariable.value) // "123"
let myOtherVariable = myVariable
myVariable.value = 456
print(myOtherVariable.value) // "123"
print(myVariable.value) // "456"
Even more code, I don't trust anything any more:
struct MyValue {
init(value: Int) { }
var value: Int {
get { return Int(arc4random()) }
set { }
}
}
The public interface for MyValue in all these cases looks totally innocent:
struct MyValue {
init(value: Int)
var value: Int
}
Protocols are the same, they provide no guarantees about immutability, or
deep-copy-on-write:
protocol SomeValue {
init(value: Int)
var value: Int { get }
}
The results may be a bit surprising.
* Perhaps we could introduce a "const" keyword like in other languages, so
the class cannot be mutated (how do we even scope this?).
* Perhaps we could prevent structures from holding references (no classes,
non-pure blocks, no arc4random, no AnySequence, ...).
* Perhaps we can add a @copy_on_write keyword on structures that enforces
certain guarantees? (how?)
* Perhaps add language support for DeepCopier (with a better name,
probably a keyword rather than a protocol), that allows anything to be
copied like a structure (my preference).
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20151218/ff18025c/attachment.html>
More information about the swift-evolution
mailing list