CodeGym /Cours /Swift SELF /if let / guard let et le style « sortie anticipée »

if let / guard let et le style « sortie anticipée »

Swift SELF
Niveau 27, Leçon 1
Disponible

1. Optional et « contrat »

Quand vous ecrivez du code en Swift, le compilateur se comporte comme un coequipier un peu pointilleux, mais tres utile : il ne vous laisse pas « faire semblant » qu’une valeur existe si elle peut ne pas exister. Si une variable a le type String?, cela signifie qu’elle peut contenir une chaine, ou bien nil — et ce n’est pas de la philosophie, mais une partie formelle du type. Par consequent, toute action sur cette valeur doit commencer par une question : « Est-ce qu’elle est bien la ? ».

Dans la vraie vie, cela ressemble souvent a ceci : l’utilisateur n’a rien saisi, readLine() a renvoye nil, et nous essayons soudain de parser un nombre ou d’acceder aux proprietes de la chaine. Et c’est la que deux chemins different s’ouvrent : on peut rendre le code propre et lisible avec if let / guard let, ou bien aller sur le chemin « pourvu que ca compile » et recevoir un jour une surprise (generalement au pire moment).

Petit exemple de ce contre quoi nous nous battons :

import Foundation

let line: String? = readLine()

// INTERDIT : line peut etre nil
// print(line.count) // erreur de compilation

Une erreur de compilation, ce n’est pas « Swift fait son difficile », c’est « Swift vous protege d’une chute a l’execution ».

2. if let : branchement « valeur presente / valeur absente »

if let, c’est le moyen le plus direct de dire : « Si l’optional contient une valeur, recupere-la et donne-la-moi comme un type normal ». Il est ideal lorsque la logique se separe vraiment en deux branches : un scenario si la valeur est presente, et un autre si elle est absente. C’est un peu comme verifier ses poches avant de sortir : si les cles sont la, parfait, on y va ; sinon, on revient et on cherche.

La syntaxe ressemble a ceci :

if let name = maybeName {
    // ici name: String
} else {
    // ici maybeName etait nil
}

Le plus important : la variable name n’existe que dans le bloc if (et c’est tres bien, car il est alors plus difficile de l’utiliser par erreur la ou elle n’est deja plus valide).

Exemple 1 : lire une chaine et afficher sa longueur

import Foundation

let line: String? = readLine()

if let text = line {
    print("Longueur de la chaine : \(text.count)") // par exemple : Longueur de la chaine : 5
} else {
    print("Entree vide (nil)")           // si aucune entree, par exemple EOF
}

Remarquez : nous n’avons pas compare a nil a la main, nous n’avons pas utilise !, nous n’avons pas ecrit de « sorts effrayants ». Nous avons simplement traite honnetement les deux cas.

Exemple 2 : deux optional a la suite — et c’est normal

Scenario tres courant : d’abord readLine() (chaine optionnelle), puis Int(text) (nombre optionnel). Au final, nous avons un « optional sur optional », mais le desempaquetage reste simple :

import Foundation

let line: String? = readLine()

if let text = line, let number = Int(text) {
    print("Vous avez saisi le nombre : \(number)") // par exemple : Vous avez saisi le nombre : 42
} else {
    print("Ce n’est pas un nombre ou il n’y a pas d’entree")
}

Cela se lit comme une phrase : « S’il y a une chaine et si elle peut etre convertie en nombre, alors affiche le nombre ».

Nuance de portee

Si vous avez desempaquete avec if let, la valeur ne vit que dans le bloc. Ce n’est pas une contrainte, mais une protection contre la propagation d’un etat invalide dans le reste du code.

import Foundation

let line: String? = "123"

if let line = line {
    print(line) // 123
}

// print(line) // ici line est a nouveau un String? (variable d’origine)

Oui, le nom coincide (line = line) — c’est possible. Cela s’appelle le shadowing et, dans les petits cas, cela reste propre, mais il ne faut pas en abuser : si vous faites cela tous les deux blocs, votre cerveau commence a soupconner qu’on lui ment.

3. guard let : desempaquetage et « sortie anticipée »

guard let fait a peu pres la meme chose que if let, mais avec une autre philosophie : « Nous eliminons d’abord les mauvais cas, puis nous ecrivons le code en ligne droite ». C’est particulierement utile dans les fonctions ou l’on veut d’abord valider les donnees d’entree, puis travailler tranquillement, sans s’enrouler dans trois couches de if.

Idee centrale : le bloc else de guard doit obligatoirement terminer la portee courante. Autrement dit, il doit contenir return, break, continue ou, tres rarement et avec prudence, fatalError. C’est pourquoi guard est l’outil ideal pour le style de la « sortie anticipée ».

Syntaxe :

guard let value = maybeValue else {
    // nous devons sortir : return / break / continue
    return
}
// ici value n’est plus optional

Exemple 1 : une fonction affiche la longueur de la chaine ou signale une erreur

import Foundation

func printLength(_ text: String?) {
    guard let text = text else {
        print("Pas de chaine (nil)")
        return
    }
    print("Longueur : \(text.count)") // par exemple : Longueur : 4
}

printLength("Swift")
printLength(nil)

Remarquez la beaute : apres guard, le code ne « saute » pas entre les branches, il avance tout droit. Et text plus bas est deja un String, et non un String?.

Exemple 2 : guard let comme « fusible » pour le parsing

import Foundation

func readPositiveInt(from line: String?) -> Int? {
    guard let line = line, let number = Int(line) else {
        return nil
    }
    guard number > 0 else {
        return nil
    }
    return number
}

print(readPositiveInt(from: "10") as Any)  // Optional(10)
print(readPositiveInt(from: "-5") as Any)  // nil
print(readPositiveInt(from: "abc") as Any) // nil

Ici, nous montrons clairement : « si ca n’a pas marche, nous avons renvoye nil ». Ce n’est pas un sujet d’erreurs (throws) — nous y viendrons plus tard. Pour l’instant, notre monnaie, c’est Optional.

Mini-schema : comment penser guard let

flowchart TD
    A[Il y a un optional] --> B{Pas nil ?}
    B -- non --> C[else : sortie anticipée]
    B -- oui --> D[On continue avec une valeur ordinaire]

D’ou cela vient dans le code Swift reel

guard let n’est pas une « construction de cours », mais un vrai style de travail que vous verrez constamment dans le code et dans les patrons standards. Par exemple, dans les scenarios « recuperer les champs obligatoires de l’initialisation et sinon abandonner », on rencontre souvent l’approche guard. Dans un des exemples de Swift Evolution, guard let est justement utilise pour extraire des valeurs obligatoires d’un dictionnaire lors de l’initialisation d’un modele.

4. if let vs guard let : comment choisir

Il est tres facile d’apprendre la syntaxe, mais il est bien plus important d’apprendre a choisir l’outil afin que le code reste lisible. Et ici, un critere simple aide beaucoup : etes-vous en train de brancher la logique, ou de valider l’entree en voulant suivre le « chemin principal » ?

Si vous avez vraiment deux scenarios equivalents (« si c’est la, on affiche un menu ; sinon, on affiche un autre »), if let est plus naturel. Si, en revanche, nil signifie « erreur de donnees d’entree », « rien a faire ensuite » ou « mauvais format », alors guard let rend generalement le code plus simple.

Voici un tableau compact (dans la vie, il vit souvent dans la tete, mais donnons quand meme un pense-bete au cerveau) :

Ce qui est necessaire sur le fond Convient le plus souvent Pourquoi
Deux scenarios (present/absent) ont la meme importance
if let
La branche else fait partie de la logique
D’abord la verification, puis le chemin principal
guard let
Le « mauvais cas » est elimine tout de suite
Reduire l’imbrication dans une fonction
guard let
Le code apres guard est lineaire
Besoin de desempaqueter « sur place » dans un petit bloc
if let
Local et clair

5. Desempaqueter plusieurs optional en chaine

Quand vous commencez a ecrire de petites applications CLI (et c’est justement ce que nous faisons), vous decouvrez vite qu’« une seule valeur optional » est rare. Le plus souvent, il s’agit d’une chaine : il y a une chaine → on la parse en nombre → on trouve un element dans un tableau → on recupere une valeur par cle dans un dictionnaire. Chaque etape peut echouer, et c’est normal : le monde n’est pas oblige d’etre commode pour nos parseurs.

Swift permet de desempaqueter plusieurs optional dans une seule construction, et cela aide beaucoup la lisibilite.

Exemple 1 : if let avec plusieurs desempaquetages

import Foundation

let line: String? = "7"
let numbers = [10, 20, 30, 40, 50, 60, 70]

if let text = line, let index = Int(text), index >= 0, index < numbers.count {
    print("Element : \(numbers[index])") // Element : 70
} else {
    print("Entree incorrecte ou index hors limites")
}

Ici, nous avons melange desempaquetage et verification de conditions. C’est autorise, mais il ne faut pas exagere : si la condition devient trop longue, il vaut mieux extraire une partie dans des variables separees ou dans une fonction separee.

Exemple 2 : la meme chose, mais dans le style « sortie anticipée »

import Foundation

func printNumber(at line: String?, in numbers: [Int]) {
    guard let text = line, let index = Int(text) else {
        print("Il faut un indice entier")
        return
    }
    guard index >= 0, index < numbers.count else {
        print("Indice hors limites")
        return
    }
    print("Element : \(numbers[index])")
}

printNumber(at: "2", in: [10, 20, 30]) // Element : 30
printNumber(at: "99", in: [10, 20, 30]) // Indice hors limites

Ce style s’adapte tres bien a la croissance du code : vous ajoutez de nouvelles verifications, mais le code principal « en bas » reste propre.

6. Pratique : if let et guard let dans une application CLI

Quand on apprend, il est bien plus utile de ne pas « simplement desempaqueter un optional », mais de le desempaqueter dans le contexte de quelque chose de vivant. Continuons notre application CLI : nous avons des livres et nous voulons des commandes comme add, list, find.

Pour l’instant, pas de fichiers, pas de reseau et pas de throws : seulement de la saisie, du parsing et une gestion propre des valeurs absentes.

Modele et donnees de test

import Foundation

struct Book {
    let id: Int
    let title: String
}

var books: [Book] = [
    Book(id: 1, title: "Swift Basics"),
    Book(id: 2, title: "Optional — pas effrayant")
]

Exemple 1 : la recherche d’un livre par id renvoie Book?, et c’est honnete

import Foundation

func findBook(byID id: Int, in books: [Book]) -> Book? {
    for book in books {
        if book.id == id { return book }
    }
    return nil
}

Utilisons maintenant if let — parce que nous avons besoin de deux scenarios : soit le livre est trouve (on l’affiche), soit il ne l’est pas (on le signale).

import Foundation

let idToFind = 2
let result = findBook(byID: idToFind, in: books)

if let book = result {
    print("Trouve : \(book.title)") // Trouve : Optional — pas effrayant
} else {
    print("Livre avec id=\(idToFind) non trouve")
}

Exemple 2 : lecture d’une commande depuis la console et sortie anticipée sur mauvaise entree

Supposons que la commande find doive ressembler a ceci : find 2. C’est-a-dire deux jetons : le nom de la commande et l’id. Si l’utilisateur a saisi quelque chose d’etrange, nous ne voulons pas planter, nous voulons dire proprement « je n’ai pas compris ».

import Foundation

func handleFindCommand(_ line: String?, books: [Book]) {
    guard let line = line else {
        print("Aucune entree")
        return
    }

    let parts = line.split(separator: " ")
    guard parts.count == 2 else {
        print("Format : find <id>")
        return
    }

    guard let id = Int(parts[1]) else {
        print("id doit etre un nombre")
        return
    }

    if let book = findBook(byID: id, in: books) {
        print("Trouve : \(book.title)")
    } else {
        print("Non trouve")
    }
}

handleFindCommand("find 1", books: books) // Trouve : Swift Basics
handleFindCommand("find abc", books: books) // id doit etre un nombre

On voit ici la « repartition des roles » : guard let et les conditions guard servent a valider la forme de la commande, tandis que if let sert a la logique « trouve / non trouve ».

Exemple 3 : boucle de lecture des commandes et guard avec break

Si l’utilisateur termine la saisie (par exemple, EOF), readLine() renverra nil. Dans une boucle, c’est un excellent cas pour une « sortie anticipée », mais pas avec return : avec break.

import Foundation

while true {
    let line = readLine()

    guard let line = line else {
        print("A bientot !") // A bientot !
        break
    }

    print("Vous avez saisi : \(line)")
}

C’est un patron tres typique pour les petites utilitaires CLI : guard rend le « bas » de la boucle propre, car vous ne propagez pas Optional plus loin.

7. Erreurs typiques

Erreur n 1 : utiliser ! au lieu d’un desempaquetage simplement parce que c’est plus court.
Quand un developpeur decouvre seulement les valeurs optionnelles, il est tentant d’ecrire value! et de continuer a travailler avec le resultat comme s’il s’agissait d’une valeur ordinaire. Le code compile, le programme fonctionne meme — jusqu’au premier cas ou la valeur sera nil. A ce moment-la, l’application se terminera brutalement.

Erreur n 2 : construire des « poupees russes » de if let.
Image tres courante dans le code debutant : if let a l’interieur de if let, puis encore un autre if. Chaque nouvelle condition augmente l’imbrication, et apres quelques niveaux, le code devient difficile a lire et encore plus difficile a modifier. Toute retouche oblige a verifier soigneusement tous les niveaux d’imbrication. Dans ce genre de situation, il vaut souvent mieux utiliser guard let : il permet de verifier les conditions au debut et de laisser le code principal sans retraits superflus.

Erreur n 3 : oublier la portee de la variable desempaquetee.
La variable creee dans un if let n’existe que dans ce bloc. Les debutants ecrivent souvent if let name = userName { ... }, puis essaient d’utiliser name apres la parenthese fermante de if. Le compilateur indique qu’une telle variable n’existe pas, et cela peut etre deroutant. Mais la regle est simple : la valeur desempaquetee ne vit que la ou il est garanti qu’elle existe.

Erreur n 4 : utiliser guard sans faire de sortie anticipée.
Il arrive que l’on ecrive une construction du type guard let value = optional else { print(...) }. Le compilateur n’autorisera pas un tel code — et c’est normal. guard n’a de sens que lorsque, en cas d’echec de la condition, l’execution s’arrete vraiment : via return, break, continue ou throw. Si, apres la verification, le programme doit continuer a fonctionner dans la meme branche, alors c’est un cas pour if, et non pour guard.

Erreur n 5 : considerer nil comme une situation exceptionnelle.
Beaucoup d’operations en Swift peuvent renvoyer nil, et c’est un resultat parfaitement normal. Par exemple, la lecture de l’entree utilisateur, la conversion d’une chaine en nombre ou la recherche d’un element dans un dictionnaire. Quand un developpeur considere nil comme « quelque chose a mal tourne », il commence a ecrire du code defensif avec des verifications excessives et une logique confuses. Il est bien plus juste de voir nil comme une branche normale du scenario, que le programme doit traiter calmement.

1
Mission
Swift SELF, niveau 27, leçon 1
Bloqué
Chaîne et longueur
Chaîne et longueur
1
Mission
Swift SELF, niveau 27, leçon 1
Bloqué
Charge double
Charge double
1
Mission
Swift SELF, niveau 27, leçon 1
Bloqué
Âge du client
Âge du client
1
Mission
Swift SELF, niveau 27, leçon 1
Bloqué
Commande des capitales
Commande des capitales
Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION