swiftlanguage-lawyer

Why can I call `Sequence.map()` without `try` though its signature has `throws`?


The signature of Sequence.map() is

func map<T, E>(_ transform: (Self.Element) throws(E) -> T) throws(E) -> [T] where E : Error

Clearly this is a throwing function.

However, when I specify a non-throwing function as its argument, it seems I can call map() without try:

let l = [1, 2, 3].map { $0 * 2 }
print(l) //=> [2, 4, 6]

Similarly, the signature of RangeReplaceableCollection.filter() is

func filter(_ isIncluded: (Self.Element) throws -> Bool) rethrows -> Self

but I can call filter() without try if the argument will never throw:

let l = [1, 2, 3].filter { $0 % 2 == 1 }
print(l) //=> [1, 3]

The question is "why" or "how does it work under the hood"? Where is this behavior defined in the language reference? Because I want a strict understanding about the language, I added #language-lawyer tag.


Solution

  • This is a typed throws. map throws errors of type E, which is one of the generic type parameters. You don't need to try because E is inferred to be Never, and that is because the closure you passed to map is not a throwing closure.

    If you had passed a closure that can throw, you will need to add try to the filter call too.

    // the first 'try' is necessary
    try [1, 2, 3].map { try someTransform($0) }
    

    The language reference says:

    Just like you can write a function that never returns with a return type of Never, you can write a function that never throws with throws(Never):

    func nonThrowingFunction() throws(Never) {
      // ...
    }
    

    This function can’t throw because it’s impossible to create a value of type Never to throw.

    in this section.

    I think the Swift Evolution proposal for this feature puts this more clearly:

    Typed throws generalizes over both untyped throws and non-throwing functions. A function specified with any Error as its thrown type:

    func throwsAnything() throws(any Error) { ... }
    

    is equivalent to untyped throws:

    func throwsAnything() throws { ... }
    

    Similarly, a function specified with Never as its thrown type:

    func throwsNothing() throws(Never) { ... }
    

    is equivalent to a non-throwing function:

    func throwsNothing() { }
    

    filter is marked rethrows, not throws. This section in the language reference talks about rethrowing functions

    A function or method can be declared with the rethrows keyword to indicate that it throws an error only if one of its function parameters throws an error. These functions and methods are known as rethrowing functions and rethrowing methods.

    Again, you don't need to try for filter because the closure you pass to it is not a throwing closure. If you had passed a closure that can throw, you will need to add try to the filter call too.

    // the first 'try' is necessary
    try [1, 2, 3].filter { try someCondition($0) }