web-dev-qa-db-fra.com

Pourquoi non facultatif, tout peut contenir zéro?

Dans Swift, je peux déclarer une constante de type Any et y mettre une String.

let any: Any = "hello world"

Bien. D'autre part, je ne peux pas mettre une valeur nil dans any car ce n'est pas facultatif.

let any: Any = nil

error: nil cannot initialize specified type 'Any' (aka 'protocol<>')
let any: Any = nil
              ^

Parfait. Mais pourquoi le compilateur me permet-il d'écrire le code suivant?

let couldBeNil: String? = nil
let any: Any = couldBeNil
print(any) // nil

Any ne suit-il pas la règle Swift selon laquelle seule une variable optionnelle var/let peut être remplie avec nil?

Testé avec Xcode Playground 7.2 + Swift 2.1.1

31
Luca Angeletti

TL; DR; Les options dans Swift sont traduites par le compilateur en instances Optional enum et, étant donné que Any peut mapper sur n'importe quelle valeur, il peut être utilisé pour stocker des options.


Comment Swift représente-t-il les optionnels? Cela se fait en mappant SomeType? à une implémentation concrète de l'énumération Optional:

Int? => Optional<Int>
String? => Optional<String>

Une déclaration simplifiée de Optional ressemble à ceci:

enum Optional<T> {
    case none    // nil
    case some(T) // non-nil
}

Désormais, une variable de type Any peut contenir une valeur enum (ou tout autre type de valeur, ou même d’information de métatype). Elle devrait donc pouvoir contenir par exemple une chaîne nil, également appelée String?.none, ou Optional<String>.none.

Voyons ce qui se passe, cependant. Comme nous le voyons dans la déclaration Optional, nil correspond au cas .none enum pour tous les types:

nil == Optional<String>.none // true
nil == Optional<Int>.none    // true
[Double]?.none == nil        // also true

Donc, théoriquement, vous devriez pouvoir affecter nil à une variable déclarée comme Any. Pourtant, le compilateur ne le permet pas.

Mais pourquoi le compilateur ne vous permet-il pas d'affecter nil à une variable Any? C'est parce qu'il ne peut pas déduire à quel type mapper le cas .none enum. Optional est une énumération générique. Il a donc besoin de quelque chose pour renseigner le paramètre générique T et plain nil est trop large. Quelle valeur .none doit-il utiliser? Celui de Int, celui de String, un autre?

Cela donne un message d'erreur supportant le paragraphe ci-dessus:

let nilAny: Any = nil // error: nil cannot initialize specified type 'Any' (aka 'protocol<>')

Le code suivant fonctionne et équivaut à l'attribution d'une nil:

let nilAny: Any = Optional<Int>.none

, car la variable Any ci-dessus contient en réalité une valeur valide de Optional enum.

Les assignations indirectes fonctionnent aussi, car en coulisse nil est converti en Optional<Type>.none.

var nilableBool: Bool? // nilableBool has the Optional<Bool>.none value
var nilBoolAsAny: Any = nilableBool // the compiler has all the needed type information from nilableBool

Contrairement à d'autres langues, Swift nil correspond à une valeur concrète. Mais il faut un type avec lequel travailler, pour que le compilateur sache quel Optional<T>.none il doit allouer. Nous pouvons penser que le mot clé fournit la syntaxe sucre.

57
Cristik

D'après Swift Docs: "Any: protocole auquel tous les types sont implicitement conformes."

i.e. typealias Any = protocol<>

Ainsi, lorsque vous déclarez String?, vous pouvez l’imaginer en tant que Optional<String>, où Optional<T> peut être implémenté en tant que:

enum Optional<T> {
  case Some(T), None
}

Et les énumérations sont des types, elles sont donc conformes au protocole Any. Comme indiqué dans les commentaires, nil n'est pas un type (et n'est donc pas conforme à Any, c'est l'absence de valeur (et dans ce cas aucune valeur n'a pas de type).

Les options sont cuites dans la langue et ne sont pas des énumérations régulières, mais il est toujours vrai qu'elles se conforment à Any alors qu'une typeless nil ne le fait pas. 

8
Alex Popov

Pour tous ceux qui cherchent un moyen de vérifier si Any contient nil:

Si l'appelant po value vous indiquera qu'une variable Any contient la valeur nil, la ligne suivante ne fonctionne toujours pas:

if value == nil { ... } // Returns false and generates a warning

Vous devez d'abord convertir cette valeur en une valeur nillable, par exemple lorsque Any contient probablement une Date:

if Optional<Date>.none == value as? Date { ... } // Returns true and no warning

note: Je ne l'ai pas testé, donc aucune garantie, mais la ligne ci-dessus peut également passer si elle est écrite comme suit:

if Optional<Any>.none == value as? Any { ... }
1
Departamento B