Is there a way to constrain the value of an inline...
# announcements
b
Is there a way to constrain the value of an inline class? Say, for example, I wanted to create an inline class
NonZeroInt
. I can do something like throw in
plus
,
minus
, etc. if the result ends up being 0...but how can I prevent the instance from being initialized with 0? I can't do an init block to verify it, and have to expose the primary constructor. I tried looking at
UInt
, which prevents you from assigning a negative number to it, but how does it enforce that? The primary constructor takes in an
Int
.
m
I think the only way is to make it a normal class. By its very nature/intent, an inline class will be 'inlined' directly to the wrapped type. So the compiler doesn't want to have to 'inline' constructor code too.
b
Hmm, that'd be a bummer. Do you know how
UInt
is able to restrict the assignment?
t
It doesn't...
Copy code
println((-3).toUInt())
// 4294967293
b
but trying to do this complains:
Copy code
val x: UInt = -10
t
It complains because
-10
is an
Int
. Change it to
10
and you get the same complaint. It's complaining about the type, not its range.
b
But how does it do that, given that it wraps an Int?
(and you're right, I hadn't noticed it complains about
10
the same way)
t
One of the main features of inline classes is that they are different types. Otherwise, we could just pass the underlying (Int in this case) type to anything that had an inline class and get around the typesafety it provides.
b
🤦 You're right, I think I've confused myself.
t
It is confusing. And like you, I wish they supported more in terms of ranges, validations, etc. I suspect they'll come up with that eventually (it's still experimental!).
b
I've got an inline class that models a value which has some custom rules for when it rolls over and how comparisons are done, and we realized it could be initialized with a bad value (like
-3
in `UInt`'s case), and want to make sure it's "corrected" when it's accessed
UInt
being in a separate package and marking the wrapped value as
internal
prevents that from happening in its case
So maybe what I'm looking for is just a way like that to prevent accessing the wrapped
value
directly
UInt
only gets away with an internal constructor by doing
Copy code
@Suppress("NON_PUBLIC_PRIMARY_CONSTRUCTOR_OF_INLINE_CLASS")
but it is seeming like hiding the internals of an inline class and making sure it's constructed properly can be necessary
And, even with this, I'd have to artificially move this definition to another module to prevent it from being accessed
f
Note that this is even the case in Rust with many types. You can create compile time constraints that are dropped at runtime in favor of performance but you need to live with the fact that they can be used incorrectly at runtime. There's no free lunch. ;)