Silex v0.47.1 / Docs Source canonique ↗

Partager une identité avec une classe

Une classe définit des objets partagés à durée de vie automatique. Passer, copier ou ranger une valeur de classe conserve l'identité de la même instance.

public class Player {
    private let name:str
    var health:int = 100

    init(name:str) {
        self.name = name
    }

    func damage(amount:int) {
        self.health -= amount
    }

    func description() str {
        return "$(self.name) : $(self.health)"
    }
}

func main() {
    var player = Player("Ada")
    player.damage(10)
    print(player.description())
}

Utilisez var pour une liaison directe de classe ou une collection de classes, même lorsque cette liaison ne change jamais d'instance. Une valeur composée, comme une structure qui conserve une classe dans son stockage privé, peut en revanche rester dans un let : la valeur extérieure reste immuable et l'identité interne demeure partagée.

Interdire la copie profonde d'une identité

Ajoutez nocopy lorsqu'une classe encapsule une ressource native ou externe qui ne peut pas être clonée sûrement :

public nocopy class DeviceHandle {
    private let native:uint
}

Passer, retourner ou affecter ordinairement cette classe continue de partager son identité et sa durée de vie automatique. En revanche, copy est refusé sur la classe comme sur toute structure, enum, optionnel ou collection qui l'atteint. Consultez copier ou transférer une valeur pour distinguer partage, copie profonde et transfert.

Initialiser un champ à sa déclaration

La valeur écrite après = peut être une expression ordinaire : appel de fonction, construction, calcul ou cascade. Elle est évaluée dans l'ordre des champs pour chaque nouvelle instance, avant le corps de son constructeur. Deux instances ne partagent donc pas accidentellement la collection créée par une même expression.

func initial_values() int[] { return [1] }

class Recorder {
    var values:int[] = initial_values()
}

func main() {
    var first = Recorder()
    var second = Recorder()
    first.values.append(2)

    print(first.values.count())  // 2
    print(second.values.count()) // 1
}

Cette forme convient lorsque la valeur ne dépend ni de self ni d'un argument du constructeur : ces noms ne sont pas disponibles dans l'expression. Elle peut notamment préparer un objet par cascade sans imposer un optionnel temporaire.

Construire une instance

Sans constructeur personnalisé, initialisez les champs visibles par leur nom :

class Position {
    var x:int
    var y:int
}

func main() {
    var position = Position(x:2, y:3)
    var target = Position()
        ..x = position.x
        ..y = position.y

    position.x = 4

    print(position.x)
    print(target.x)
    print(target.y)
}

La cascade configure ici une nouvelle instance à partir de position. Le programme affiche 4, 2, puis 3 : modifier ensuite position ne modifie pas target, car chaque appel à Position() crée une identité distincte.

Déclarez init lorsque la construction doit établir un invariant. Dès qu'un constructeur existe, l'initialiseur automatique par champs nommés disparaît. Un champ let sans valeur déclarée et un champ var dont le type ne fournit ni valeur intrinsèque ni construction sans argument restent alors en attente. Le constructeur doit les affecter sur chaque chemin normal. Un champ déjà initialisé peut être lu ; self dans son ensemble ne peut être utilisé qu'une fois tous les champs initialisés.

struct Range {
    let minimum:float
    let maximum:float

    init(minimum:float, maximum:float) {
        self.minimum = minimum
        self.maximum = maximum
    }
}

class Gauge {
    var limits:Range

    init(minimum:float, maximum:float) {
        self.limits = Range(minimum, maximum)
    }
}

Dans init, une affectation self.champ = valeur initialise le champ visé ; elle ne constitue pas un usage prématuré de l’objet complet. Un champ déjà initialisé peut ensuite être lu ou modifié pendant que d’autres restent en attente. Les boucles peuvent donc préparer un stockage dont la taille dépend d’un argument :

class PreparedList {
    var values:int[]
    let requested_count:int

    init(count:int) {
        self.values = []
        var index = 0
        while index < count {
            self.values.append(index)
            index++
        }
        self.requested_count = count
    }
}

Lire un champ encore en attente ou transmettre self comme un objet complet reste interdit. Une affectation présente uniquement dans une boucle qui peut ne jamais s’exécuter ne suffit pas non plus à établir l’initialisation sur tous les chemins ; le champ doit recevoir une valeur sur le chemin de sortie de la boucle.

Les trois formes se complètent : placez une expression sur le champ pour une valeur propre à chaque instance, utilisez init lorsque la valeur dépend de ses arguments, et conservez l'initialiseur automatique par champs nommés lorsque l'appelant doit fournir librement la configuration. Déclarez T? uniquement lorsque l'absence de valeur appartient réellement au modèle ; ce n'est pas un état intermédiaire requis par la construction.

Ajouter des méthodes

Les méthodes reçoivent self implicitement et acceptent les arguments positionnels, nommés et les valeurs par défaut des fonctions ordinaires. Silex déduit si une méthode modifie l'instance ; un tel appel exige un accès var à l'identité partagée.

Les champs, constructeurs et méthodes héritent de la visibilité de la classe. Un modificateur peut la restreindre sans jamais dépasser la visibilité du type. Les portées exactes sont regroupées dans la visibilité des déclarations.

Comparer des identités

== compare l'identité partagée de deux valeurs de classe, pas leurs champs. Les règles de duplication et d'emprunt sont détaillées dans copier ou transférer une valeur et emprunter une valeur.

Le code source ne manipule jamais l'adresse d'un objet, son compteur de références, son allocation ou une libération manuelle. L'héritage, les membres statiques, les spécialisations génériques et le nettoyage possèdent chacun un contrat dédié.

Revenir aux types de données · Spécialiser une classe par héritage