警告 "Simultaneous accesses to parameter 'self' ..." 真的适用于此吗?

Does the warning "Simultaneous accesses to parameter 'self' ..." really apply here?

我写了一个 Array 的扩展,允许我弹出最后一个元素并立即将它添加到另一个数组:

extension Array {

    mutating func popLast(to otherArray: inout [Element]) -> Element? {
        guard self.count > 0 else { return nil }
        return otherArray.appendAndReturn(self.popLast()!)
    }

    mutating func appendAndReturn(_ element: Element) -> Element {
        self.append(element)
        return element
    }

}

playground 中的这个简单示例很有魅力:

var newNumbers = [1,2,3,4,5,6,7,8,9]
var usedNumbers: [Int] = []

newNumbers.popLast(to: &usedNumbers)
print(usedNumbers) // [9]

for _ in newNumbers {
    newNumbers.popLast(to: &usedNumbers)
}

print(usedNumbers) // [9, 8, 7, 6, 5, 4, 3, 2, 1]

但是在结构内部使用扩展(警告后的代码)给了我这个警告:

Simultaneous accesses to parameter 'self', but modification requires exclusive access; consider copying to a local variable

struct Test {

    var newNumbers = [1,2,3,4,5,6,7,8,9]
    var usedNumbers: [Int] = []

    mutating func getNewNumber() -> Int? {
        return newNumbers.popLast(to: &usedNumbers)
    }

}

这只是一个警告,我的应用程序运行正常,符合预期的行为,但我很好奇这里是否真的存在危险。查看 SE-0176,我理解警告的目的,如果我要使用它从我追加它的同一个数组中弹出最后一个元素,因为写时复制可能会搞砸。所以我猜这与结构有关。但是在同一结构内的两个不同数组上使用它,我认为没有危险。 我是不是遗漏了什么,有没有办法编写可以避免潜在问题的扩展?

更新:

您的代码无需修改即可运行。

正如@Hamish 在下面的评论中指出的那样,这个 was a bug that is now fixed in the version of Swift which shipped with Xcode 9 beta 3. I also verified that it works at the IBM Swift Sandbox 使用 Linux x86_64 build Swift开发。 4.0(2017 年 7 月 13 日).


正如您在评论中发现的那样,问题是您需要独占访问该结构才能对其进行变异,但是您将对该结构的一部分的引用传递给了 inout 参数。这显然应该有效,因为您正在访问结构的不同部分,但是 due to a bug 这里的编译器太严格了。

警告建议复制到局部变量。由于您的 return 很复杂,我使用 defer 语句将 newNumbers 的副本 return 复制到 newNumbers 以避免必须存储调用结果在临时变量中:

struct Test {

    var newNumbers = [1,2,3,4,5,6,7,8,9]
    var usedNumbers: [Int] = []

    mutating func getNewNumber() -> Int? {
        var newNumbersCopy = newNumbers
        defer { newNumbers = newNumbersCopy }
        return newNumbersCopy.popLast(to: &usedNumbers)
    }

}

您也可以选择通过复制 usedNumbers:

来修复它
mutating func getNewNumber() -> Int? {
    var usedNumbersCopy = usedNumbers
    defer { usedNumbers = usedNumbersCopy }
    return newNumbers.popLast(to: &usedNumbersCopy)
}