¿Cuáles son las diferencias entre lanzamientos y relanzamientos en Swift?

81

Después de buscar algunas referencias para resolverlo, desafortunadamente, no pude encontrar una descripción útil y simple para comprender las diferencias entre throwsy rethrows. Es un poco confuso tratar de entender cómo debemos usarlos.

Mencionaría que estoy familiarizado con el -predeterminado- throwscon su forma más simple para propagar un error, de la siguiente manera:

enum CustomError: Error {
    case potato
    case tomato
}

func throwCustomError(_ string: String) throws {
    if string.lowercased().trimmingCharacters(in: .whitespaces) == "potato" {
        throw CustomError.potato
    }

    if string.lowercased().trimmingCharacters(in: .whitespaces) == "tomato" {
        throw CustomError.tomato
    }
}

do {
    try throwCustomError("potato")
} catch let error as CustomError {
    switch error {
    case .potato:
        print("potatos catched") // potatos catched
    case .tomato:
        print("tomato catched")
    }
}

Hasta ahora todo bien, pero el problema surge cuando:

func throwCustomError(function:(String) throws -> ()) throws {
    try function("throws string")
}

func rethrowCustomError(function:(String) throws -> ()) rethrows {
    try function("rethrows string")
}

rethrowCustomError { string in
    print(string) // rethrows string
}

try throwCustomError { string in
    print(string) // throws string
}

lo que sé hasta ahora es que, al llamar a una función throws, debe ser manejada por a try, a diferencia de rethrows. ¡¿Y qué?! ¿Cuál es la lógica que debemos seguir al decidir utilizar throwso rethrows?

Ahmad F
fuente

Respuestas:

184

De "Declaraciones" en el libro Swift:

Relanzamiento de funciones y métodos

Una función o método se puede declarar con la rethrowspalabra clave para indicar que arroja un error solo si uno de sus parámetros de función arroja un error. Estas funciones y métodos se conocen como funciones de relanzamiento y métodos de relanzamiento . Las funciones y métodos de lanzamiento deben tener al menos un parámetro de función de lanzamiento.

Un ejemplo típico es el mapmétodo:

public func map<T>(_ transform: (Element) throws -> T) rethrows -> [T]

Si mapse llama con una transformación que no arroja, no genera un error en sí mismo y se puede llamar sin try:

// Example 1:

let a = [1, 2, 3]

func f1(n: Int) -> Int {
    return n * n
}

let a1 = a.map(f1)

Pero si mapse llama con un cierre de lanzamiento, entonces él mismo puede lanzar y debe ser llamado con try:

// Example 2:

let a = [1, 2, 3]
enum CustomError: Error {
    case illegalArgument
}

func f2(n: Int) throws -> Int {
    guard n >= 0 else {
        throw CustomError.illegalArgument
    }
    return n*n
}


do {
    let a2 = try a.map(f2)
} catch {
    // ...
}
  • Si mapse declarara como en throwslugar de, rethrowsentonces tendría que llamarlo tryincluso en el ejemplo 1, lo cual es "inconveniente" y aumenta el código innecesariamente.
  • Si mapse declararon sin, throws/rethrowsentonces no podría llamarlo con un cierre de lanzamiento como en el ejemplo 2.

Lo mismo es cierto para otros métodos de la librería estándar Swift que tienen parámetros de la función: filter(), index(where:), forEach()y muchos muchos más.

En tu caso,

func throwCustomError(function:(String) throws -> ()) throws

denota una función que puede arrojar un error, incluso si se llama con un argumento que no arroja, mientras que

func rethrowCustomError(function:(String) throws -> ()) rethrows

denota una función que arroja un error solo si se llama con un argumento arrojadizo.

Hablando en términos generales, rethrowses para funciones que no arrojan errores "por sí mismas", sino sólo errores "reenviados" desde sus parámetros de función.

Martín R
fuente
36
¡La última oración es dorada!
Klaas
1
@Honey: La última oración de la respuesta es cómo la resumiría.
Martin R
Sí, eso parece mejor. ¿Sería correcto decir que los rethrows solo se usan con cierres, aparte de que no son necesarios?
Miel
3
@Honey: No entiendo completamente lo que quieres decir. rethrowssolo se usa con funciones que toman parámetros de función que podrían arrojar.
Martin R
¿Cómo sería la sintaxis si hiciera ambas cosas? 🤔🤔 ¿Serían "relanzamientos" ??
Kautsya Kanu
14

Solo para agregar algo junto con la respuesta de Martin. Una función de no lanzar con la misma firma que una función de lanzar se considera una sub-typefunción de lanzamiento. Es por eso que rethrows puede determinar cuál es y solo requiere trycuando el func param también lanza, pero aún acepta la misma firma de función que no lanza. Es una forma conveniente de tener que usar solo un bloque do try cuando se lanza el parámetro func, pero el otro código de la función no arroja un error.

JustinM
fuente