dispatch_sync and rethrows

Hi,


Is there any way to declare `rethrows` a function that uses `dispatch_sync`?


For example:


func perform_sync(queue: dispatch_queue_t, block: () throws -> Void) rethrows {
    var blockError: ErrorType? = nil
    dispatch_sync(queue) {
        do {
            try block()
        } catch {
            blockError = error
        }
    }
    if let blockError = blockError {
        throw blockError
    }
}


The code above has the compiler complain with the message "'rethrows' function may only throw by calling a parameter function".


That's quite unfortunate. If it were possible, one could call this `perform_sync` function without `try`:


try perform_sync(queue) {  // try, obviously
     try dangerousFunction()
}
perform_sync(queue) {      // Look, Ma! No try!
     safe_function()
}


Of course, such rethrowing is unsafe: it requires the cooperation of the developer. Well, I'd happily welcome an explicit unsafe construct in the Swift language that would allow me to do that.


Thanks in advance if you have any clue.

Answered by Ransak in 24735022

I was able to do it in a round about way:


func testperform_sync(){  //call this function to test everything
    enum Error : ErrorType {
        case Test1;
        case Test2;
    }
   
    var testfunc = {()  -> Void in
        print("Inside safe testfunc closure")
    }
   
    func rethrow(myerror:ErrorType) throws ->()
    {
        print("Inside rethrowing function")
        throw myerror
    }
   
    var myclosure = {() throws -> Void in
        let error: Error = Error.Test1
        print("Inside unsafe testfunc closure")
        throw error
    }
   
    func perform_sync(queue: dispatch_queue_t, block: () throws -> Void,
         block2:((myerror:ErrorType) throws -> ()) ) rethrows {
        var blockError: ErrorType? = nil
        dispatch_sync(queue) {
            do {
                try block()
            } catch {
                blockError = error
                print("This was blocks error: \(blockError)")
            }
        }
        if let blockError = blockError{
            print ("Block \(blockError) will be sent to block2")
            try block2(myerror: blockError)
        }
    }
    print("TEST WITH SAFE FUNCTION AND PLACEHOLDER PRINT")
    perform_sync(dispatch_get_global_queue(Int(QOS_CLASS_USER_INITIATED.rawValue),
        0),
        block: (testfunc), block2: print)
   
    print("\n\n\nTEST WITH A PRE-DEFINED UNSAFE CLOSURE")
    do {try perform_sync(dispatch_get_global_queue(Int(QOS_CLASS_USER_INITIATED.rawValue), 0),
        block: myclosure , block2:rethrow)
    }catch{
        print("Error transferred from block to block2 is \(error)")
    }
   
    print("\n\n\nTEST WITH AN UNSAFE CLOSURE")
    do {try perform_sync(dispatch_get_global_queue(Int(QOS_CLASS_USER_INITIATED.rawValue), 0),
        block: {throw Error.Test2} ,
        block2:rethrow)
    }catch{
        print("Error transferred from block to block2 is \(error)")
    }
   
}

It's not using "rethrows", but it makes your code execute, and, I believe, safe:


func perform_sync(queue: dispatch_queue_t, block: () throws -> Void) throws {
    var blockError: ErrorType? = nil
    dispatch_sync(queue) {
        do {
            try block()
        } catch {
            blockError = error
        }
    }
    if let blockError = blockError {
        throw blockError
    }
}

func perform_sync(queue: dispatch_queue_t, block: () -> Void) {
    dispatch_sync(queue) {
        block()
    }
}

The goal was really to have `rethrows`, because a rethrowing function whose argument does not throw can be called without `try`.


func wrap(() -> throws Void) rethrows { ... }
try wrap() { try danger() }     // needs try before wrap
wrap() { safe() }               // does not need try before wrap

Just overload dispatch_sync with a version that throws for now, since rethrows essentially does that anyway. I believe I saw that this has already been reported as an enhancement request, for dispatch_sync specifically and the standard library in general, though I can't recall where I saw that so you could report it yourself.

The problem is not with the dispatch_sync but with your function itself:

if let blockError = blockError { 
    throw blockError 
}

It does not just rethrow but throw's itself. This is why it has to be marked as throws instead of rethrows.

Remove These 3 lines and rethrows works just fine.

But that is what my code does, right? When you use safe(), you don't have to use try.

Yes, you are right, and your answer is valid. I was already doing that, actually : define two versions of the function, one that eats a throwing closure, another one that eats a non-throwing closure, and let the overloading engine of Swift catch the correct one.


Still there is an issue :-) : the library API is polluted with an extra function that deserves no real purpose, and makes autocompletion confusing.


No, the goal is really to have a `rethrows` clause.


And that's impossible for now, OK. And that's a real usability issue.

... and the error would be lost, wouldn't it?

Yes the error would be lost.


But I just figured out, you can overload the function. Providing one which takes a throwing closure and one which takes a non throwing. Both with the same name.

enum Error : ErrorType {
    case Test;
}
func perform_sync(queue: dispatch_queue_t, block: () -> Void) {
    dispatch_sync(queue) {
        block()
    }
}
func perform_sync(queue: dispatch_queue_t, block: () throws -> Void) throws {
    var blockError: ErrorType? = nil
    dispatch_sync(queue) {
        do {
            try block()
        } catch {
            blockError = error
        }
    }
    if let blockError = blockError {
        throw blockError
    }
}
try perform_sync(dispatch_get_main_queue())  {
    throw Error.Test
}
perform_sync(dispatch_get_main_queue())  {
    print("a")
}
Accepted Answer

I was able to do it in a round about way:


func testperform_sync(){  //call this function to test everything
    enum Error : ErrorType {
        case Test1;
        case Test2;
    }
   
    var testfunc = {()  -> Void in
        print("Inside safe testfunc closure")
    }
   
    func rethrow(myerror:ErrorType) throws ->()
    {
        print("Inside rethrowing function")
        throw myerror
    }
   
    var myclosure = {() throws -> Void in
        let error: Error = Error.Test1
        print("Inside unsafe testfunc closure")
        throw error
    }
   
    func perform_sync(queue: dispatch_queue_t, block: () throws -> Void,
         block2:((myerror:ErrorType) throws -> ()) ) rethrows {
        var blockError: ErrorType? = nil
        dispatch_sync(queue) {
            do {
                try block()
            } catch {
                blockError = error
                print("This was blocks error: \(blockError)")
            }
        }
        if let blockError = blockError{
            print ("Block \(blockError) will be sent to block2")
            try block2(myerror: blockError)
        }
    }
    print("TEST WITH SAFE FUNCTION AND PLACEHOLDER PRINT")
    perform_sync(dispatch_get_global_queue(Int(QOS_CLASS_USER_INITIATED.rawValue),
        0),
        block: (testfunc), block2: print)
   
    print("\n\n\nTEST WITH A PRE-DEFINED UNSAFE CLOSURE")
    do {try perform_sync(dispatch_get_global_queue(Int(QOS_CLASS_USER_INITIATED.rawValue), 0),
        block: myclosure , block2:rethrow)
    }catch{
        print("Error transferred from block to block2 is \(error)")
    }
   
    print("\n\n\nTEST WITH AN UNSAFE CLOSURE")
    do {try perform_sync(dispatch_get_global_queue(Int(QOS_CLASS_USER_INITIATED.rawValue), 0),
        block: {throw Error.Test2} ,
        block2:rethrow)
    }catch{
        print("Error transferred from block to block2 is \(error)")
    }
   
}

But what is the advantage of using rethrows over my example? I both cases you have a function with a single name which accepts both a throwing closure and a non-throwing closure and is throwing or not-throwing itself depending on the passed closure.

This is genius ! Here is the short form of your idea:


// Only one function exposed, declared as rethrows.
func perform_sync(queue: dispatch_queue_t, block: () throws -> Void) rethrows {
    func rethrow(myerror:ErrorType) throws ->()
    {
        throw myerror
    }
    func perform_sync_impl(queue: dispatch_queue_t, block: () throws -> Void,
        block2:((myerror:ErrorType) throws -> ()) ) rethrows {
            var blockError: ErrorType? = nil
            dispatch_sync(queue) {
                do {
                    try block()
                } catch {
                    blockError = error
                }
            }
            if let blockError = blockError {
                try block2(myerror: blockError)
            }
    }
    try perform_sync_impl(queue, block: block, block2: rethrow)
}

do {
    // try required for throwing closure, obviously
    try perform_sync(dispatch_get_global_queue(Int(QOS_CLASS_USER_INITIATED.rawValue), 0)) {
        throw NSError(domain: "foo", code: 0, userInfo: nil)
    }
} catch {
    print("caught \(error)")
}

// try not required for non-throwing closure, yeah :-)
perform_sync(dispatch_get_global_queue(Int(QOS_CLASS_USER_INITIATED.rawValue), 0)) {
    print("hello")
}

When you eat your own code, it's not a big issue to have two functions. However, when you design a library, it is an issue. Because each added function makes the API more complex.

That's why you should probably just provide the overload for dispatch_sync and then build your various functions on top of that, as I mentioned above. Then you'll be able to remove the overload if/when things are updated so that dispatch_sync is rethrows.

Well, Ransak could find a solution, so what I should do I guess is thank him a lot, and use his rethrowing wrapper of dispatch_sync (in a gist: https://gist.github.com/groue/f2ecc98b8301ed63d843)

dispatch_sync and rethrows
 
 
Q