Best way to handle escaping function that might throw


(Howard Lovatt) #1

Hi,

If I have an escaping function that I store and then call, I need to
declare the calling function as throwing, not rethrowing. EG:

    struct FStore {
        let f: () throws -> Void
        init(f: @escaping () throws -> Void) { self.f = f }
        func call() throws { try f() } // Can't put rethrows here - have to
use throws
    }
Is there a better solution?

Thanks for any suggestions,

  -- Howard.


(Jacob Bandes-Storch) #2

Moving to swift-users list.

No, there's no way to do this today. The point of rethrows is that within
one call site, "f(block)" can be treated as throwing if the block throws,
or not throwing if the block doesn't throw. In your example, once the
FStore object is constructed, the information about the original passed-in
function is lost, so the caller has no way to know whether call() can throw
or not.

If this *were* possible, the information would somehow need to be encoded
in the type system when creating FStore(f: block). That would require
something like dependent typing, or generic-param-based-rethrows, e.g.

struct FStore<T: () throws -> Void> { // made-up syntax
    let f: T
    func call() rethrows(T) { try f() } // throws-ness of this function
depends on throws-ness of T
}

···

On Mon, Jan 9, 2017 at 9:21 PM, Howard Lovatt via swift-evolution < swift-evolution@swift.org> wrote:

Hi,

If I have an escaping function that I store and then call, I need to
declare the calling function as throwing, not rethrowing. EG:

    struct FStore {
        let f: () throws -> Void
        init(f: @escaping () throws -> Void) { self.f = f }
        func call() throws { try f() } // Can't put rethrows here - have
to use throws
    }
Is there a better solution?

Thanks for any suggestions,

  -- Howard.

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(TJ Usiyan) #3

I suggest using an enum to represent success and failure. It's usually
referred to as `Result`. It might seem weird for the closure that you
presented as an example, since it would be Result<Void>, but it properly
captures the possibility of having thrown.

enum UselessError : Swift.Error {
    case somethingBadHappened
}

enum Result<T> {
    case success(T)
    case failure(Swift.Error)

    init(throwingClosure: (Void) throws -> T) {
        do {
            self = try .success(throwingClosure())
        } catch {
            self = .failure(error)
        }
    }
}

func burnItDown() throws -> Void {
    throw UselessError.somethingBadHappened
}

Result(throwingClosure: burnItDown)
···

On Mon, Jan 9, 2017 at 10:28 PM, Jacob Bandes-Storch via swift-users < swift-users@swift.org> wrote:

Moving to swift-users list.

No, there's no way to do this today. The point of rethrows is that within
one call site, "f(block)" can be treated as throwing if the block throws,
or not throwing if the block doesn't throw. In your example, once the
FStore object is constructed, the information about the original passed-in
function is lost, so the caller has no way to know whether call() can throw
or not.

If this *were* possible, the information would somehow need to be encoded
in the type system when creating FStore(f: block). That would require
something like dependent typing, or generic-param-based-rethrows, e.g.

struct FStore<T: () throws -> Void> { // made-up syntax
    let f: T
    func call() rethrows(T) { try f() } // throws-ness of this function
depends on throws-ness of T
}

On Mon, Jan 9, 2017 at 9:21 PM, Howard Lovatt via swift-evolution < > swift-evolution@swift.org> wrote:

Hi,

If I have an escaping function that I store and then call, I need to
declare the calling function as throwing, not rethrowing. EG:

    struct FStore {
        let f: () throws -> Void
        init(f: @escaping () throws -> Void) { self.f = f }
        func call() throws { try f() } // Can't put rethrows here - have
to use throws
    }
Is there a better solution?

Thanks for any suggestions,

  -- Howard.

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-users mailing list
swift-users@swift.org
https://lists.swift.org/mailman/listinfo/swift-users


(Howard Lovatt) #4

Another possibility, other than generics, would be to drop rethrows all
together and have the compiler infer if a throw is possible or not,
including:

    struct FStore {
        let f: () throws -> Void
        func call() throws { try f() }
    }

The compiler can make two versions, one if f can throw and one if it
definitely doesn't.

Just a thought.

···

On Tue, 10 Jan 2017 at 4:29 pm, Jacob Bandes-Storch <jtbandes@gmail.com> wrote:

Moving to swift-users list.

No, there's no way to do this today. The point of rethrows is that within
one call site, "f(block)" can be treated as throwing if the block throws,
or not throwing if the block doesn't throw. In your example, once the
FStore object is constructed, the information about the original passed-in
function is lost, so the caller has no way to know whether call() can throw
or not.

If this *were* possible, the information would somehow need to be encoded
in the type system when creating FStore(f: block). That would require
something like dependent typing, or generic-param-based-rethrows, e.g.

struct FStore<T: () throws -> Void> { // made-up syntax
    let f: T
    func call() rethrows(T) { try f() } // throws-ness of this function
depends on throws-ness of T
}

On Mon, Jan 9, 2017 at 9:21 PM, Howard Lovatt via swift-evolution < > swift-evolution@swift.org> wrote:

Hi,

If I have an escaping function that I store and then call, I need to
declare the calling function as throwing, not rethrowing. EG:

    struct FStore {
        let f: () throws -> Void
        init(f: @escaping () throws -> Void) { self.f = f }
        func call() throws { try f() } // Can't put rethrows here - have
to use throws
    }
Is there a better solution?

Thanks for any suggestions,

  -- Howard.

_______________________________________________

swift-evolution mailing list

swift-evolution@swift.org

https://lists.swift.org/mailman/listinfo/swift-evolution

--

-- Howard.


(Howard Lovatt) #5

Yes, I have used Result in the past.

In this case I wanted to use throws because that is what functions like map
do.

Looks like I will have to stick with throwing, not too bad since the
overhead is reasonably low.

···

On Wed, 11 Jan 2017 at 8:03 am, T.J. Usiyan <griotspeak@gmail.com> wrote:

I suggest using an enum to represent success and failure. It's usually
referred to as `Result`. It might seem weird for the closure that you
presented as an example, since it would be Result<Void>, but it properly
captures the possibility of having thrown.

enum UselessError : Swift.Error {
    case somethingBadHappened
}

enum Result<T> {
    case success(T)
    case failure(Swift.Error)

    init(throwingClosure: (Void) throws -> T) {
        do {
            self = try .success(throwingClosure())
        } catch {
            self = .failure(error)
        }
    }
}

func burnItDown() throws -> Void {
    throw UselessError.somethingBadHappened
}

Result(throwingClosure: burnItDown)

On Mon, Jan 9, 2017 at 10:28 PM, Jacob Bandes-Storch via swift-users < > swift-users@swift.org> wrote:

Moving to swift-users list.

No, there's no way to do this today. The point of rethrows is that within
one call site, "f(block)" can be treated as throwing if the block throws,
or not throwing if the block doesn't throw. In your example, once the
FStore object is constructed, the information about the original passed-in
function is lost, so the caller has no way to know whether call() can throw
or not.

If this *were* possible, the information would somehow need to be encoded
in the type system when creating FStore(f: block). That would require
something like dependent typing, or generic-param-based-rethrows, e.g.

struct FStore<T: () throws -> Void> { // made-up syntax
    let f: T
    func call() rethrows(T) { try f() } // throws-ness of this function
depends on throws-ness of T
}

On Mon, Jan 9, 2017 at 9:21 PM, Howard Lovatt via swift-evolution < > swift-evolution@swift.org> wrote:

Hi,

If I have an escaping function that I store and then call, I need to
declare the calling function as throwing, not rethrowing. EG:

    struct FStore {
        let f: () throws -> Void
        init(f: @escaping () throws -> Void) { self.f = f }
        func call() throws { try f() } // Can't put rethrows here - have
to use throws
    }
Is there a better solution?

Thanks for any suggestions,

  -- Howard.

_______________________________________________

swift-evolution mailing list

swift-evolution@swift.org

https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________

swift-users mailing list

swift-users@swift.org

https://lists.swift.org/mailman/listinfo/swift-users

--

-- Howard.


(Slava Pestov) #6

Another possibility, other than generics, would be to drop rethrows all together and have the compiler infer if a throw is possible or not, including:

    struct FStore {
        let f: () throws -> Void
        func call() throws { try f() }
    }

The compiler can make two versions, one if f can throw and one if it definitely doesn't.

It seems that this approach is impractical, because you either have to compile two versions of every function, or make all function bodies available for inlining, which is a non-starter for a stable ABI.

Slava

···

On Jan 11, 2017, at 2:02 PM, Howard Lovatt via swift-evolution <swift-evolution@swift.org> wrote:

Just a thought.

On Tue, 10 Jan 2017 at 4:29 pm, Jacob Bandes-Storch <jtbandes@gmail.com <mailto:jtbandes@gmail.com>> wrote:
Moving to swift-users list.

No, there's no way to do this today. The point of rethrows is that within one call site, "f(block)" can be treated as throwing if the block throws, or not throwing if the block doesn't throw. In your example, once the FStore object is constructed, the information about the original passed-in function is lost, so the caller has no way to know whether call() can throw or not.

If this *were* possible, the information would somehow need to be encoded in the type system when creating FStore(f: block). That would require something like dependent typing, or generic-param-based-rethrows, e.g.

struct FStore<T: () throws -> Void> { // made-up syntax
    let f: T
    func call() rethrows(T) { try f() } // throws-ness of this function depends on throws-ness of T
}

On Mon, Jan 9, 2017 at 9:21 PM, Howard Lovatt via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
Hi,

If I have an escaping function that I store and then call, I need to declare the calling function as throwing, not rethrowing. EG:

    struct FStore {
        let f: () throws -> Void
        init(f: @escaping () throws -> Void) { self.f = f }
        func call() throws { try f() } // Can't put rethrows here - have to use throws
    }

Is there a better solution?

Thanks for any suggestions,

  -- Howard.

_______________________________________________

swift-evolution mailing list

swift-evolution@swift.org <mailto:swift-evolution@swift.org>

https://lists.swift.org/mailman/listinfo/swift-evolution

--
-- Howard.
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution


(Anton Zhilin) #7

The best way to deal with such situations should be typed throws. Then
rethrows should be removed and replaced with generics in throws clause. E
== Never ⇔ function does not throw.

struct FStore<E> {
    let f: () throws(E) -> Void
    init(_ f: @escaping () throws(E) -> Void) { self.f = f }
    func call() throws(E) { try f() }
}

let store = FStore<Never>({ print("Hello") })
store.call()

[Phase2]


(Howard Lovatt) #8

@Anton, Yes that would work and we would get typed throws, which I like. As
an aside, I think E would have to be constrained to be an Error, `<E:

`, and maybe `throws<E>` reads better because you are used to types

in angle brackets.

  -- Howard.

···

On 13 January 2017 at 08:32, Anton Zhilin <antonyzhilin@gmail.com> wrote:

The best way to deal with such situations should be typed throws. Then
rethrows should be removed and replaced with generics in throws clause. E
== Never ⇔ function does not throw.

struct FStore<E> {
    let f: () throws(E) -> Void
    init(_ f: @escaping () throws(E) -> Void) { self.f = f }
    func call() throws(E) { try f() }
}

let store = FStore<Never>({ print("Hello") })
store.call()

[Phase2]


(Howard Lovatt) #9

@Slava,

I'm imagining that the compiler does much the same as Anton suggested, e.g.
Anton's:

    struct FStore<E: Error> {
        let f: () throws<E> -> Void
        init(_ f: @escaping () throws<E> -> Void) { self.f = f }
        func call() throws<E> { try f() }
    }

Is much the same as my:

    struct FStore {
        let f: () throws -> Void
        init(_ f: @escaping () throws -> Void) { self.f = f }
        func call() throws { try f() }
    }

From a compiler point of view.

The idea is that the compiler reuses some of its existing generic's
infrastructure. The main differences between my proposal and Anton's is
that because mine doesn't use a typed throw, E can only be 'AnyError' (any
type that implements Error) or Never, and E is not explicit. The
throws/rethrows distinction just becomes a compiler optimization in both
cases.

  -- Howard.

···

On 13 January 2017 at 10:42, Slava Pestov <spestov@apple.com> wrote:

On Jan 11, 2017, at 2:02 PM, Howard Lovatt via swift-evolution < > swift-evolution@swift.org> wrote:

Another possibility, other than generics, would be to drop rethrows all
together and have the compiler infer if a throw is possible or not,
including:

    struct FStore {
        let f: () throws -> Void
        func call() throws { try f() }
    }

The compiler can make two versions, one if f can throw and one if it
definitely doesn't.

It seems that this approach is impractical, because you either have to
compile two versions of every function, or make all function bodies
available for inlining, which is a non-starter for a stable ABI.

Slava

Just a thought.

On Tue, 10 Jan 2017 at 4:29 pm, Jacob Bandes-Storch <jtbandes@gmail.com> > wrote:

Moving to swift-users list.

No, there's no way to do this today. The point of rethrows is that within
one call site, "f(block)" can be treated as throwing if the block throws,
or not throwing if the block doesn't throw. In your example, once the
FStore object is constructed, the information about the original passed-in
function is lost, so the caller has no way to know whether call() can throw
or not.

If this *were* possible, the information would somehow need to be encoded
in the type system when creating FStore(f: block). That would require
something like dependent typing, or generic-param-based-rethrows, e.g.

struct FStore<T: () throws -> Void> { // made-up syntax
    let f: T
    func call() rethrows(T) { try f() } // throws-ness of this function
depends on throws-ness of T
}

On Mon, Jan 9, 2017 at 9:21 PM, Howard Lovatt via swift-evolution <swift- >> evolution@swift.org> wrote:

Hi,

If I have an escaping function that I store and then call, I need to
declare the calling function as throwing, not rethrowing. EG:

    struct FStore {
        let f: () throws -> Void
        init(f: @escaping () throws -> Void) { self.f = f }
        func call() throws { try f() } // Can't put rethrows here - have
to use throws
    }
Is there a better solution?

Thanks for any suggestions,

  -- Howard.

_______________________________________________

swift-evolution mailing list

swift-evolution@swift.org

https://lists.swift.org/mailman/listinfo/swift-evolution

--

-- Howard.
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Pierre Monod-Broca) #10

@Howard
I was thinking the same about the constraint.

Does that mean that Never should be a subtype of Error (maybe it is already) ? Else you couldn't say throws(Never) or FSTore<Never>

@Howard
I'm not sure about <>, it should stay reserved for generics, IMHO.

Pierre

···

Le 13 janv. 2017 à 01:11, Howard Lovatt via swift-users <swift-users@swift.org> a écrit :

@Anton, Yes that would work and we would get typed throws, which I like. As an aside, I think E would have to be constrained to be an Error, `<E: Error>`, and maybe `throws<E>` reads better because you are used to types in angle brackets.

  -- Howard.

On 13 January 2017 at 08:32, Anton Zhilin <antonyzhilin@gmail.com> wrote:
The best way to deal with such situations should be typed throws. Then rethrows should be removed and replaced with generics in throws clause. E == Never ⇔ function does not throw.

struct FStore<E> {
    let f: () throws(E) -> Void
    init(_ f: @escaping () throws(E) -> Void) { self.f = f }
    func call() throws(E) { try f() }
}

let store = FStore<Never>({ print("Hello") })
store.call()
[Phase2]

_______________________________________________
swift-users mailing list
swift-users@swift.org
https://lists.swift.org/mailman/listinfo/swift-users


(Howard Lovatt) #11

I am assuming Never is the subtype of all types, because any function
regardless of it's return type, including Void, can return Never.

···

On Fri, 13 Jan 2017 at 6:32 pm, Pierre Monod-Broca < pierremonodbroca@gmail.com> wrote:

@Howard
I was thinking the same about the constraint.

Does that mean that Never should be a subtype of Error (maybe it is
already) ? Else you couldn't say throws(Never) or FSTore<Never>

@Howard
I'm not sure about <>, it should stay reserved for generics, IMHO.

Pierre

Le 13 janv. 2017 à 01:11, Howard Lovatt via swift-users < > swift-users@swift.org> a écrit :

@Anton, Yes that would work and we would get typed throws, which I like.
As an aside, I think E would have to be constrained to be an Error, `<E:
>`, and maybe `throws<E>` reads better because you are used to types
in angle brackets.

  -- Howard.

On 13 January 2017 at 08:32, Anton Zhilin <antonyzhilin@gmail.com> wrote:

The best way to deal with such situations should be typed throws. Then
rethrows should be removed and replaced with generics in throws clause. E
== Never ⇔ function does not throw.

struct FStore<E> {

    let f: () throws(E) -> Void

    init(_ f: @escaping () throws(E) -> Void) { self.f = f }

    func call() throws(E) { try f() }

}

let store = FStore<Never>({ print("Hello") })

store.call()

[Phase2]

_______________________________________________
swift-users mailing list
swift-users@swift.org
https://lists.swift.org/mailman/listinfo/swift-users

--

-- Howard.