[Pitch] Throwing unwrap operators

This idea was sparked by conversation in another thread about a more concise way to write the following:

guard let x1 = f1(), let x2 = f2(), ... else { ... }
doSomething(with: x1)
doSomething(with: x2)
...

It was suggested to write a function that throws when an optional is nil, and then wrapping the calls to f1, f2, ... in that function, and then wrapping *those* in a do-catch block. This would both exit early in case one value was nil, and also use the values as soon as they’re available instead of needing to assign them to a temp variable first and using the temp variable.

Instead of a throwing unwrap function, I am proposing a throwing unwrap operator. This operator would work like !, but instead of a fatal error when the value is nil, it would throw.

struct UnwrapError: Error {}
postfix operator ^?
extension Optional {
   static postfix func ^?(optional: Optional) throws -> Wrapped {
       guard let wrapped = optional else {
           throw UnwrapError()
       }
       return wrapped
   }
}

In addition, to round this out, I think it would be helpful to be able to throw arbitrary errors, as a nil value may carry meaning that should be propagated to the rest of the program. Thus there could be a throwing nil coalescing operator, which returns the unwrapped value if non-nil, or throws the specified error if nil.

infix operator ^??
extension Optional {
   static func ^??(lhs: Optional, rhs: Error) throws -> Wrapped {
       guard let wrapped = lhs else {
           throw rhs
       }
       return wrapped
   }
}

Thoughts? I think these would be helpful additions to allow doing something with Optionals while simultaneously exiting early in the case of a nil value — putting the unwrapping, the use, and the exiting early all on one line.

Hopefully this doesn’t distract from the other important conversations happening on the mailing list!

2 Likes

I like the general direction where this is going, but I haven’t settled on an opinion regarding its appearance yet.
I’ve argued before that the gap between optionals and errors should be closed by making it easy to swift from optionals to non-optionals with throwing.
The “unwrap-or-die” idea discussed earlier is very similar to this.

In the end, I think all optional, throwing and fatalError-related proposals have to be united and carefully considered as an overarching proposal to extend and complete Swift’s representation of “less-than-normal” situations.

···

On Jul 30, 2017, at 9:25 PM, Robert Bennett via swift-evolution <swift-evolution@swift.org> wrote:

This idea was sparked by conversation in another thread about a more concise way to write the following:

guard let x1 = f1(), let x2 = f2(), ... else { ... }
doSomething(with: x1)
doSomething(with: x2)
...

It was suggested to write a function that throws when an optional is nil, and then wrapping the calls to f1, f2, ... in that function, and then wrapping *those* in a do-catch block. This would both exit early in case one value was nil, and also use the values as soon as they’re available instead of needing to assign them to a temp variable first and using the temp variable.

Instead of a throwing unwrap function, I am proposing a throwing unwrap operator. This operator would work like !, but instead of a fatal error when the value is nil, it would throw.

struct UnwrapError: Error {}
postfix operator ^?
extension Optional {
  static postfix func ^?(optional: Optional) throws -> Wrapped {
      guard let wrapped = optional else {
          throw UnwrapError()
      }
      return wrapped
  }
}

In addition, to round this out, I think it would be helpful to be able to throw arbitrary errors, as a nil value may carry meaning that should be propagated to the rest of the program. Thus there could be a throwing nil coalescing operator, which returns the unwrapped value if non-nil, or throws the specified error if nil.

infix operator ^??
extension Optional {
  static func ^??(lhs: Optional, rhs: Error) throws -> Wrapped {
      guard let wrapped = lhs else {
          throw rhs
      }
      return wrapped
  }
}

Thoughts? I think these would be helpful additions to allow doing something with Optionals while simultaneously exiting early in the case of a nil value — putting the unwrapping, the use, and the exiting early all on one line.

Hopefully this doesn’t distract from the other important conversations happening on the mailing list!
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

From 2016:

See: https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160404/014272.html
and https://gist.github.com/erica/5a26d523f3d6ffb74e34d179740596f7

-- E

···

On Jul 30, 2017, at 12:30 PM, Gor Gyolchanyan via swift-evolution <swift-evolution@swift.org> wrote:

I like the general direction where this is going, but I haven’t settled on an opinion regarding its appearance yet.
I’ve argued before that the gap between optionals and errors should be closed by making it easy to swift from optionals to non-optionals with throwing.
The “unwrap-or-die” idea discussed earlier is very similar to this.

In the end, I think all optional, throwing and fatalError-related proposals have to be united and carefully considered as an overarching proposal to extend and complete Swift’s representation of “less-than-normal” situations.

On Jul 30, 2017, at 9:25 PM, Robert Bennett via swift-evolution <swift-evolution@swift.org> wrote:

This idea was sparked by conversation in another thread about a more concise way to write the following:

guard let x1 = f1(), let x2 = f2(), ... else { ... }
doSomething(with: x1)
doSomething(with: x2)
...

It was suggested to write a function that throws when an optional is nil, and then wrapping the calls to f1, f2, ... in that function, and then wrapping *those* in a do-catch block. This would both exit early in case one value was nil, and also use the values as soon as they’re available instead of needing to assign them to a temp variable first and using the temp variable.

Instead of a throwing unwrap function, I am proposing a throwing unwrap operator. This operator would work like !, but instead of a fatal error when the value is nil, it would throw.

struct UnwrapError: Error {}
postfix operator ^?
extension Optional {
static postfix func ^?(optional: Optional) throws -> Wrapped {
     guard let wrapped = optional else {
         throw UnwrapError()
     }
     return wrapped
}
}

In addition, to round this out, I think it would be helpful to be able to throw arbitrary errors, as a nil value may carry meaning that should be propagated to the rest of the program. Thus there could be a throwing nil coalescing operator, which returns the unwrapped value if non-nil, or throws the specified error if nil.

infix operator ^??
extension Optional {
static func ^??(lhs: Optional, rhs: Error) throws -> Wrapped {
     guard let wrapped = lhs else {
         throw rhs
     }
     return wrapped
}
}

Thoughts? I think these would be helpful additions to allow doing something with Optionals while simultaneously exiting early in the case of a nil value — putting the unwrapping, the use, and the exiting early all on one line.

Hopefully this doesn’t distract from the other important conversations happening on the mailing list!
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

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

1 Like

That seems like a straight-forward way of plugging that particular hole in the ”less-than-normal” feature set of Swift.
I do, however, think that ??? Is not a great choice, because it doesn’t indicate the error handling at all.
Considering that the traditional ?? means “unwrap optional or use the other value’, I’d go for something along the lines of ?throw that means “unwrap optional or throw error”.

func foo() throws -> String {
  return bar ?throw EmbarrassingError.oops
}

But there are other holes in completeness of Swift’s features that are closely related to this.

I opened a discussion a while back regarding something similar, but that discussion got abandoned without a conclusion.
In a nutshell, I proposed making `throws` and `inout` less magical by promoting them to proper declaration specifiers.
Currently, something like

var foo: inout String = &bar

can be exactly replicated in the following way:

var foo: String {
  get {
    return bar
  }
  
  set {
    bar = newValue
  }
}

So the would be just some minor syntax sugar for logical completeness and convenience.
Further helping the logical completeness of `inout` would be allowing functions to have a `get` and `set` accessors just like subscripts. This would allow implementing a `set` accessor of the function, essentially making it an `inout` function with all the usual perks like the prefix ampersand operator.

The `throws` could get the same treatment by first allowing property accessors to be marked as `throws`:

var foo: String throws {
  return try throwingFunction()
}

print(try foo)

Then getting a syntax sugar just like `inout`:

var foo: String throws = throwingFunction() // notice the lack of `try`, due to the fact that the error from `throwingFunction()` is not thrown into the current scope.

In cases where the getter and setter of the variable can have different thowing capabilities, the trick used by access level indicators could be used:

var foo: String throws(set)

or

var foo: String throws(get)

What do you think?

···

On Jul 30, 2017, at 9:37 PM, Erica Sadun <erica@ericasadun.com> wrote:

From 2016:

See: https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160404/014272.html
and https://gist.github.com/erica/5a26d523f3d6ffb74e34d179740596f7

-- E

On Jul 30, 2017, at 12:30 PM, Gor Gyolchanyan via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

I like the general direction where this is going, but I haven’t settled on an opinion regarding its appearance yet.
I’ve argued before that the gap between optionals and errors should be closed by making it easy to swift from optionals to non-optionals with throwing.
The “unwrap-or-die” idea discussed earlier is very similar to this.

In the end, I think all optional, throwing and fatalError-related proposals have to be united and carefully considered as an overarching proposal to extend and complete Swift’s representation of “less-than-normal” situations.

On Jul 30, 2017, at 9:25 PM, Robert Bennett via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

This idea was sparked by conversation in another thread about a more concise way to write the following:

guard let x1 = f1(), let x2 = f2(), ... else { ... }
doSomething(with: x1)
doSomething(with: x2)
...

It was suggested to write a function that throws when an optional is nil, and then wrapping the calls to f1, f2, ... in that function, and then wrapping *those* in a do-catch block. This would both exit early in case one value was nil, and also use the values as soon as they’re available instead of needing to assign them to a temp variable first and using the temp variable.

Instead of a throwing unwrap function, I am proposing a throwing unwrap operator. This operator would work like !, but instead of a fatal error when the value is nil, it would throw.

struct UnwrapError: Error {}
postfix operator ^?
extension Optional {
static postfix func ^?(optional: Optional) throws -> Wrapped {
     guard let wrapped = optional else {
         throw UnwrapError()
     }
     return wrapped
}
}

In addition, to round this out, I think it would be helpful to be able to throw arbitrary errors, as a nil value may carry meaning that should be propagated to the rest of the program. Thus there could be a throwing nil coalescing operator, which returns the unwrapped value if non-nil, or throws the specified error if nil.

infix operator ^??
extension Optional {
static func ^??(lhs: Optional, rhs: Error) throws -> Wrapped {
     guard let wrapped = lhs else {
         throw rhs
     }
     return wrapped
}
}

Thoughts? I think these would be helpful additions to allow doing something with Optionals while simultaneously exiting early in the case of a nil value — putting the unwrapping, the use, and the exiting early all on one line.

Hopefully this doesn’t distract from the other important conversations happening on the mailing list!
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

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

That's really dangerous. Unless you want to introduce a substructural type system like Rust's, all of a sudden all your structs can be aliased and mutated. There's a reason you can't do this:

func reassigner<T>(reassigning value: inout T) -> Any {
	return { value = $0 }
}

error: escaping closures can only capture inout parameters explicitly by value

See the Ownership Manifesto about local inout values in the section “Local ephemeral bindings.”

1 Like

Hah I didn’t understand any of that document the first time that I read it. Now that I’m starting to get a grasp of rust, I think i’ll have another go at it. Thanks for the nudge!