Silex v0.47.1 / Docs Source canonique ↗

Contrôler un accès avec une propriété

Une propriété conserve la déclaration familière d'un champ, puis place ses accesseurs dans un bloc. let expose une lecture seule. var expose une lecture et une écriture, même lorsque seul son getter est écrit.

struct Rectangle {
    let width:int
    let height:int

    let area:int {
        get { return self.width * self.height }
    }
}

struct Thermometer {
    var raw:int

    var celsius:int {
        get { return self.raw }
        set(value) { self.raw = value }
    }
}

Rectangle(width:6, height:7).area appelle le getter. L'affectation exacte thermometer.celsius = 21 appelle le setter avec 21 dans son paramètre explicite value. Un setter ne retourne aucune valeur et ne constitue pas un site de propagation pour try ou Result : une écriture susceptible d'échouer doit rester une méthode explicite.

get et set sont contextuels. Ils restent disponibles comme identifiants ordinaires hors du bloc d'une propriété.

Initialiser le stockage une seule fois

Une propriété accepte une valeur déclarée avant son bloc d'accesseurs. Cette expression initialise son stockage une fois pour chaque construction, sans créer un second champ :

struct Graph {
    var name:str = "" {
        get {
            if self.name == "" { return "Tata" }
            return self.name
        }
    }
}

func main() {
    var graph = Graph()
    print(graph.name) // Tata
    graph.name = "toto"
    print(graph.name) // toto
}

Comme name est déclaré avec var, le compilateur fournit ici le setter ordinaire qui range sa valeur dans le stockage de name. Déclarer set(value) { ... } remplace ce comportement lorsque l'écriture doit être contrôlée. Une propriété let ne reçoit jamais de setter implicite.

Dans ses propres accesseurs, le nom de la propriété désigne son stockage caché optionnel. Une comparaison entre T? et T, telle que self.name == "", promeut la valeur en optionnel avant de comparer. Le retour du getter extrait le stockage vers le type public ; sans valeur déclarée, il faut donc garantir son initialisation avant de le retourner. Retourner un stockage encore absent interrompt proprement le programme avec le diagnostic property 'name' returned before its storage was initialized.

Distinguer la mutabilité du calcul

Un getter d'instance ne peut pas modifier self, y compris pour initialiser paresseusement son stockage. Utilisez la valeur déclarée ci-dessus pour une initialisation propre à chaque instance. Cette règle rend toute lecture valide indépendamment du fait que le récepteur ait été lié avec let ou var : le lecteur n'a pas à connaître l'implémentation de la propriété pour savoir si value.property est autorisé. Un compteur d'accès ou un cache attaché à l'instance doit donc passer par une méthode explicite.

Un setter peut modifier self. Un var possède son setter standard par défaut ; un bloc set explicite le remplace. Une propriété let ne peut déclarer que get.

Une affectation composée lit puis écrit la propriété exactement une fois de chaque côté :

thermometer.celsius += 4

équivaut, pour les effets observables, à un appel du getter suivi d'un appel du setter. La valeur du récepteur n'est pas réévaluée inutilement.

Modifier une valeur obtenue par lecture

Seule l'affectation de la propriété elle-même appelle son setter :

player.position = Position(x:10) // appelle le setter de position

Une sélection supplémentaire travaille sur le résultat du getter. Si ce résultat est une structure, il s'agit d'une valeur temporaire et sa mutation est refusée :

player.position.x = 10 // refusé si Position est une structure

Il faut alors construire puis affecter une nouvelle position. Si le getter retourne une classe, le résultat conserve au contraire son identité partagée : player.position.x = 10 modifie cette instance sans rappeler le setter de position.

Initialiser paresseusement un membre statique

Une propriété possède un stockage caché distinct de son type public. Dans ses propres accesseurs seulement, son nom désigne ce stockage optionnel. Un getter statique peut ainsi effectuer une première initialisation sans déclarer un second champ :

class Data {
    static let instance:Data {
        get {
            if Data.instance == null {
                Data.instance = Data()
            }
            return Data.instance
        }
    }

    private init() {}
}

À l'extérieur du getter, Data.instance a le type Data et appelle le getter. À l'intérieur, le même nom permet de tester puis d'initialiser le stockage. Le retour l'extrait vers le type public ; quitter le getter avant son initialisation produit donc le même diagnostic d'exécution contrôlé. Les getters statiques sont sérialisés : deux accès concurrents ne peuvent pas initialiser ce stockage simultanément.

Initialisation, réflexion et protocoles

Une propriété calculée n'est pas un champ. Elle ne participe jamais à l'initialiseur automatique par champs nommés. reflect(value).fields contient le stockage visible, reflect(value).properties les propriétés visibles et reflect(value).methods les méthodes visibles ; les accesseurs générés ne sont pas exposés comme méthodes.

Un protocole exprime le contrat d'une propriété sans introduire de stockage :

protocol Named {
    name:str { get }
}

protocol Renamable {
    name:str { get set }
}

Un champ let ou var satisfait une exigence { get } du même nom et du même type. Un champ var ou une propriété var satisfait { get set }, que son setter soit implicite ou explicite. Les mots let et var restent interdits dans un protocole : celui-ci décrit un accès, jamais la représentation qui le fournit.

Revenir aux types de données · Définir un contrat avec un protocole