Il y a 4 ans -
Temps de lecture 3 minutes
Pépite 15 – Le saviez-vous ? Les secrets de Swift.Result !
Longtemps réclamé, déjà implémenté par de nombreuses bibliothèques tierces, Result
fait enfin son apparition dans la standard library de Swift 5.0.
Si de nombreux articles se sont déjà attardés sur ses fonctionnalités, peu ont expliqué les ajouts qu’il a nécessité dans la standard library. Petit tour d’horizon des secrets d’Avengers: EndGame de Result
.
Un Result peut en cacher un autre
Comme expliqué auparavant, Result
était déjà implémenté dans plusieurs bibliothèques majeures. Dès lors, beaucoup de personnes avaient peur de voir leur code ne plus compiler avec Swift 5.0, Swift.Result
risquant de rentrer en conflit avec les bibliothèques tierces.
C’est pour éviter cet écueil que la Core Team a introduit la notion de type shadowing : dorénavant, n’importe quel type ayant le même nom qu’un type de la standard library aura précédence sur celui-ci.
[py]import Alamofire
// Equivalent à func doSomething() -> Alamofire.Result<Int>
func doSomething() -> Result<Int> { }[/py]
Le revers de la médaille est qu’il peut être parfois difficile de voir à la première lecture si le type utilisé est celui de la standard library ou d’une bibliothèque.
[py]import Foundation
import RxSwift
// Pas d’import définissant un Result
// Donc ici Result == Swift.Result
func doSomething() -> Result<Int, Error> { }[/py]
Error en votre faveur
En Swift, il est (pour le moment) impossible d’utiliser un protocole comme type concret d’un générique ayant des contraintes.
[cpp]struct Request<Body: Encodable> {
let body: Encodable?
}
struct InfinityStone: Encodable {
…
}
// KO => Body == Encodable (protocole)
// Using ‘Encodable’ as a concrete type conforming to protocol ‘Encodable’ is not supported
let request: Request<Encodable> = Request<InfinityStone>()
// OK => Body == InfinityStone (type concret)
let request = Request<InfinityStone>()[/cpp]
Pourtant le code suivant compile :
[cpp]// OK => Success == Int, Error == Error (protocole)
let result = Result<Int, Error>(2)[/cpp]
Et alors même que Result
est défini comme suit dans la standard library : struct Result<Success, Failure: Error> { }
.
La logique aurait donc voulu que nous ayons un message « Using ‘Error’ as a concrete type conforming to protocol ‘Error’ is not supported ».
(swift evolution 235) nous donne l’explication : Error
conforme dorénavant à… Error
!
Cet ajout à la standard library permet d’utiliser Result
sans avoir à préciser des Error
concrets, ce que l’on souhaite dans 90% des cas.
[cpp]let snapResult: Result<Void, Error> = Result { throw SnapError.infinityStoneMissing }[/cpp]
Dans les 10% restants, le code à écrire sera quelque peu plus complexe :
[cpp]// KO => ‘Result<Void, SnapError>’ is not convertible to ‘Result<Void, Error>’
let snapResult = Result<Void, SnapError> { throw SnapError.infinityStoneMissing }
// OK
let snapResult = Result<Void, SnapError>.failure(.infinityStoneMissing)
// KO => cannot convert value of type ‘Result<Void, SnapError>’ to specified type ‘Result<Void, Error>’
let anySnapResult: Result<Void, Error> = snapResult
// OK
let anySnapResult: Result<Void, Error> = snapResult.mapError { $0 as Error }[/cpp]
Voila, vous en savez un peu plus sur Result
. Si c’est indéniablement un type utile, son insertion dans la standard library aura nécessité de procéder à quelques ajustements pour nous permettre de l’utiliser facilement. Pour les plus curieux d’entre vous, vous pouvez lire le thread [Revised] SE-0235: Add Result to the Standard Library pour connaître la genèse du projet, ou bien l’article The power of Result types in Swift pour apprendre à utiliser Result
.
Commentaire