I have a function I'm trying to create that needs ...
# general-advice
m
I have a function I'm trying to create that needs to essentially a nullable lateinit with inlined generics:
Copy code
inline fun <T1, T2, R> BsonReader.read(
    prop1: KProperty<T1>, read1: BsonReader.(BsonType) -> T1,
    prop2: KProperty<T2>, read2: BsonReader.(BsonType) -> T2,
    create: (T1, T2) -> R
): R {

    readStartDocument()
    val name1 = prop1.encodedName
    val name2 = prop2.encodedName
    var val1: T1
    var val2: T2
    var readVal1 = false
    var readVal2 = false
    var type = readBsonType()
    while (type != BsonType.END_OF_DOCUMENT) {

        when (val name = readName()) {
            name1 -> { val1 = read1(type); readVal1 = true }
            name2 -> { val2 = read2(type); readVal2 = true }
            "_id" -> readObjectId()
            else -> error("unknown field name: $name")
        }

        type = readBsonType()
    }

    readEndDocument()
    require(readVal1) { "no value for $name1" }
    require(readVal2) { "no value for $name2" }
    return create(val1, val2) // Error: variable must be initialized
}
At the end of the function I know that the variable was initialized, but the compiler can't pick that up. I can't make the variable nullable otherwise I will have to do a null assertion and the point is to allow the generics to be nullable at the source Does anyone know of a way to assert that the
val1
and
val2
are initialized to the compiler? The only thing I've been able to think of is trying to pass the variables into Java code, but it seems really clunky to try that if that'd even work
a
what about splitting out that while loop into a separate function, that only tries to find one property at a time? It will still loop over the elements, but when it finds a property then it can call
read()
and early-return the value. If it reaches END_OF_DOCUMENT, then throw an exception - "property $propName was not found". This way, you handle the three cases: 1. the property is found and the value is not null, 2. the property is found and the value is null, 3. the property isn't found (throw an exception)
for example
m
That's an interesting option I'll need to put more thought into. The issue I see so far is that order isn't guaranteed so the read functions may hit
val2
before
val1
and just throw an exception
I could possible read all of the values then pull one out at a time and cast it 🤔
Thanks for the help!
fist bump 1
a
I could possible read all of the values
Ahh yeah, that could work. I assumed that it would be too heavy to load the whole document into memory, but actually that's not true. Also, you could only retain values for the specified keys.
m
I ended up using a proxy Java class to store the values so that they aren't forced to be initialized or have a nullable type:
Copy code
inline fun <T1, T2, R> BsonReader.read(
    prop1: KProperty<T1>, read1: BsonReader.(BsonType) -> T1,
    prop2: KProperty<T2>, read2: BsonReader.(BsonType) -> T2,
    create: (T1, T2) -> R
): R {

    readStartDocument()
    val name1 = prop1.encodedName
    val name2 = prop2.encodedName
    val values = BsonValues<T1, T2>()
    var type = readBsonType()
    while (type != BsonType.END_OF_DOCUMENT) {

        when (val name = readName()) {
            name1 -> values.val1 = read1(type)
            name2 -> values.val2 = read2(type)
            "_id" -> readObjectId()
            else -> error("unknown field name: $name")
        }

        type = readBsonType()
    }

    readEndDocument()
    require(values.is1Set) { "no value for $name1" }
    require(values.is2Set) { "no value for $name2" }
    return create(values.val1, values.val2)
}
Sucks to go against the Kotlin system, but it's the only way to maintain full functionality that I could see