95

Consider:

enum Line {
    case    Horizontal(CGFloat)
    case    Vertical(CGFloat)
}

let leftEdge             =  Line.Horizontal(0.0)
let leftMaskRightEdge    =  Line.Horizontal(0.05)

How can I access, say, lefEdge's associated value, directly, without using a switch statement?

let noIdeaHowTo          = leftEdge.associatedValue + 0.5

This doesn't even compile!

I had a look at these SO questions but none of the answers seem to address this issue.

The noIdeaHowTo non compiling line above should really be that one-liner, but because the associated value can be any type, I fail to even see how user code could write even a "generic" get or associatedValue method in le enum itself.

I ended up with this, but it is gross, and needs me to revisit the code each time I add/modify a case ...

enum Line {
    case    Horizontal(CGFloat)
    case    Vertical(CGFloat)

    var associatedValue: CGFloat {
        get {
            switch self {
                case    .Horizontal(let value): return value
                case    .Vertical(let value): return value
            }
        }
    }
}

Any pointer anyone?

Community
  • 1
  • 1
verec
  • 5,224
  • 5
  • 33
  • 40
  • I think what you did is right (computed property, that is). Enums can have associated values of any type, and more importantly of any count. I do not see how Apple could provide shorthand access to them without forcing us into numerous `as?` and `if`. – 0x416e746f6e Jul 11 '15 at 16:33
  • BTW, if your enum has CGFloat associated with every case, you can consider using raw value instead of association. – 0x416e746f6e Jul 11 '15 at 16:35
  • 1
    @AntonBronnikov Raw values are constant and they must be unique, i.e., you cannot have two `Horizontal` instances with a different value. – Arkku Jul 11 '15 at 16:36
  • True. Computed property then. – 0x416e746f6e Jul 11 '15 at 16:40
  • 2
    In Swift 2, you can do this in an `if` which is a little more compact than the switch: `if case .Horizontal(let value) = leftEdge { print("value = \(value)") }` – vacawama Jul 11 '15 at 17:36
  • Checkout Edwin's answer (works in Swift 2 and 3): http://stackoverflow.com/questions/25880789/how-to-get-a-swift-enums-associated-value-regardless-of-the-enum-case – BonanzaDriver Oct 14 '16 at 15:19

6 Answers6

119

As others have pointed out, this is now kind of possible in Swift 2:

import CoreGraphics

enum Line {
    case    Horizontal(CGFloat)
    case    Vertical(CGFloat)
}

let min = Line.Horizontal(0.0)
let mid = Line.Horizontal(0.5)
let max = Line.Horizontal(1.0)

func doToLine(line: Line) -> CGFloat? {
    if case .Horizontal(let value) = line {
        return value
    }
    return .None
}

doToLine(min) // prints 0
doToLine(mid) // prints 0.5
doToLine(max) // prints 1
verec
  • 5,224
  • 5
  • 33
  • 40
  • 6
    How would you implemented that in a way that it works for any enum and any case? – Rodrigo Ruiz Aug 23 '17 at 21:53
  • 2
    @RodrigoRuiz I recommend Arkku's answer for you. You want a struct containing an enum and a value, not just an enum. – uliwitness Sep 14 '17 at 13:27
  • Just to point out: this isn't fundamentally any different from the original `switch` solution, i.e., you could rewrite that `if case` as a `switch` with a `default` case of `return .none` for exactly the same results. Both solutions require you to add support for different cases separately, and do not provide a general way to access the associated value of "any case". I still strongly urge the general problem to be solved using a `struct` or `class` containing the plain "type" `enum` and the value separately. – Arkku Aug 31 '18 at 11:28
  • I get a syntax error `Pattern cannot match values of type 'EnumName'` with Swift 5. – androidguy May 31 '20 at 21:46
34

You can use a guard statement to access the associated value, like this.

enum Line {
    case    Horizontal(Float)
    case    Vertical(Float)
}

let leftEdge             =  Line.Horizontal(0.0)
let leftMaskRightEdge    =  Line.Horizontal(0.05)

guard case .Horizontal(let leftEdgeValue) = leftEdge else { fatalError() }

print(leftEdgeValue)
16

I think you may be trying to use enum for something it was not intended for. The way to access the associated values is indeed through switch as you've done, the idea being that the switch always handles each possible member case of the enum.

Different members of the enum can have different associated values (e.g., you could have Diagonal(CGFloat, CGFloat) and Text(String) in your enum Line), so you must always confirm which case you're dealing with before you can access the associated value. For instance, consider:

enum Line {
    case Horizontal(CGFloat)
    case Vertical(CGFloat)
    case Diagonal(CGFloat, CGFloat)
    case Text(String)
}
var myLine = someFunctionReturningEnumLine()
let value = myLine.associatedValue // <- type?

How could you presume to get the associated value from myLine when you might be dealing with CGFloat, String, or two CGFloats? This is why you need the switch to first discover which case you have.

In your particular case it sounds like you might be better off with a class or struct for Line, which might then store the CGFloat and also have an enum property for Vertical and Horizontal. Or you could model Vertical and Horizontal as separate classes, with Line being a protocol (for example).

Arkku
  • 41,011
  • 10
  • 62
  • 84
  • 2
    Yes. I definitely think this would need language support. This is one case where the return value should just not be about varying types, but also varying arity. Possibly a varying count tuple as in case x ... return (1) case y ... return (0, "stuff") case z ... return (-1, 3.14, ["for", "good" ,"measure"]). And this might be better left for the compiler to support directly. Smelling a radar on its way ... – verec Jul 12 '15 at 10:08
  • @verec I think the correct answer for language support would be to keep associated values as they are, but allow adding stored properties to enums separately from associated values. That is: `enum Line { case horizontal, vertical; var value: CGFloat }` - basically syntactic sugar for generating the `struct`, which is currently the best solution. If associated values and stored properties were made mutually exclusive, the use of `init(value: CGFloat)` would then look like `var line: Line = .horizontal(value: 1)`. – Arkku Aug 31 '18 at 11:36
  • I think you may be interested in this question: https://stackoverflow.com/q/75903676/4423545 not for some use cases, but for theoretical interest in the topic. Check my answer there and you will see that we are able to deal with `CGFloat`, `String`, or two `CGFloat`. Those code is bad for usage in production, but still is interesting that THIS IS POSSIBLE to deal with different types :) – Andrew_STOP_RU_WAR_IN_UA Apr 01 '23 at 14:20
7

You can get the associated value without using a switch using the if case let syntax:

enum Messages {
    case ping
    case say(message: String)
}

let val = Messages.say(message: "Hello")

if case let .say(msg) = val {
    print(msg)
}

The block inside the if case let will run if the enum value is .say, and will have the associated value in scope as the variable name you use in the if statement.

stevex
  • 5,589
  • 37
  • 52
6

Why this is not possible is already answered, so this is only an advice. Why don't you implement it like this. I mean enums and structs are both value types.

enum Orientation {
    case Horizontal
    case Vertical
}

struct Line {

    let orientation : Orientation
    let value : CGFloat

    init(_ orientation: Orientation, _ value: CGFloat) {

        self.orientation = orientation
        self.value = value
    }
} 

let x = Line(.Horizontal, 20.0)

// if you want that syntax 'Line.Horizontal(0.0)' you could fake it like this

struct Line {

    let orientation : Orientation
    let value : CGFloat

    private init(_ orientation: Orientation, _ value: CGFloat) {

        self.orientation = orientation
        self.value = value
    }

    static func Horizontal(value: CGFloat) -> Line { return Line(.Horizontal, value) }
    static func Vertical(value: CGFloat) -> Line { return Line(.Vertical, value) }
}

let y = Line.Horizontal(20.0)
DevAndArtist
  • 4,971
  • 1
  • 23
  • 48
3

With Swift 2 it's possible to get the associated value (read only) using reflection.

To make that easier just add the code below to your project and extend your enum with the EVAssociated protocol.

    public protocol EVAssociated {
    }

    public extension EVAssociated {
        public var associated: (label:String, value: Any?) {
            get {
                let mirror = Mirror(reflecting: self)
                if let associated = mirror.children.first {
                    return (associated.label!, associated.value)
                }
                print("WARNING: Enum option of \(self) does not have an associated value")
                return ("\(self)", nil)
            }
        }
    }

Then you can access the .asociated value with code like this:

    class EVReflectionTests: XCTestCase {
            func testEnumAssociatedValues() {
                let parameters:[EVAssociated] = [usersParameters.number(19),
usersParameters.authors_only(false)]
            let y = WordPressRequestConvertible.MeLikes("XX", Dictionary(associated: parameters))
            // Now just extract the label and associated values from this enum
            let label = y.associated.label
            let (token, param) = y.associated.value as! (String, [String:Any]?)

            XCTAssertEqual("MeLikes", label, "The label of the enum should be MeLikes")
            XCTAssertEqual("XX", token, "The token associated value of the enum should be XX")
            XCTAssertEqual(19, param?["number"] as? Int, "The number param associated value of the enum should be 19")
            XCTAssertEqual(false, param?["authors_only"] as? Bool, "The authors_only param associated value of the enum should be false")

            print("\(label) = {token = \(token), params = \(param)")
        }
    }

    // See http://github.com/evermeer/EVWordPressAPI for a full functional usage of associated values
    enum WordPressRequestConvertible: EVAssociated {
        case Users(String, Dictionary<String, Any>?)
        case Suggest(String, Dictionary<String, Any>?)
        case Me(String, Dictionary<String, Any>?)
        case MeLikes(String, Dictionary<String, Any>?)
        case Shortcodes(String, Dictionary<String, Any>?)
    }

    public enum usersParameters: EVAssociated {
        case context(String)
        case http_envelope(Bool)
        case pretty(Bool)
        case meta(String)
        case fields(String)
        case callback(String)
        case number(Int)
        case offset(Int)
        case order(String)
        case order_by(String)
        case authors_only(Bool)
        case type(String)
    }

The code above is from my project https://github.com/evermeer/EVReflection https://github.com/evermeer/EVReflection

Edwin Vermeer
  • 13,017
  • 2
  • 34
  • 58