google / flatbuffers

FlatBuffers: Memory Efficient Serialization Library

Home Page:https://flatbuffers.dev/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Union as sealed class in Kotlin

michaelcosta opened this issue · comments

This will likely end up as another for @paulovap and something I will likely attempt to add here or in the KMP fork (#7185) but:

I was wondering why a union of types generates a companion object of vals instead of an enum (swift) or sealed class? We have a use case where MessagePayload is a union of valid request/response types. Swift is able to use the enum name as a type parameter they can perform a switch statement or alias for but kotlin has the UByte values as vals within the companion object on our MessagePayload. Is there a specific reason why Kotlin (and Java) have these as static finals vs enum?

Kotlin does what java does for compatibility of the flatbuffers runtime.

Java uses constants over Enum because enums takes more space in the heap and are slightly more inefficient in comparisons.

Can you describe it a little more how is this a problem for you? Is it just ergonomics (like exhausting items in a switch) or is there something else?

It's mostly the ergonomics of it. We have a generic request builder that takes in a lambda for the inner payload and (on swift) the BridgePayloadType enum.

// Can't do this as nicely on Android as it's just UByte (would love to have some safety around it)
       private let requestTypeEnum: BridgePayloadType 
...
        let envelop = BridgeMessageEnvelope.createMessageEnvelope(
            &builder,
            payloadType: requestTypeEnum,
            payloadOffset: requestPayloadBuilder(requestId, &builder)
        )
        builder.finish(offset: envelop)

I didn't really think about the efficiency / heap size when raising the issue so thanks for the reasoning as to why it is created in this way. My concern was just losing the type safety of using union which we get from the swift side

One option I was thinking about is to take advantage of value classes instead of primitive type, we could keep performance and add some type safety in exchange of a little bit bigger binary size. Something on the lines of:

// schema: enum Color:ubyte (bit_flags) { Red = 0, Green, Blue = 3}
@JvmInline
value class Color private constructor(val data: UByte) {
    companion object {
        val Red: Color = Color(1u)
	val Green: Color = Color(2u)
	val Blue: Color = Color(8u)
        val names : Map<Color, String> = mapOf(Red to "Red", Green to "Green", Blue to "Blue")
        fun name(e: Color) : String = names[e]!!  
    }
}

data class Brush(val size: Int, val color: Color)

fun main() { println(Brush(10, Color.Red)) }

Would that help with your use-case?

Yes that would be perfect actually, the ability to maintain type safety would be great and feels like it conforms a bit better to the recommended usage of union as it's more clear that it's scoped to that outer value class when using in the builder

This issue is stale because it has been open 6 months with no activity. Please comment or label not-stale, or this will be closed in 14 days.

This issue was automatically closed due to no activity for 6 months plus the 14 day notice period.