Swift 3 позволяет скрывать параметр

Я переключаюсь на 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(&a)
print("\(a)")

и, конечно же, выходы Optional(3) после выполнения.

В этом примере локальная переменная x затеняет параметр функции x.

Включение предупреждения Hidden Local Variables (GCC_WARN_SHADOW) в настройках проекта не приводит к возникновению предупреждения.

Вопрос: Как мне заставить компилятор Swift 3 предупреждать меня о таком затенении?

Ответ 1

Хотя вы, возможно, нашли полезные решения уже, Apple Documentation on Functions на самом деле имеет комментарий к этому точному типу использования. Вы запросили ответ, почему подсветка кода не предупреждает вас о конфликте имен, но основная причина, по которой вы, вероятно, не получаете предупреждения, состоит в том, что параметры inout и все параметры не имеют приоритета над переменными, инициализированными в (они являются только копиями значения, которое они представляют при манипулировании внутри функции). Таким образом, ваша функция, как я проиллюстрирую ниже, не рассматривает параметр, который вы передаете, потому что вы инициализируете новую переменную с тем же именем. Поэтому по правилам, которые управляют параметрами, ваш переданный параметр полностью игнорируется. Я вижу ваше разочарование, так как в некоторых других языках это будет ошибкой компилятора. Однако, с inouts здесь, это просто не так, как соглашение. См. Здесь docs:

Параметры вставки передаются следующим образом:

Когда функция вызывается, значение аргумента копируется. В тело функции, копия изменяется. Когда функция return, значение copys присваивается исходному аргументу. Эта поведение называется копированием или копированием по результату. Для Например, когда вычисленное свойство или свойство с наблюдателями как входной параметр, его геттер называется частью вызов функции и его сеттер вызывается как часть возврата функции.

В качестве оптимизации, когда аргумент представляет собой значение, хранящееся на физическом адрес в памяти, то же место памяти используется как внутри, так и внутри вне тела функции. Оптимизированное поведение называется вызовом Справка; он удовлетворяет всем требованиям копирования при удалении накладных расходов на копирование. Напишите свой код используя модель, предоставленную копированием в копии, в зависимости от оптимизация по запросу, так что она ведет себя корректно с без оптимизации.

Не обращайтесь к значению, которое было передано как аргумент in-out, даже если исходный аргумент доступен в текущей области. Когда функция возвращает, ваши изменения в оригинале перезаписываются значение копии. Не зависеть от оптимизация по запросу, чтобы попытаться сохранить изменения перезаписаны. [..]

В вашем случае, если бы вы действительно изменяли параметр, который вы проходили, вы использовали бы что-то похожее на это:

Если вам нужно захватить и изменить параметр in-out, используйте явный локальная копия, например, в многопоточном коде, который обеспечивает всю мутацию до того, как функция вернется.

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(&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(&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(&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

Итак, как вы можете видеть здесь в первой функции, ваши inout параметры и параметры в целом перестают иметь приоритет над тем, что содержится внутри, потому что технически не имеет инициализации внутри функции, которая является целью inout само соглашение: функция сохраняет это значение в памяти, присваивает этому экземпляру памяти указатель, и любые мутации, применяемые к этому указателю, затем применяются к исходной переменной, которая выходит за пределы области функции, когда функция заканчивается. Так что любые мутации, которые вы могли бы сделать с ним после 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!

Вы должны перейти к быстрому соответствие шаблону и все, что связано с распаковкой и необязательная цепочка и Concurrency (не обязательно для быстрого), чтобы убедиться, что вы пишете хороший аккуратный код без сбоев.

Ответ 3

Изменить имя X (в значении параметра или varible) на другое имя

Пример

func f(_ x: inout Int?) {
    var y: Int?
    y = 105
    if x! < 1000 
{
    x = y
}
}

var a: Int? = 3
f(&a)
print("\(a)")