@objc annotated enum to enable manual memory layout for enum types?

Hi, per swift/TypeLayout.rst at main · apple/swift · GitHub

enum Type: Int32 {
}

only matters to the rawValue type. Its memory layout is not impacted by the choice of the rawValue representation.

For me, there are some benefits of define exactly what sizes my enum going to occupy (better C interoperability, for one). Currently, I use @objc annotation to support that.

This has worked so far (with some downside, such as description are not as descriptive as naked enum), but want to get opinions from community whether this is a good choice and whether there are any guarantees provided with this workaround. Thanks.

(To see why and what exactly: swift-mujoco/Mjt.swift at main · liuliu/swift-mujoco · GitHub)

Wrong, ignore: I don't think @objc is needed here, enum E: Int32 value occupies exactly 4 bytes. The endianess of its storage is the same as the endianess of the corresponding base type (in this case Int32).

BTW, if you need to access that enum from C it's better to define it on the C side as NS_ENUM / CF_ENUM and import into Swift. You would be able to extend it (e.g. add a calculated property or CustomStringConvertibility) in Swift if needed. Automatic CaseIterable conformance might be a problem (IIRC) but it's not a huge hassle to define it manually.

Edit: a slightly different design if you don't need C interoperability and can tolerate some performance degradation:

enum MjtWarning: String, CaseIterable {
    case inertia
    case contactfull
    case cnstrfull
    case vgeomfull
    case badqpos
    case badqvel
    case badqacc
    case badctrl
    
    var index: Int {
        Self.allCases.firstIndex(of: self)!
    }
}

Hi, it doesn't based on the doc: swift/TypeLayout.rst at main · apple/swift · GitHub You can also try out the following code:

enum A: Int {
  case a
  case b
  case c
}
print(MemoryLayout<A>.size)

It should give 1 as Swift always uses smallest size for enum. It seems that rawValue is just for presentation, not for the underlying storage.

1 Like

You are correct, and there are no ways to control the representation of an enum other than @objc in today’s Swift, so go for it.

Indeed, my C++ background put me on the wrong track here.

Example
enum A: UInt32 {
    case
    a00, a01, a02, a03, a04, a05, a06, a07, a08, a09, a0a, a0b, a0c, a0d, a0e, a0f,
    a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a1a, a1b, a1c, a1d, a1e, a1f,
    a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a2a, a2b, a2c, a2d, a2e, a2f,
    a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a3a, a3b, a3c, a3d, a3e, a3f,
    a40, a41, a42, a43, a44, a45, a46, a47, a48, a49, a4a, a4b, a4c, a4d, a4e, a4f,
    a50, a51, a52, a53, a54, a55, a56, a57, a58, a59, a5a, a5b, a5c, a5d, a5e, a5f,
    a60, a61, a62, a63, a64, a65, a66, a67, a68, a69, a6a, a6b, a6c, a6d, a6e, a6f,
    a70, a71, a72, a73, a74, a75, a76, a77, a78, a79, a7a, a7b, a7c, a7d, a7e, a7f,
    a80, a81, a82, a83, a84, a85, a86, a87, a88, a89, a8a, a8b, a8c, a8d, a8e, a8f,
    a90, a91, a92, a93, a94, a95, a96, a97, a98, a99, a9a, a9b, a9c, a9d, a9e, a9f,
    aa0, aa1, aa2, aa3, aa4, aa5, aa6, aa7, aa8, aa9, aaa, aab, aac, aad, aae, aaf,
    ab0, ab1, ab2, ab3, ab4, ab5, ab6, ab7, ab8, ab9, aba, abb, abc, abd, abe, abf,
    ac0, ac1, ac2, ac3, ac4, ac5, ac6, ac7, ac8, ac9, aca, acb, acc, acd, ace, acf,
    ad0, ad1, ad2, ad3, ad4, ad5, ad6, ad7, ad8, ad9, ada, adb, adc, add, ade, adf,
    ae0, ae1, ae2, ae3, ae4, ae5, ae6, ae7, ae8, ae9, aea, aeb, aec, aed, aee, aef,
    af0, af1, af2, af3, af4, af5, af6, af7, af8, af9, afa, afb, afc, afd, afe, a7fff = 0x7FFF
}
print(MemoryLayout<A>.size) // 1

Which brings up a followup question: can we have that internal tag exposed?

print(A.a7fff.rawValue) // 0x7fff
// pseudocode:
print(A.a7ff.index) // 255 
Could be useful to have both raw value and integer representation.
a)
enum Color: Int {
    case red, green blue
    // want the name as well:
    var name: String {
        switch self {
            ... boilerplate ☹️
        }
    }
}

b)
enum Color: String {
    case red, green blue
    // want the index as well:
    var index: String {
        switch self {
            ... boilerplate ☹️ or slow code based on Self.allCases.firstIndex(of: self)!
        }
    }
}

c)
enum Color: String {
    case red, green blue
    // no boilerplate 😀
}
// Pseudocode:
Color.blue.index // 2 👍

Found a hacky way:

protocol EnumIndexable {
    var index: Int { get }
}

extension EnumIndexable {
    var index: Int {
        let size = MemoryLayout.size(ofValue: self)
        switch size {
        case 1: return Int(unsafeBitCast(self, to: UInt8.self))
        case 2: return Int(unsafeBitCast(self, to: UInt16.self))
        default: fatalError("TODO")
        }
    }
}

enum Color: String, EnumIndexable {
    case red, green, blue
}

func test() {
    print(Color.blue.rawValue) // "blue"
    print(Color.blue.index) // 2
    print()
}

test()