How does this interact with didSet/willSet? Are properties with those listeners not considered stored properties for the sake of reinitialization, or are they no longer guaranteed to have an oldValue/newValue respectively? Or do you have some other solution in mind?
Ah, I think I get it. So partial consumption is always treated differently from full consumption, even if all stored properties happen to be consumed. So any use of partial reinitialization preserves the fact that a noncopyable value must always ultimately come from an initializer. And code that uses partial reinitialization will never depend on exhaustive knowledge of stored properties (unlike initializers), so stored properties can always be added to a noncopyable struct without breaking code that uses partial reinitialization.
I know this isn't directed at me, so apologies if I'm talking out of my lane.
In order for didSet to access oldValue, and for willSet to access newValue (if passed into an inout parameter), the value needs to be implicitly copied. So I believe they already wouldn't work for noncopyable properties [1]. For copyable properties, allowing them to be partially reinitialized would (in my opinion) defeat the purpose of the consume operator, which is to prevent implicit copies.
As for didSet and willSet accessors that don't access oldValue or newValue, I think it could be useful for those to also support partial reinitialization in limited ways.
Maybe a property's didSet can be called after the property is reinitialized, if reinitializing the property makes the whole value initialized. Then the BufferedFile.switchFile example would still work if file had a didSet.
Maybe a property's willSet can be called before the property is consumed, if the whole value is initialized prior. It's harder for me to think of cases where that would be useful. And it would have footguns: consume would suddenly gain side effects, and its behavior would depend on whether or not the property is reinitialized later.
[1] Edit: I suppose it's possible for copying to be avoided as long as there is a direct assignment instead of an inout access, since with direct assignment the new and old values can coexist without copying. With some testing, it appears that the compiler behaves somewhat strangely in such cases.
Compiler behavior
The compiler (as of 6.2) errors at the point of definition (but doesn't print the definition or its location) if a noncopyable property's didSet accesses oldValue. It separately errors at the point of use if that property is passed as inout, but not if it is directly assigned.
The compiler allows a noncopyable property's willSet to access newValue. It allows direct assignment to that property, but errors at the point of use if that property is accessed as inout.
struct A: ~Copyable {}
func doBorrow(_ a: borrowing A) {}
func doMutate(_ a: inout A) {}
struct B: ~Copyable {
var a1: A = A() {
didSet {
doBorrow(oldValue)
}
}
var a2: A = A() {
willSet {
doBorrow(newValue)
}
}
}
func f1() {
var b = B()
b.a1 = A()
}
func f2() {
var b = B()
doMutate(&b.a1)
}
func f3() {
var b = B()
b.a2 = A()
}
func f4() {
var b = B()
doMutate(&b.a2)
}
<unknown>:0: error: cannot partially reinitialize 'self' after it has been consumed; only full reinitialization is allowed
test.swift:17:27:7: error: 'b' used after consume
25 |
26 | func f2() {
27 | var b = B()
| `- error: 'b' used after consume
28 | doMutate(&b.a1)
| |- note: consumed here
| `- note: used here
29 | }
30 |
test.swift:37:7: error: 'b' used after consume
35 |
36 | func f4() {
37 | var b = B()
| `- error: 'b' used after consume
38 | doMutate(&b.a2)
| |- note: consumed here
| `- note: used here
39 | }
40 |
Properties with didSet/willSet should be considered computed for the purposes of this proposal. I can make that explicit.