# Best approach to enum with associated value common across all cases

**URL:** <https://forums.swift.org/t/best-approach-to-enum-with-associated-value-common-across-all-cases/72577>\
**Category:** Using Swift\
**Tags:** enums\
**Created:** [June 18, 2024, 2:51pm UTC](https://forums.swift.org/t/best-approach-to-enum-with-associated-value-common-across-all-cases/72577 "2024-06-18T14:51:58Z")\
**Posts on this page:** 12\
**Page:** 1

<div class="post-metadata">

**Author:** ![gahms](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/gahms/32/938_2.png) [@gahms](https://forums.swift.org/u/gahms)\
**Post date:** [June 18, 2024, 2:51pm UTC](https://forums.swift.org/t/best-approach-to-enum-with-associated-value-common-across-all-cases/72577/1 "2024-06-18T14:51:58Z")

</div>

What is the best approach when we have an enum where we want an associated value common across all enum cases?

Obvious solution is to wrap it in a `struct` but I wonder if there is a better approach.

Example

```swift
enum Element {
  case circle(x: Double, y: Double, radius: Double)
  case line(x1: Double, y1: Double, x2: Double, y2: Double)
}

```

Now if I want each object to have an `id`:

```swift
struct ElementBox {
  let id: String
  var element: Element
}

```

Another approach could be

```swift
enum Element {
  case circle(id: String, x: Double, y: Double, radius: Double)
  case line(id: String, x1: Double, y1: Double, x2: Double, y2: Double)

  var id: String {
      switch self {
          case let .circle(id, _,_,_): return id
          case let .line(id, _,_,_,_): return id
      }
  }
}

```

Both seems a bit clumsy.

---

<div class="post-metadata">

**Author:** ![Slava\_Pestov](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/slava_pestov/32/30204_2.png) [@Slava\_Pestov](https://forums.swift.org/u/Slava_Pestov)\
**Post date:** [June 18, 2024, 3:07pm UTC](https://forums.swift.org/t/best-approach-to-enum-with-associated-value-common-across-all-cases/72577/2 "2024-06-18T15:07:36Z")

</div>

> [@gahms](#):
>
> Obvious solution is to wrap it in a `struct` but I wonder if there is a better approach.

I think this is the best approach. In your example, an “id” is not an inherent property of a circle or a line, it is a separate thing, so it makes sense for it to be its own data type that wraps a shape and adds an “id”.

---

<div class="post-metadata">

**Author:** ![Andropov](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/andropov/32/35955_2.png) [@Andropov](https://forums.swift.org/u/Andropov)\
**Post date:** [June 18, 2024, 3:23pm UTC](https://forums.swift.org/t/best-approach-to-enum-with-associated-value-common-across-all-cases/72577/3 "2024-06-18T15:23:36Z")

</div>

What is the goal of storing the properties of `Element` in the `enum`'s associated values? That would help a lot in thinking of alternatives. Without other context, I would have written it without using enums at all:

```swift
protocol Element: Identifiable {
    var id: UUID { get }
}

struct Circle: Element {
    let id = UUID()
    let x: Double
    let y: Double
    let radius: Double
}

struct Line: Element {
    let id = UUID()
    let x1: Double
    let y1: Double
    let x2: Double
    let y2: Double
}

```

And rely in type information instead of the `enum` case to know the type of element. Why wouldn't this be a fit for what you're trying to do? What properties of `enum` do you need to leverage that a `struct` wouldn't have?

IMHO, the ergonomics of `enum`s with associated values get awkward when you have more than a couple values. In fact (hope this doesn't come off as too opinionated) I think that's a clear sign that a different data structure could do better at modeling the concept at hand.

For example, getting the radius of a `Element` with your proposed code also feels 'clumsy':

```swift
func getRadius(of element: Element) -> Double? {
    switch fooElement {
    case .circle(_, _, _, let radius):
        return radius
    default:
        return nil
    }
}

```

And things only get worse if the types grow.

---

<div class="post-metadata">

**Author:** ![ktraunmueller](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/ktraunmueller/32/46137_2.png) [@ktraunmueller](https://forums.swift.org/u/ktraunmueller)\
**Post date:** [June 18, 2024, 4:01pm UTC](https://forums.swift.org/t/best-approach-to-enum-with-associated-value-common-across-all-cases/72577/4 "2024-06-18T16:01:33Z")

</div>

Nicolai's example is very much in line with what I would do. I use enums with associated values _a lot_, and I think they are one of the best features of Swift.

They let you define use case-specific data structures, very much like a set of different structs, but under one common static type umbrella, that doesn't require downcasting or runtime type checking.

With enums, you can switch over the cases, use pattern matching, or use `case let` to check specific cases, and the compiler forces you to handle _all_ cases, so you can't inadvertently miss cases.

In general, I think enums are preferable over a protocol-based design in cases like this, because you as a type designer define the problem space, you clearly stake out the boundaries of the design, and often this is all it needs, and easier to reason about than open-ended designs (using protocols).

---

<div class="post-metadata">

**Author:** ![Slava\_Pestov](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/slava_pestov/32/30204_2.png) [@Slava\_Pestov](https://forums.swift.org/u/Slava_Pestov)\
**Post date:** [June 18, 2024, 4:03pm UTC](https://forums.swift.org/t/best-approach-to-enum-with-associated-value-common-across-all-cases/72577/5 "2024-06-18T16:03:19Z")

</div>

> [@Andropov](#):
>
> For example, getting the radius of a `Element` with your proposed code also feels 'clumsy':

A getRadius() method that expects the input be a specific case is itself an anti-pattern. Operations should be exhaustive, so computing the area, drawing a shape, etc.

---

<div class="post-metadata">

**Author:** ![Andropov](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/andropov/32/35955_2.png) [@Andropov](https://forums.swift.org/u/Andropov)\
**Post date:** [June 18, 2024, 5:03pm UTC](https://forums.swift.org/t/best-approach-to-enum-with-associated-value-common-across-all-cases/72577/6 "2024-06-18T17:03:39Z")

</div>

> [@ktraunmueller](#):
>
> I use enums with associated values _a lot_, and I think they are one of the best features of Swift.
> 
> They let you define use case-specific data structures, very much like a set of different structs, but under one common static type umbrella, that doesn't require downcasting or runtime type checking.
> 
> With enums, you can switch over the cases, use pattern matching, or use `case let` to check specific cases, and the compiler forces you to handle _all_ cases, so you can't inadvertently miss cases.

Ah, don't take me wrong, I love `enum`s in Swift! What I'm arguing is that if you end up having to bundle several associated types, both common (like `id`) and unrelated (like `radius`) in the same `enum`... you're better off with a `struct` or a `class`.

I believe this is _exactly_ what you'd want to use generics for. You should be able to do pretty much everything that the enum-based `Element`s does using generics, and you wouldn't need to downcast or do runtime checking at all.

In fact, I'd argue the opposite is true: while with `enum`s you would end up having lots of `switch` statements scattered throughout the code just to extract the properties of each enum case (which has a runtime cost, just like runtime type checking), if you use generics, you'll be exposing the specific "kind" of object (Circle, Line...) at compile time, instead of at runtime, avoiding any runtime checks for the "kind" of object (like those `switch`es).

The obvious loss here is the ability to iterate over all "kinds" of elements if you don't use an `enum`. But in my experience, having access to the concrete "kind" of `Element` removes a lot of the use cases of that `switch`, and you may not need to iterate over the possible element types that often anymore.

Again, that depends on the use case in particular. There's obviously use cases for `enum`s with associated values (I use them a lot for errors, and I love it!). I just don't think it's wise to use them as a sort-of-replacement of the type system.

* * *

> [@Slava\_Pestov](#):
>
> A getRadius() method that expects the input be a specific case is itself an anti-pattern. Operations should be exhaustive, so computing the area, drawing a shape, etc.

That was kind of my point, sorry if I wasn't clear. I was arguing that if you use `enum`s to model the wrong kind of thing, it's very easy to run into antipatterns. Continuing with the previous `radius` example: let's say I create a `.circle` enum element:

```swift
let circle: Element = .circle(id: UUID(), x: 1, y: 2, radius: 3)

```

How would you go about accessing its radius after it's been created?

```swift
let radius = circle.radius // ❌ This obviously doesn't compile

```

You're pretty much forced to use an antipattern / unsafe operation:

```swift
let radius = if case .circle(_, _, _, let radius) = circle {
    radius
} else {
    // ⚠️ Can crash at runtime, compile-time guarantees are lost...
    fatalError("Expected a circle!")
}

```

If what you're pointing out is that you shouldn't need to explicitly access `radius` outside a function that handles all `enum` cases, like a `computeArea` function:

```swift
func computeArea(of element: Element2) -> Double {
    switch element {
    case .circle(_, _, _, let radius):
        // ⚠️ IMHO having to write those unused _, _, _, is already a
        // 'smoking gun' that there should be better ways to do this
        return Double.pi * radius * radius
    case .line:
        return .zero
    }
}

```

I agree _in theory_. But in practice, I think you'll often run into edge cases where you need to access a specific value of an `enum` case outside the scope of a function that accesses all cases.

And since you can easily do this with a `struct` based model by extending the `Element` protocol anyway...

```swift
protocol Element: Identifiable {
    var id: UUID { get }
    var area: Double { get }
}

struct Circle: Element {
    let id = UUID()
    let x: Double
    let y: Double
    let radius: Double
    
    var area: Double {
        return Double.pi * radius * radius
    }
}

struct Line: Element {
    let id = UUID()
    let x1: Double
    let y1: Double
    let x2: Double
    let y2: Double
    
    let area: Double = .zero
}

```

Why not use it instead of `enum`s? It's just as easy, and you have much better expressivity than with an enum with half a dozen associated values.

---

<div class="post-metadata">

**Author:** ![taylorswift](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/taylorswift/32/43413_2.png) [@taylorswift](https://forums.swift.org/u/taylorswift)\
**Post date:** [June 18, 2024, 5:57pm UTC](https://forums.swift.org/t/best-approach-to-enum-with-associated-value-common-across-all-cases/72577/7 "2024-06-18T17:57:27Z")

</div>

> [@Andropov](#):
>
> Why not use it instead of `enum`s? It's just as easy, and you have much better expressivity than with an enum with half a dozen associated values.

there’s a huge downside to this approach, in that you can’t express the union of both types without going through `any Element`, which i expect would allocate in this example, as neither type fits into the inline storage of an existential.

---

<div class="post-metadata">

**Author:** ![ktraunmueller](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/ktraunmueller/32/46137_2.png) [@ktraunmueller](https://forums.swift.org/u/ktraunmueller)\
**Post date:** [June 18, 2024, 5:59pm UTC](https://forums.swift.org/t/best-approach-to-enum-with-associated-value-common-across-all-cases/72577/8 "2024-06-18T17:59:32Z")

</div>

An enum type defines one concrete type, while a protocol defines an open space of adopters. I think the restrictiveness of a closed set of options is often sufficient, simpler, and thus preferable.

The primary use case I see for generics is as a way to implement algorithms operating on an open, yet constrained set of data types (via type constraints on argument placeholders). Yes, there's generic data types, but the algorithms are what make up the interesting bits.

---

<div class="post-metadata">

**Author:** ![mayoff](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/mayoff/32/613_2.png) [@mayoff](https://forums.swift.org/u/mayoff)\
**Post date:** [June 18, 2024, 5:59pm UTC](https://forums.swift.org/t/best-approach-to-enum-with-associated-value-common-across-all-cases/72577/9 "2024-06-18T17:59:33Z")

</div>

> [@Andropov](#):
>
> How would you go about accessing its radius after it's been created?
> 
> ```swift
> let radius = circle.radius // ❌ This obviously doesn't compile
> 
> ```
> 
> You're pretty much forced to use an antipattern / unsafe operation:
> 
> ```swift
> let radius = if case .circle(_, _, _, let radius) = circle {
> radius
> } else {
> // ⚠️ Can crash at runtime, compile-time guarantees are lost...
> fatalError("Expected a circle!")
> }
> 
> ```

You have exactly the same problem with a protocol-based design:

```swift
let radius = if let c = circle as? Circle {
    c.radius
} else {
    fatalError()
}

```

If you’re asking for a case-specific property, it doesn’t matter whether you represent your cases with a single `enum` type or a set of protocol-conforming types; you have to deal with the possibility of having the wrong case regardless.

---

<div class="post-metadata">

**Author:** ![ktraunmueller](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/ktraunmueller/32/46137_2.png) [@ktraunmueller](https://forums.swift.org/u/ktraunmueller)\
**Post date:** [June 18, 2024, 6:07pm UTC](https://forums.swift.org/t/best-approach-to-enum-with-associated-value-common-across-all-cases/72577/10 "2024-06-18T18:07:50Z")

</div>

And the compiler can't help you as much with the down-casting as compared to a closed set of options.

I think the initial design is just fine.

---

<div class="post-metadata">

**Author:** ![Slava\_Pestov](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/slava_pestov/32/30204_2.png) [@Slava\_Pestov](https://forums.swift.org/u/Slava_Pestov)\
**Post date:** [June 18, 2024, 6:11pm UTC](https://forums.swift.org/t/best-approach-to-enum-with-associated-value-common-across-all-cases/72577/11 "2024-06-18T18:11:49Z")

</div>

> [@Andropov](#):
>
> ```swift
> let circle: Element = .circle(id: UUID(), x: 1, y: 2, radius: 3)
> 
> ```
> 
> How would you go about accessing its radius after it's been created?
> 
> ```swift
> let radius = circle.radius // ❌ This obviously doesn't compile
> 
> ```
> 
> You're pretty much forced to use an antipattern / unsafe operation:

If you need to do this, you should declare a `Circle` type and then the `Element.circle` case can store a `Circle` value instead of directly storing its center and radius.

> [@Andropov](#):
>
> I agree _in theory_. But in practice, I think you'll often run into edge cases where you need to access a specific value of an `enum` case outside the scope of a function that accesses all cases.

I suspect this means you're storing too much "stuff" directly in your enum. Declare more types 🙂

---

<div class="post-metadata">

**Author:** ![Andropov](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/andropov/32/35955_2.png) [@Andropov](https://forums.swift.org/u/Andropov)\
**Post date:** [June 18, 2024, 6:39pm UTC](https://forums.swift.org/t/best-approach-to-enum-with-associated-value-common-across-all-cases/72577/12 "2024-06-18T18:39:18Z")

</div>

> [@taylorswift](#):
>
> there’s a huge downside to this approach, in that you can’t express the union of both types without going through `any Element`, which i expect would allocate in this example, as neither type fits into the inline storage of an existential.

Inability to express the union without going through `any Element` is a fair concern, hence why I asked what the ultimate case is, trying to evaluate why that flexibility is needed. But _if_ you don't need to create a collection of elements of different concrete types of `Element`, you can leverage generics, use `some Element` everywhere, and have a more performant and ergonomic version.

And if you _do_ need to mix elements of different concrete types and go throuhg `any Element`, well, I'm not sure if the existential type would fare much worse than `enum` + `switch`. That'd be an interesting thing to benchmark (not sure if it's already been done).

> [@mayoff](#):
>
> You have exactly the same problem with a protocol-based design:
> 
> ```swift
> let radius = if let c = circle as? Circle {
> c.radius
> } else {
> fatalError()
> }
> 
> ```
> 
> If you’re asking for a case-specific property, it doesn’t matter whether you represent your cases with a single `enum` type or a set of protocol-conforming types; you have to deal with the possibility of having the wrong case regardless.

No, no, but my point is that by using an enum:

```swift
let circle: Element = .circle(id: UUID(), x: 1, y: 2, radius: 3)
// ⚠️ You need to do this now to access radius after circle has been
// created, compiler doesn't know that circle has a radius as its type
// is just "Element"
let radius = if case .circle(_, _, _, let radius) = circle {
    radius
} else {
    fatalError("Expected a circle!")
}

```

You always need to check the "kind" information (Circle/Line/...) at **runtime**. The whole point about using the protocol is that such information becomes part of the type system an thus trivial to check at **compile time**. So this works:

```swift
let circle = Circle(x: 1, y: 2, radius: 3)
// Compiler already knows that circle has a radius property, because
// its type is "Circle"
let radius = circle.radius

```

Precisely because the compiler is aware that circle is not `any Element`, but rather a concrete type, `Circle`. This information is obscured (in the sense that you'll need to check for it at runtime) if you use an `enum`, and there's no easy way to tell the compiler "this function should only accept values with this specific characteristics" as you can do with protocols.

Circling (hehe) back to the specific example of the `getRadius` function, what you'd do with protocols is change the signature to only accept `Circle` (and not any kind of element):

```swift
// Compiler enforces that this never receives a Line
func getRadius(of circle: Circle) -> Double? {
    return circle.radius
}

```

And the compiler guarantees that `getRadius` is only called when the type is `Circle`. Obviously the example above is trivial (you'd just access the property directly), but if you had other types with radius (for example, `Sphere`), you could create:

```swift
protocol Circular {
    var radius: Double { get }
}

func getRadius(of circular: some Circular) -> Double? {
    return circular.radius
}

```

And the compiler would guarantee for you that only types with `radius` are fed to the function. This is the kind of expressivity that you'd be missing when using `enum`. You might not need it (it will depend on the specific use case) but having a case with more than a few associated values hints that you do.

The one case where you could run into the invalid value again is if you needed to use `any Element` as the argument:

```swift
// ❌ Ideally, don't do this
func getRadius(of element: any Element) -> Double? {
    if let circle = element as? Circle {
        return circle.radius
    } else {
        fatalError("Expected circle")
    }
}

```

But you are unlikely to need to do that, logically. The reason you can run into such cases with `enum` is because the compiler can't reason about the "kind" of object (Circle/Line...) at compile time.

So even if you have, for example, an array of `Element`s where all the items are of the enum's case `.circle`, the compiler doesn't know that those all have a radius (as the type it sees is just `[Element]`). With the `struct`+protocol version you can create a `[Circle]` array. So it protects you from running into the runtime issue of encountering objects of unexpected "kind".

And if your elements are all of (logically) different types and need to use `any Element`, that means that you don't expect all elements to be circles, and thus you don't expect all elements to have a `radius` either.
