Действительно ли здесь применяется предупреждение «Одновременный доступ к параметру self»?

Я написал расширение для 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
    }

}

Этот простой пример на детской площадке работает как шарм:

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]

Но использование расширения внутри структуры (код после предупреждения) дает мне такое предупреждение:

Одновременный доступ к параметру self, но для изменения требуется монопольный доступ; рассмотрите возможность копирования в локальную переменную

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, я понимаю цель предупреждения, если бы я использовал его для извлечения последнего элемента из того же массива, в который я его добавляю, потому что копирование при записи может испортить это. Думаю, это связано со структурой. Но, используя его на двух разных массивах внутри одной структуры, я не вижу опасности. Я что-то упустил, и есть ли способ написать расширение, которое обойдет потенциальную проблему?


person Benno Kress    schedule 27.06.2017    source источник
comment
Было бы полезно использовать минимальный воспроизводимый пример.   -  person Martin R    schedule 27.06.2017
comment
@MartinR Предоставленный код игровой площадки - это именно то, что я делаю в приложении. Я просто использую там элемент другого типа, который не добавляет ценности вопросу.   -  person Benno Kress    schedule 27.06.2017
comment
Предоставленный код не вызывает предупреждающего сообщения, поэтому должно быть что-то еще в том, как вы используете его в своем приложении. MCVE - это (автономный) фрагмент кода, который любой может скопировать / вставить в Xcode для воспроизведения проблемы.   -  person Martin R    schedule 27.06.2017
comment
@MartinR Я знаю, что вы хотите помочь, но когда я просто скопировал точный код с игровой площадки в свой проект, он выдает предупреждение, поэтому я не могу предложить лучшего MCVE. Как упоминалось в вопросе: на детской площадке нет предупреждений, только в проекте (что для меня тоже странно). Снимок экрана   -  person Benno Kress    schedule 27.06.2017
comment
Я скопировал приведенный выше код в main.swift проекта Xcode 9 beta 2 и не получил предупреждения. Ваш код на скриншоте - это не то, что вы разместили в вопросе.   -  person Martin R    schedule 27.06.2017
comment
Являются ли newNumbers и usedNumbers переменными-членами структуры? Это объяснило бы упоминание о доступе к self.   -  person David Rönnqvist    schedule 27.06.2017
comment
@MartinR ладно, похоже, я слишком упростил код. Предупреждение появляется после его использования в структуре - теперь и на игровой площадке. Я обновляю приведенный выше код ... извините за это!   -  person Benno Kress    schedule 27.06.2017
comment
@ DavidRönnqvist: Да, я тоже это понял. Итак, проблема в том, что он использует копирование при записи для всей структуры, я полагаю? Но разве все же не безопасно, если вся функция запускается внутри копии структуры? Я честно думаю, что должен, но не знаю ...   -  person Benno Kress    schedule 27.06.2017
comment
Из SE-0176: Однако обратите внимание, что изменение части типа значения по-прежнему требует монопольного доступа ко всему значению ...   -  person Martin R    schedule 27.06.2017
comment
Из раздела Типы значений раздела SE-0176 Предложение. Вызов метода для типа значения - это доступ ко всему значению, поскольку [предполагается], что метод может читать или записывать произвольную часть ценить. [...] обратите внимание, что изменение части типа значения по-прежнему требует монопольного доступа ко всему значению, и что получение этого доступа может само по себе предотвращать перекрывающиеся доступы.   -  person David Rönnqvist    schedule 27.06.2017
comment
Да, спасибо, ребята, и еще раз извините за путаницу вначале. Я думал, что сузил свой код до наиболее простого для понимания примера и не осознал важность struct здесь.   -  person Benno Kress    schedule 27.06.2017
comment
Возможно, мне что-то не хватает, но я не вижу, как пример OP конфликтует с , обратите внимание, что для изменения части типа значения по-прежнему требуется эксклюзивный доступ ко всему значению - у нас есть эксклюзивный доступ ко всему значению, мы внутри метода mutating. И Доступ к различным сохраненным свойствам структуры или различным элементам кортежа может перекрываться, поэтому одновременный доступ на запись для newNumbers & usedNumbers не должен быть проблемой.   -  person Hamish    schedule 27.06.2017
comment
Действительно, предложенный пример modifying(&object.pair) { pair in swap(&pair.x, &pair.y) } фактически не компилируется, потому что компилятор считает, что одновременный доступ на запись к pair.x и pair.y в конфликтах закрытия.   -  person Hamish    schedule 27.06.2017
comment
Соответствующий отчет об ошибке: bugs.swift.org/browse/SR-5119   -  person Hamish    schedule 27.06.2017


Ответы (1)


Обновление:

Теперь ваш код работает без изменений.

Как @Hamish отметил в комментариях ниже, это было ошибкой , который теперь исправлен в версии Swift, поставляемой с Xcode 9 beta 3. Я также подтвердил, что он работает на IBM Swift Sandbox, в котором используется сборка Linux x86_64 Swift Dev. 4.0 (13 июля 2017 г.).


Как вы поняли в комментариях, проблема в том, что вам нужен монопольный доступ к структуре, чтобы изменить ее, но вы передаете ссылку на часть структуры в параметр inout. Очевидно, это должно работать, поскольку вы обращаетесь к разным частям структуры, но из-за к ошибке компилятор здесь слишком строг.

Предупреждение предлагает скопировать в локальную переменную. Поскольку ваш возврат сложен, я использовал оператор defer, чтобы вернуть копию newNumbers в 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)
}
person vacawama    schedule 27.06.2017
comment
Ваше решение - возможное исправление (как вы упомянули, в комментариях мы заметили, что моя проблема действительно связана с ошибкой). Все мое намерение при написании расширения состояло в том, чтобы сократить код при сохранении читабельности, поэтому я пока останусь без расширения, поскольку читаемое решение кажется достижимым только в 3 строках. - person Benno Kress; 27.06.2017
comment
Похоже, исправление уже в трубе. Я просто подумал, что стоит упомянуть, что defer можно использовать, чтобы позволить вашему возврату по-прежнему работать без необходимости сохранять результат во временной переменной. - person vacawama; 27.06.2017
comment
Другой возможный обходной путь с использованием модифицированной версии функции modifying из предложения: gist.github.com/hamishknight/7392cbbbf5090250250 (почему в этом конкретном случае не запускается статическое принуждение, idk). - person Hamish; 27.06.2017
comment
Интересный @Hamish. Я думаю, что это позволяет избежать предупреждения из-за того, как работает inout. По сути, это работает так же, как и мое первое решение. Копия newNumbers делается в modifying, а результат записывается обратно в newNumbers на выходе. Когда вызывается popLast(to:), они не работают с одной и той же структурой. - person vacawama; 27.06.2017
comment
@vacawama Но тогда по этой логике код OP ничем не отличается от вашего второго решения;) Когда адресуемая переменная передается в параметр inout, это делается по ссылке (но семантически это все равно следует рассматривать как копирование в copy-out) - поэтому newNumbers передается по ссылке на modifying. - person Hamish; 27.06.2017
comment
Ошибка обнаружена в коде OP на месте вызова. В этот момент делается ссылка на usedNumbers, чтобы значение inout могло быть передано ему, что предотвращает изменение newNumbers (та же структура). Вот что вызывает проблему одновременного доступа. В случае модификации я считаю, что структура внутри модификации является копией во время вызова popLast(to:), что позволяет избежать проблемы. - person vacawama; 27.06.2017
comment
@Hamish: developer.apple.com/library/content/documentation/Swift/ - person vacawama; 27.06.2017
comment
@vacawama github.com/apple/swift/blob/ master / docs / SIL.rst # inout-arguments :) Конечно, в предыдущих версиях языка inout параметры выполнялись с использованием копирования в копирование, но теперь они компилируются как проход по ссылкам. Я могу только предположить, что они не обновили документацию, чтобы отразить это из-за того, что их семантически все еще следует рассматривать как копирование-в-копирование-выход. - person Hamish; 27.06.2017
comment
Я не думаю, что структура, содержащая 2 массива, соответствует критерию , хранящемуся по адресу для эталонной оптимизации, поэтому она использует копирование в - копирование. - person vacawama; 27.06.2017
comment
@vacawama Я смотрю на IR здесь, и он передается по ссылке - gist.github.com/ hamishknight / ac9372e6bc0b2dd69d4f4f9287328eed, если вам интересно. - person Hamish; 27.06.2017
comment
Код OP теперь компилируется нормально, начиная с версии Swift 4, которая поставляется с Xcode 9 beta 3 :) - person Hamish; 20.07.2017
comment
Спасибо, @Hamish! Я обновил ответ этой информацией. - person vacawama; 20.07.2017