this is a pattern i get burned by when having LLMs write Swift code:
here, S is a noncopyable struct, with a noncopyable field T and a copyable field U. but U is only copyable because it needs to support protocol conformances (like Sequence). neither field should ever actually be copied - if they do, that’s a very bad day for this barbie.
struct S: ~Copyable {
struct T: ~Copyable {
var x: [Int]
}
struct U {
var y: [Int]
}
let t: T
let u: U
}
if you, or your AI assistant write something like this, one could be highly confident this is avoiding the copy hazard, mine (Gemini Flash 3.6) certainly was:
func f(s: inout S, yield: (inout S.T, inout S.U) -> ()) {
let local: S = consume s
var t: S.T = local.t
var u: S.U = local.u
yield(&t, &u)
s = .init(t: t, u: u)
}
We have direct compiler proof that
local's lifetime is already ended right on that line!
When we tested adding_ = consume localright after constructing ..., the Swift compiler emitted this exact diagnostic:
error: 'local' consumed more than once
_ = consume local
`- note: consumed again here
— an erroneous LLM output (Gemini 3.6 Flash)
the problem here is that the AI thinks there’s Rust-style partial consumption of fields going on here, and that the access to local.t is “turning off” the pessimizing assumptions of SE-0380. but that’s not really what the fine print of SE-0429 says - because U is still copyable, SE-0380 remains in effect and local.u is considered to be alive for the full duration of f. which means yield remains vulnerable to copy-on-write.
because encapsulation usually prohibits the injection of empty sentinel values in real code, the only “right” way to express this operation is like this:
func g(s: inout S, yield: (inout S.T, inout S.U) -> ()) {
let local: S = consume s
var t: S.T
var u: S.U
// or just skip `local` and `consume s` directly
switch (consume local) {
case let local:
t = local.t
u = local.u
}
yield(&t, &u)
s = .init(t: t, u: u)
}
but i find this highly unintuitive. this is an avoidable footgun and this behavior is just a bad default.
i feel a more sensible default is to just turn off SE-0380 if any noncopyable field is being explicitly consumed, even if other fields are copyable.