I think value semantics are best summarized by an out-of-place transform function:
func transformedCopy<T>(
_ x: T, transform: (inout T)->()
) -> T {
var result = x
transform(&result)
return result
}
That implementation is correct if and only if T
has value semantics.
• • •
More concretely, a type T
has value semantics if and only if, for all possible instances of T
and all possible transform
operations (edit: which do not mutate anything other than the inout
argument), the use of transformedCopy
does not modify its argument.
Thus, in the following snippet:
var x: T = ...
// 1
let y = transformedCopy(x){ ... }
// 2
If at lines 1 and 2 we print out a complete description of all salient features of x
, and they are not identical to each other, then T
does not have value semantics.
In particular, if there exists any possible x
and any possible transform
(edit: same caveat as above) for which they are different, then the type does not have value semantics.
• • •
Edit:
Of course, if transform
captures and mutates the variable which is being passed in as x
then we get the same situation as in MutableReference
above, so we ought to rule that out.
But even if it doesn’t, we can still get situations like this:
var a: Int = 1
var b: Int {
get { a }
set { a = newValue }
}
print(b) // 1
let c = transformedCopy(b){
a += 1
$0 += a
}
print(b) // 2
So indeed the definition is quite slippery.
I do, however, still maintain that the implementation of transformedCopy
is correct if and only if T
has value semantics. Indeed, functions like that are exactly and precisely where I would want to use a T: ValueSemantics
constraint.