Подтвердить что ты не робот

Два разных типа ноля в Свифте?

Я столкнулся с некоторым странным поведением на языке Swift при работе в REPL (= Read-Eval-Print-Loop), где, как представляется, существуют два разных типа значений nil с различным поведением во время выполнения

Для этого я определяю функцию g:

func g(x:String!) {
    println("start!");
    println((x == "foo") ? "foo" : "not");
}

Затем я определяю две переменные:

var x:String
var y:String!

Когда я вызываю g(x), он работает как Objective-C:

start!
not

Когда я вызываю g(y), я получил сообщение об ошибке:

start!
fatal error: Can't unwrap Optional.None
Execution interrupted. Enter Swift code to recover and continue.
Enter LLDB commands to investigate (type :help for assistance.)

Обратите внимание, что эта ошибка попадает во время выполнения! Функция уже запущена. Вы можете видеть это из-за "начала!" строка на выходе.

Кажется, что в первом случае функция получает значение nil вместе с запиской "это не значение nil". Во втором случае он, кажется, получает нуль, с прикрепленной запиской "это значение равно нулю, пожалуйста, кричите и не просто используйте его".

Почему я не получил ошибку в первом случае и ошибку во втором случае? Не должно быть поведения g (x) и g (y) одного и того же?

Является ли это ожидаемым поведением? Я что-то упускаю? Это ошибка в Swift? Ошибка в спецификации? Или ошибка в реализации? Или просто ошибка в REPL?. Нельзя ли получить доступ к неинициализированным переменным?


Весь протокол сеанса, для справки...

$ /Applications/Xcode6-Beta.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/swift
Welcome to Swift!  Type :help for assistance.
  1> func g(x:String!) {println("start!"); println((x=="foo") ? "foo" : "not");} 
  2> var x:String
x: String = {
  core = {
    _baseAddress = Builtin.RawPointer = 0x0000000000000000
    _countAndFlags = 0
    _owner = None
  }
}
  3> var y:String!
y: String! = nil
  4> g(x)
start!
not
  5> g(y)
start!
fatal error: Can't unwrap Optional.None
Execution interrupted. Enter Swift code to recover and continue.
Enter LLDB commands to investigate (type :help for assistance.)
  6> 

Чтобы воспроизвести весь сеанс, просто введите следующий код в терминал:

/Applications/Xcode6-Beta.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/swift
func g(x:String!) {println("start!"); println((x=="foo") ? "foo" : "not");}
var x:String
var y:String!
g(x)
g(y)

(функция написана в одной строке, потому что функция копирования и вставки повреждена в REPL, по крайней мере, на моей машине. Однако это написано в одной строке... Это еще одна история...)


Обновление в марте 2015 г. Apple, похоже, исправила эту проблему. Уже невозможно объявить нормальную переменную без начального значения:

 2> var x:String
repl.swift:2:1: error: variables currently must have an initial value when entered at the top level of the REPL
var x:String
^
4b9b3361

Ответ 1

REPL выполняет некоторую инициализацию для вас автоматически, даже если вы этого не хотите

Я скопировал и запечатал ваш код на игровой площадке, и он меня выбрал:

error: variable 'x' used before being initialized
g(x)
  ^
<REPL>:22:5: note: variable defined here
var x:String

Я думаю, что это правильный результат. Код не должен компилироваться.

Когда я ввожу это в REPL

var a:String

он печатает

a: String = {
  core = {
    _baseAddress = Builtin.RawPointer = 0x0000000000000000
    _countAndFlags = 0
    _owner = None
  }
}

поэтому REPL каким-то образом инициализирует a в строку nil String (что я не уверен, что она законна для существования), а остальная часть рассказа объясняется в других ответах


Похоже, что REPL выполняет инициализацию по умолчанию для каждой переменной, поэтому вы не можете использовать неинициализированную переменную

Welcome to Swift!  Type :help for assistance.
  1> var a:String
a: String = {
  core = {
    _baseAddress = Builtin.RawPointer = 0x0000000000000000
    _countAndFlags = 0
    _owner = None
  }
}
  2> var b:Int
b: Int = 0
  3> var c:Int[]
c: Int[] = size=0
  4> var d:Dictionary<Int,Int>
d: Dictionary<Int, Int> = {}

более интересно, что он все еще может инициализировать необязательный тип с помощью nil

  5> import Foundation
  6> var f:NSObject
f: NSObject = {}
  7> var g:NSNumber
g: NSNumber = {
  Foundation.NSValue = <parent is NULL>

}
  8> print(g)
fatal error: Can't unwrap Optional.None
Execution interrupted. Enter Swift code to recover and continue.

Таким образом, REPL включает доступ к неинициализированной переменной (которая должна быть ошибкой времени компиляции) в ошибку времени выполнения

Welcome to Swift!  Type :help for assistance.
  1> class Test{ var val:Int; init(v:Int) {val=v} }
  2> var t:Test
t: Test = {
  val = <parent is NULL>

}
  3> t.val
Execution interrupted. Enter Swift code to recover and continue.
Enter LLDB commands to investigate (type :help for assistance.)
  4> t = Test(v:1)
  5> t.val
$R2: Int = 1
  6> 

Ответ 2

Ожидается поведение.

var y:String! 

(с восклицательным знаком) является "неявным образом развернутым необязательным", это означает, что это необязательно, но вам не нужно его разворачивать (добавлять!), когда вы хотите получить к нему доступ. Но, как опция, вы будете запускать исключение во время выполнения при попытке получить к нему доступ, если оно не имеет значения:

Из слова "Язык быстрого программирования" - стр. 78

"Если вы попытаетесь получить доступ к неявно развернутому необязательному, если он не содержит значения, вы вызовете ошибку времени выполнения. Результат будет таким же, как если бы вы поместили восклицательный знак после обычного необязательного, который не содержит значение".

Таким образом, факт, что g (y) падает, верен. Кажется странным, что g (x) не сбой, но, похоже, это поведение REPL, которое инициализирует x для вас.

Для необязательных значений понятие nil не существует. Итак, x не ноль, это что-то вроде "uninitialized". Если вы попытаетесь поместить этот код в реальный проект

    func g(testValue:String!) {
        println("start!");
        println((testValue == "foo") ? "foo" : "not");
    }

    var x:String
    var y:String!

    g(x)
    g(y)

Он не будет компилироваться, вы получите:

Переменная 'x', используемая до инициализации

Ответ 3

Является ли это ожидаемым поведением? Я что-то упускаю? Это ошибка в Swift?

Похоже, это из-за вашей предыдущей активности в REPL. Вот то, что я получаю, когда вставляю свой код в игровую площадку:

Playground execution failed: error: code after 'return' will never be executedcode after 'return' will never be executed<REPL>:8:3: error: variable 'x' used before being initialized
g(x)
  ^
<REPL>:6:5: note: variable defined here
var x:String

В основном, он жалуется, что вы используете x, не указав при этом значение. Однако, смотря на расшифровку вашей сессии, вы даете x значение в строке 2:

  2> var x:String
x: String = {
  core = {
    _baseAddress = Builtin.RawPointer = 0x0000000000000000
    _countAndFlags = 0
    _owner = None
  }
}

Поэтому неудивительно, что компилятор в порядке с x. Ошибка, которую вы получаете, связана с тем, что y объявляется как неявно развернутая переменная. Вы устанавливаете его на nil, пытаетесь получить к нему доступ и получаете ошибку времени выполнения, которая говорит, что значение не может быть развернуто. Ошибка там неудивительна - то, что должно было произойти, когда вы пытаетесь принудительно развернуть ноль переменной.

Ответ 4

var x: String

должно иметь значение. May не будет noil.

Это проблема. Вы не должны использовать неинициализированную переменную (она не является необязательной).


var y: String!

может использоваться без инициализации, значение может быть ноль. Это похоже на запись

var y: String?//НО, пожалуйста, каждый раз, когда я использую его, вставил восклицательный знак после y для меня

Итак, вместо y! вы можете написать y. Но значение может быть nil. Используя y, когда значение nil → crash.