#swift
#swift
Вопрос:
Я переключаюсь на Swift, и я действительно недоволен тем, что следующий код компилируется без предупреждения:
func f(_ x: inout Int?) {
var x: Int? // <-- this declaration should produce a warning
x = 105
if x! < 1000 {}
}
var a: Int? = 3
f(amp;a)
print("(a)")
и, конечно же, выводит Optional(3)
данные при выполнении.
В этом примере x
локальная переменная затеняет параметр x
функции.
Включение функции Hidden Local Variables
warning ( GCC_WARN_SHADOW
) в настройках проекта также не приводит к появлению предупреждения.
Вопрос: Как мне поступить, чтобы компилятор Swift 3 предупредил меня о таком затенении?
Комментарии:
1. Очень хороший момент, просто обратите внимание, что использование
inout
параметров и / или опций не является необходимым условием для минимального примера этой «проблемы» (а просто вариант использования, когда это затенение может стать особенно запутанным). Например. дляfunc f(_ x: Int) { let x: Int = 105; print(x) }
локального значенияx
(105
) затмит любой заданный аргументf
, без компиляторапредупреждения.2. По какой-то причине они не хотят помещать предупреждающие переключатели в компилятор. Предполагается, что подобные вещи должны быть переданы сторонним инструментам компоновки. Однако я не верю, что у SwiftLint есть правило для этого.
3. Это было зарегистрировано как ошибка: bugs.swift.org/browse/SR-1687
4. @dfri да, я только что скопировал опции из реального кода, который я писал
5. @JoshCaswell что касается переключателей, это очень прискорбно; сторонние инструменты не должны требоваться для предотвращения вещей, которые очень реалистично могут привести к серьезным ошибкам, и их трудно обнаружить
Ответ №1:
Хотя вы, возможно, уже нашли полезные решения, в документации Apple по функциям фактически есть комментарий именно к этому типу использования. Вы запросили ответ на вопрос, почему подсветка кода не предупреждает вас о конфликте имен, но основная причина, по которой вы, вероятно, не получаете никаких предупреждений, заключается в том, что inout
параметры и все параметры не имеют приоритета над переменными, инициализированными в функции (они являются только копиями значения, которое они представляют при инициализации).манипулируется внутри функции). Итак, ваша функция, как я проиллюстрирую ниже, не учитывает параметр, который вы передаете, потому что вы инициализируете новую переменную с тем же именем. Следовательно, по правилам, которыми управляют параметры, ваш переданный параметр полностью игнорируется. Я вижу ваше разочарование, так как в некоторых других языках это было бы ошибкой компилятора. Однако, с inouts здесь, это просто не так, как принято. Смотрите Здесь, в документах :
Входные и выходные параметры передаются следующим образом:
При вызове функции значение аргумента копируется. В теле функции копия изменяется. Когда функция возвращается, исходному аргументу присваивается значение копии. Это поведение известно как копирование при копировании или вызов по значению result . Например, когда вычисляемое свойство или свойство с наблюдателями передается в качестве параметра ввода-вывода, его получатель вызывается как часть вызова функции, а его установщик вызывается как часть возврата функции.
В качестве оптимизации, когда аргументом является значение, хранящееся по физическому адресу в памяти, одна и та же ячейка памяти используется как внутри, так и снаружи тела функции. Оптимизированное поведение известно как вызов по ссылке; оно удовлетворяет всем требованиям модели копирования при копировании, устраняя накладные расходы на копирование. Напишите свой код, используя модель, заданную копированием копирования, независимо от оптимизации вызова по ссылке, чтобы он вел себя правильно с оптимизацией или без нее.
Не обращайтесь к значению, которое было передано в качестве входного аргумента, даже если исходный аргумент доступен в текущей области. Когда функция возвращается, ваши изменения в оригинале перезаписываются значением копии. Не зависите от реализации оптимизации вызова по ссылке, чтобы попытаться предотвратить перезапись изменений. [..]
В вашем случае, если бы вы действительно хотели изменить передаваемый вами параметр, вы бы использовали что-то похожее на это:
Если вам нужно захватить и изменить входной параметр, используйте явную локальную копию, например, в многопоточном коде, которая гарантирует, что все изменения завершены до возврата функции.
func multithreadedFunction(queue: DispatchQueue, x: inout Int) {
// Make a local copy and manually copy it back.
var localX = x
defer { x = localX }
// Operate on localX asynchronously, then wait before returning.
queue.async { someMutatingOperation(amp;localX) }
queue.sync {}
}
Итак, как вы можете видеть здесь, хотя localX не называется x, как то, что вы делаете, localX использует целый другой экземпляр памяти для хранения данных. Которое в данном случае имеет то же значение, что и x, но не является экземпляром x, поэтому оно не компилируется как ошибка именования.
Чтобы показать, что это все еще применяется при изменении localX на var x = Int ? как вы делаете внутри своей функции:
func f(_ x: inout Int?) {
print(x, "is x")
var x: Int? // <-- this declaration should produce a warning
print(x, "is x after initializing var x : Int?")
x = 105
print(x, "is x after giving a value of 105")
if x! < 1000 {}
}
var a: Int? = 3
f(amp;a)
print("(a)", "is x after your function")
ВОЗВРАТ:
Optional(3) is x
nil is x after initializing var x: Int?
Optional(105) is x after giving a value of 105 to x
Optional(3) is x after your function
Чтобы показать вам, как далеко это заходит, я использую то, что сделал Мохсен, чтобы показать вам, что он не был полностью неправ в своей логике, чтобы показать вам это правило в соглашении, хотя я согласен, что он не обращался к отсутствию предупреждения о коде в вашем вопросе.
func f(_ x: inout Int?) {
print(x, "is inout x")
var y: Int? // <-- this declaration should produce a warning
print(x, "is inout x and ", y, "is y")
x = 105
print(x, "is inout x and ", y, "is y after giving a value of 105 to inout x")
if x! < 1000 {}
}
var a: Int? = 3
f(amp;a)
print("(a)", "is x after your function")
С принтами:
Optional(3) is inout x
Optional(3) is inout x and nil is y
Optional(105) is inout x and nil is y after giving a value of 105 to inout x
Optional(105) is x after your function
Итак, как вы можете видеть здесь, в первой функции, ваши параметры ввода и параметры в целом перестают иметь приоритет над тем, что содержится внутри, потому что технически внутри функции нет инициализации, что является целью самого соглашения о вводе: функция сохраняет это значение в памяти, присваивает этов памяти создается указатель, и любые изменения, применяемые к этому указателю, затем применяются к исходной переменной, которая находится за пределами области действия функции, когда функция завершается. Поэтому, какие бы мутации вы ни сделали с ним после var x: Int?
, они не изменят переменную в вашем параметре inout при return
попадании, потому что вы переопределили указатель, присвоенный букве x. Чтобы показать вам, что это не так non-inouts
, мы назначим отдельную переменную из x:
func f(_ x: Int?) {
print(x!, "is inout x")
var y: Int? // <-- this declaration should produce a warning
print(x!, "is inout x and ", y!, "is y")
x = 105
y = 100
print(x!, "is inout x and ", y!, "is y after giving a value of 105 to inout x")
if x! < 1000 {}
}
var a: Int? = 3
f(a)
print("(a!)", "is x after your function")
ВОЗВРАТ
Playground execution failed: error: SomeTest.playground:6:7: error: cannot assign to value: 'x' is a 'let' constant
x = 105
Но, если я вернусь к исходной функции и переименую новую переменную в тот же указатель, что и имя параметра:
func f(_ x: Int?) {
print(x, "is inout x")
var x: Int? // <-- this declaration should produce a warning
print(x, "is inout x and ")
x = 100
print(x, "is inout x and ")
if x! < 1000 {}
}
var a: Int? = 3
f(a)
print("(a!)", "is x after your function")
мы получаем:
Optional(3) is inout x
nil is inout x and
Optional(100) is inout x and
3 is x after your function
Таким образом, в целом, параметр inout и стандартный параметр никогда не изменяются, потому что в пределах области действия функции указатель на x полностью переопределяется Int?
.
Вот почему вы не получаете предупреждение о коде, и технически вы не должны этого делать, потому что соглашения, окружающие параметры, диктуют, что то, что вы написали, не является конфликтом компиляции и является допустимым кодом (возможно, это может быть не для вашего варианта использования, но обычно это так), и поэтому вы, скорее всего, будетене удается найти способ выделить эту проблему с именами.
Ответ №2:
Что касается необязательного суффикса и параметра inout, в подобном случае вам следует обращаться с ним примерно так:
func f(_ x: inout Int?)
{
guard let guardedX = x else
{
return
}
... Rest of method
if x != nil
{
x = guardedX
}
}
- Это предотвратит это (необязательно) при печати.
- Это предотвратит сбой, который произойдет при вашей проверке
if x! < 1000
, когда кто-то изменит x на nil!
Вы должны перейти к сопоставлению шаблонов swift и всему, что связано с развертыванием и необязательным объединением в цепочки и параллелизмом (не специфичным для swift), чтобы убедиться, что вы пишете хороший аккуратный код без сбоев.
Ответ №3:
Это также происходит для параметров, которые также не InOut
являются, т.е.:
func testShadowParam(number: Int) {
let number: Int = 3 // also does not produce a warning
print(number)
}
Это документированная ошибка компилятора: https://bugs.swift.org/browse/SR-1687