Decoupling enum case literals from `RawRepresentable`

i have some enums that support encoding to and decoding from BSON.

import BSON 

@frozen public
enum TagBits:Int32, Sendable
{
}

//  Inherits default implementations where Self:RawRepresentable,
//  Self.RawValue:BSONCodable
extension TagBits:BSONEncodable, BSONDecodable
{
}

due to the peculiarities of MongoDB, the RawValue must be Int32; there is no such thing as UInt32 in BSON.

normally, this isn’t a problem, but in this instance, TagBits really only makes sense to be specified in unsigned hex literals:

enum TagBits:UInt32, Sendable
{
    case polygon  = 0xC0_000000
    case square   = 0xC2_000000
    case triangle = 0xC3_000000
}

what would help here is if i could declare a custom conformance to RawRepresentable that’s layered on top of the literal constants, like:

extension TagBits:RawRepresentable
{
    public 
    var rawValue:Int32 { .init(bitPattern: self.literalValue) }

    public
    init?(rawValue:Int32) 
    {
        self.init(literalValue: .init(bitPattern: rawValue)) 
    }
}

why must RawRepresentable be tied to the type of the literal constants?

You can use any ExpressibleByIntegerLiteral (or StringLiteral) as an enum’s raw type, or you can implement RawRepresentable yourself. But for non-@objc enums, having the constants inline is only shorthand for the two switch statements that appear in RawRepresentable’s requirements, it has no other effect. I’m not saying it’s not nice shorthand, but there’s nothing special going on here.

EDIT: so if you could have your cake and eat it too, what would the rule actually be? What code turns 0xC2_000000 into an Int32?

the issue with that is that you need to keep the rawValue and the init(rawValue:) sides in sync with one another.

the rule would be the extension TagBits:RawRepresentable block in my original post. the justification is that RawRepresentable and its requirements are meant to work with generics (especially other protocols), but the literal constants are just there to provide a consistent mapping. so the mapping logic should go through init(literalValue:) and literalValue instead of piggybacking on RawRepresentable.

1 Like

Where are the Cx constants coming from? Can't this work for you instead?

enum TagBits: Int32, Sendable {
    case polygon  = -0x40_000000 // for 0xC0_000000
    case square   = -0x3E_000000 // for 0xC2_000000
    case triangle = -0x3D_000000 // for 0xC3_000000
}

Might be a bit inconvenient to go downwards (0, f, e, ...) rather than upwards (0, 1, 2), but this seems to be quite rare/niche use case.

2 Likes