I have a question regarding null-safety, I think n...
# language-evolution
s
I have a question regarding null-safety, I think nullable types are awesome, and it really allows you to define what is, and what might not have a value set, awesome. One scenario where this kinda feels janky for me, is when you know a field to not be nullable, or even a number of fields, but you still have to either use
!!
everywhere, use null checks, or ?.let etc, my question is: Is there a better way to handle this?
A case in point for me: I am using a data class, that is intended to be poolable, I know that at certain points, the data within said class will not be null, so I don't want to have to litter my code with null-safety checks.
for example:
Copy code
@Serializable
data class AbilityPacket(
    var abilityId: Int? = null,
    var targeting: AbilityTargeting? = null,
    var entity: Entity? = null
) : NetworkPacket {
    override fun reset() {
        abilityId = null
        targeting = null
        entity = null
    }
}
(NOTE: I am not asking how is this done now, I'm starting a discussion about how this language could evolve to better suit this use-case)
Due to proper encapsulation, I know use sites of this class will definitely have all fields set, but I still have to use null safety anyway.
One idea I had, why not enable use of the Java-nullable type, for example:
Copy code
data class AbilityPacket(
    var abilityId: Int! = null,
    var targeting: AbilityTargeting! = null,
    var entity: Entity! = null
) ......
which would allow us to decide whether the usage of this type is null-safe or not.
y
There's
lateinit
, but obviously it doesn't help here because of your
reset
. You can use some property delegation to help out.
s
Yea lateinit is similar to this, it basically hands over the trust to the developer: "Make sure this property is available, before we access it"
This is more of a resetable lateinit
Could imagine something like this:
Copy code
@Serializable
data class AbilityPacket(
    reinit var abilityId: Int,
    reinit var targeting: AbilityTargeting,
    reinit var entity: Entity
) : NetworkPacket {
    override fun reset() {
        ::abilityId.reset()
        ::targeting.reset()
        ::entity.reset()
    }
}
e
Copy code
class Packet(
    var _id: Int?,
) {
    val id: Int
        get() = _id!!
is possible of course, but the better design IMO would be to create a separate type with non-nullable properties, which you convert into after validation
💯 3
s
Good idea
p
You could potentially use a value class wrapping the type with nullable properties and expose them with non null inline properties.
s
How would that look?
p
Copy code
@JvmInline
value class PopulatedPacket(@PublishedApi internal val packet: AbilityPacket) {
    inline val abilityId: Int get() = packet.abilityId!!
    inline val targeting: AbilityTargeting get() = packet.targeting!!
    inline val entity: Entity get() = packet.entity!!
}
s
I thought value classes were only for primitive types
p
https://pl.kotl.in/4gD-IeXk1 also for non-primitives
s
Oh nice
What even is the purpose of a value class lol
Is it to enrich a value but its all inlined