50

tl;dr

Is it possible to instantiate a generic Swift 4 enum member with an associated value of type Void?

Background

I'm using a simple Result enum (similar to antitypical Result):

enum Result<T> {
  case success(T)
  case error(Error?)
}

Now I'd like to use this enum to represent the result of an operation which does not yield an actual result value; the operation is either succeeded or failed. For this I'd define the type as Result<Void>, but I'm struggling with how to create the Result instance, neither let res: Result<Void> = .success nor let res: Result<Void> = .success() works.

Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
dr_barto
  • 5,723
  • 3
  • 26
  • 47
  • are you sure about the second way? looks like it works http://swift.sandbox.bluemix.net/#/repl/599d61b1b459cc41aee76d9d – pacification Aug 23 '17 at 11:08
  • Interesting, thanks for the example. I re-checked, but inside Xcode 9 Beta 5 I get `Missing argument for parameter #1 in call`. – dr_barto Aug 23 '17 at 11:10
  • @Hamish: You are probably right (I was still at SE-0110 and SE-0029 ... :) – Martin R Aug 23 '17 at 11:23
  • 1
    @Hamish: It could also be a consequence of [SE-0029](https://github.com/apple/swift-evolution/blob/master/proposals/0029-remove-implicit-tuple-splat.md): The "constructor" `let f = Result.success` has the type `(Void) -> Result` in both Swift 3 and 4b5. In Swift 3 you can call `let r = f()`, in 4b5 you have to add a argument: `let r = f(())` – Martin R Aug 23 '17 at 11:39

5 Answers5

82

In Swift 3 you can omit the associated value of type Void:

let res: Result<Void> = .success()

In Swift 4 you have to pass an associated value of type Void:

let res: Result<Void> = .success(())
// Or just:
let res = Result.success(())
Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
54

In Swift 4, an enum case with a Void associated value is no longer equivalent to a an enum case with an empty list of associated values.

I believe this is, as Martin says, a result of SE-0029 where you can no longer pass a tuple of arguments to a function and have them "splat" across the parameters (although the proposal was marked implemented in Swift 3, I believe this particular case was picked up later in the implementation of SE-0110 for Swift 4).

As a result, this means you can no longer call a (Void) -> T as a () -> T in Swift 4. You now have to pass Void in explicitly:

let result = Result.success(())

However, I find this pretty ugly, so I usually implement an extension like this:

extension Result where Success == Void {
    static var success: Result {
        return .success(())
    }
}

Which lets you say things like this:

var result = Result.success
result = .success

It's worth noting that this workaround isn't just limited to enum cases, it can be also used with methods in general. For example:

struct Foo<T> {
  func bar(_ a: T) {}
}

extension Foo where T == Void {
  func bar() { bar(()) }
}

let f = Foo<Void>()

// without extension:
f.bar(())

// with extension:
f.bar()
Samps
  • 799
  • 6
  • 12
Hamish
  • 78,605
  • 19
  • 187
  • 280
  • 3
    ((Nice)). Didn't realize you can define vars based on the type of generics. – GoldenJoe Jan 30 '18 at 16:19
  • Awesome solution! I moved to this from .success(Void()) – Lensflare Mar 01 '19 at 14:50
  • The Swift 5 version would be `extension Result where Success == Void { ... }`, but for some reason, `let r1 = Result.success ` fails to compile with “Ambiguous use of 'success'”, but `let r2: Result = .success` compiles without problems. – Martin R May 20 '19 at 08:46
  • @MartinR That sucks – the problem is that `Result.success` could refer to both the enum case constructor for `.success` or the static var. There is already a ranking rule that prefers vars over functions, however that doesn't currently consider enum case constructors :(. `let r2: Result = .success` works because only the static var is of type `Result`. The reason why `Result.success` worked in my original example is because the generic placeholder isn't specified, so can only resolve to the static var, as that satisfies the placeholder with `Void`. – Hamish May 21 '19 at 23:36
  • Unfortunately I don't believe there's currently a nice workaround for this besides doing `let r2: Result = .success`. Ideally the overload ranking rules would change to treat enum case constructors more like static functions. Better yet, it would be cool if enum case constructors were represented as implicitly generated static functions in the AST, which would help eliminate other inconsistencies with them (that might not be possible post-ABI stability though, as I'm not sure the mangling will be the same). – Hamish May 21 '19 at 23:43
5

Void is simple typealias for empty tuple: () so you can use it as any of following:

let res1: Result<Void> = .success(())
let res2 = Result<Void>.success(())
let res3 = Result.success(() as Void)
let res4 = Result.success(())
KaQu
  • 111
  • 5
4

Swift 5 has updated Result to require the Failure parameter, but still requires the associated value:

let res: Result<Void, Error> = .success(())
WongWray
  • 2,414
  • 1
  • 20
  • 25
4

i find .success(Void()) is more descriptive and simple.

Saqib Saud
  • 2,799
  • 2
  • 27
  • 41