There is a number of use cases where a type is necessary to encapsulate access to a specific part of a type without creating complex object graphs. The most representative example is the String
type with its var unicodeScalars: UnicodeScalarView { get }
, var utf16: UTF16View { get }
, and var utf8: UTF8View { get }
properties. Currently, these work because they don't allow mutating the string, so they can simply hold a shallow copy of the string internally and provide read-only access to the respective functionality. Even if these views are saved and the original string isn't, they still work because they hold a shallow copy of the string. However, trying to implement the same pattern with mutating methods becomes problematic.
Problem
struct Vector {
var coordinates: [Double]
var rgba: RgbaView {
mutating get { Rgba(vector: &self) }
}
struct RgbaView {
let vector: UnsafeMutablePointer<Vector>
var r: Double {
get { vector.pointee.coordinates[0] }
set { vector.pointee.coordinates[0] = newValue }
}
var g: Double {
get { vector.pointee.coordinates[1] }
set { vector.pointee.coordinates[1] = newValue }
}
var b: Double {
get { vector.pointee.coordinates[2] }
set { vector.pointee.coordinates[2] = newValue }
}
var a: Double {
get { vector.pointee.coordinates[3] }
set { vector.pointee.coordinates[3] = newValue }
}
}
}
Here are the problems with this implementation:
- The struct
Vector.RgbaView
may be stored in a property, which can outlive the originalVector
value, causing a dangling pointer (crash). - The accessor
Vector.rgba
has to have a mutating getter, meaning that the nonmutating members ofVector.RgbaView
cannot be accessed through a read-only instance ofVector
. - Implementing
Vector.RgbaView
is clunky and dangerous, because it involves working with unsafe pointers.
Solution
My idea is to add a new attribute (e.g. @temporary
) for struct
and enum
declarations that would impose the following limitations and liberties on the declared type:
- Values of the
@temporary
type may not be assigned to a property or passed to a function call, but only returned. - The
@temporary
type may provide custominfix operator =
implementations. -
inout
parameters in initializers may be initialized with immutable values. The@temporary
type is mutable if and only if all of itsinout
parameters are initialized with a mutable value. - Properties of
@temporary
types have theirself
value inherit the mutability of the enclosing object.
Example
struct Vector {
var coordinates: [Double]
var rgba: RgbaView { .init(vector: &self) }
@temporary struct RgbaView {
var vector: inout Vector
var r: Double {
get { vector.coordinates[0] }
set { vector.coordinates[0] = newValue }
}
var g: Double {
get { vector.coordinates[1] }
set { vector.coordinates[1] = newValue }
}
var b: Double {
get { vector.coordinates[2] }
set { vector.coordinates[2] = newValue }
}
var a: Double {
get { vector.coordinates[3] }
set { vector.coordinates[3] = newValue }
}
}
}
I'd like to hear some opinions about this idea. Has this been proposed before? What fundamental problems this idea has?