I built a protocol with associated types, now I wish to make a type eraser for it, but since type erasers always use reference types somehow I can't find any strategy to have a type eraser that would let assign some variable x to y and mutating y without mutating x :
var x = AnyFoo(something) // Where something is a struct conforming to protocol Foo
var y = x
x.bar = 10
y.bar = 15 // Without modifying the value for x.foo
I tried different approaches, one inspired from AnySequence and the other inspired from this tutorial but no matter how I try to implement it I always end up being blocked when trying to avoid x and y to share a reference to the same underlying objects.
/// Protocol to allow cloning.
protocol Clonable {
var clone: Self { get }
}
/// The protocol which implementations of which will have their type erased to `AnyBox`.
protocol Box {
associatedtype BoxType: Clonable
var boxed: BoxType { get set }
}
/// Type erased instance of a `Box`.
struct AnyBox<T>: Box where T: Clonable {
typealias BoxType = T
private var boxedClone: T
init<B>(_ b: B) where B: Box, B.BoxType == T {
boxedClone = b.boxed.clone // Clone to allow mutation.
}
var boxed: T {
get { return boxedClone }
set { boxedClone = newValue }
}
}
/// Make Ints clonable so that they can be boxed.
extension Int: Clonable {
var clone: Int { return self }
}
/// A box to type erase.
struct IntBox: Box {
typealias BoxType = Int
var boxed: Int
}
let i = IntBox(boxed: 0)
var x = AnyBox(i) // Type erase.
let y = x
x.boxed = 1
x.boxed // 1
y.boxed // 0
Thank you! Would this be able to erase anything else ? If I'm right the type T of boxedClone isn't guaranteed to conform to Box (actually the initialiser enforces it but compiler won't know it outside of the init body).
I think in hindsight my solution is not what you want. Perhaps this is nearer to what you need:
/// The protocol which implementations of which will have their type erased to `AnyBox`.
protocol Box {
associatedtype BoxedType
var boxed: BoxedType { get set }
}
/// A box of a value type (struct, enum) to type erase.
struct ValueBox<T>: Box {
typealias BoxType = T
var boxed: T // Int is used as an example, but could be any struct or enum.
}
/// Example mutable reference type (class).
class Reference<T> {
var value: T
init(_ initial: T) {
value = initial
}
}
/// A box of a reference type (class) to type erase that uses copy-on-write to maintain sperate copies (like Array does).
struct ReferenceBox<T>: Box {
typealias BoxType = T // Note Int not Reference, since this box takes care of the copying.
var boxed: Reference<T> // Reference is used as an example, but clould be any class.
init(boxed: Reference<T>) { self.boxed = boxed }
}
/// Type erased version of a *known* set of `Box` types.
enum AnyKnownBox<T>: Box {
case valueBox(ValueBox<T>)
case referenceBox(ReferenceBox<T>)
typealias BoxedType = T
var boxed: T {
get {
switch self {
case .valueBox(let b):
return b.boxed
case .referenceBox(let b):
return b.boxed.value
}
}
set {
switch self {
case .valueBox(_):
self = .valueBox(ValueBox(boxed: newValue))
case .referenceBox(_):
self = .referenceBox(ReferenceBox(boxed: Reference(newValue)))
}
}
}
}
var v = ValueBox(boxed: 0)
var x = AnyKnownBox.valueBox(v) // Type erase away ValueBox.
let y = x
v.boxed = 2
x.boxed = 1
v.boxed // 2
x.boxed // 1
y.boxed // 0
var r = Reference(0)
var rb = ReferenceBox(boxed: r)
var rx = AnyKnownBox.referenceBox(rb) // Type erase away ReferenceBox.
let ry = rx
r.value = 3
rb.boxed = Reference(2)
rx.boxed = 1
r.value // 3
rb.boxed.value // 2
rx.boxed // 1
ry.boxed // 3 - ry still ponts to r wheras both rb and rx have new references created when they were mutated.
Another approach is to just represent your types in an enum directly rather than have a protocol and type erase the protocol.