-
Notifications
You must be signed in to change notification settings - Fork 364
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Inline classes #103
Inline classes #103
Conversation
proposals/inline-classes.md
Outdated
- String enum for Kotlin/JS (see [WebIDL enums](https://www.w3.org/TR/WebIDL-1/#idl-enums)) | ||
- Units of measurement | ||
- Result type (aka Try monad) [KT-18608](https://youtrack.jetbrains.com/issue/KT-18608) | ||
- Inline property delegates |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd say that this and the next item need clarification/some examples
|
||
- Inline class must have a public primary constructor with a single value parameter | ||
- Inline class must have a single read-only (`val`) property as an underlying value, which is defined in primary constructor | ||
- Underlying value cannot be of the same type that is containing inline class |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd say, having undefined (recursively defined), e.g. generics with an upper bound equal to the class should be prohibited as well.
Or like this
inline class Id<T>(val x: T)
inline class A(val w: Id<A>)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree with restriction about recursively defined generics, thanks!
About the example, it's OK if we'll map Id<T>
always to Any
, I'll add a note about this
proposals/inline-classes.md
Outdated
- Underlying value cannot be of the same type that is containing inline class | ||
- Inline class cannot have `init` block | ||
- Inline class must be final | ||
- Inline class can implement only interfaces |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder what happens with non-abstract interface methods.
Do we need to generate DefaultImpls in the case?
proposals/inline-classes.md
Outdated
| **Reference type** | Underlying type | Underlying type | Wrapper type | | ||
| **Nullable reference type** | Underlying type | Wrapper type | Wrapper type | | ||
| **Primitive type** | Underlying type | Wrapper type | Wrapper type | | ||
| **Nullable primitive type** | Underlying type | Wrapper type | Wrapper type | |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here, "wrapper" term is a bit ambiguous since underlying type is a wrapper too :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree, I think I'll replace it to a "Boxed type"
fun test(vararg foos: Foo) { ... } // should be an error | ||
``` | ||
|
||
## Java interoperability |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd also add some short description/examples of how these wrappers look like and where the methods' bodies are
|
||
Compiler will generate original `equals` method that is delegated to the typed version. | ||
|
||
By default, compiler will automatically generate `equals`, `hashCode` and `toString` same as for data classes. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
BTW, are data inline classes allowed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, they can be allowed... I'm not sure it makes sense, I'll add a note
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Related - is there a reason sealed classes can't support inlining as long as every subclass is also inline?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@hzsweers Since there's no instance, how would you do is
checks at runtime for use in a when
or boolean expression?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would assume that they would work the same way is
checks would work in normal use (I didn't see that listed as a listed caveat and assumed that meant it was supported, though admittedly I don't see how)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I expect is
check to not be supported on inline classes since they are identity-less, as said here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@LouisCAD Could you please elaborate? Type check should work the same as it works for primitives.
<expr> is InlineClassType
There are two options:
- type of
expr
is statically known to be an inline class type, then type check is trivial - type of
expr
is statically unknown, so if it's actually of an inline class type then it should be boxed, therefore we do type check for boxed type
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@zarechenskiy Does that mean that anytime you put an inline class
value to a reference that is higher in the class hierarchy (like Any
), it is boxed, therefore, no longer inline for this usage?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@LouisCAD Yes, same as for primitives
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
proposals/inline-classes.md
Outdated
|
||
| Underlying Type \ Declaration Site | Not-null | Nullable | Generic | | ||
| --------------------------------------------- | --------------- | ---------------- | ---------------- | | ||
| **Reference type** | Underlying type | Underlying type | Wrapper type | |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So, we decided that fun foo(x: List<Name>)
here it compiles to List<Name>
from Java POV?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I'll add an example
@@ -0,0 +1,163 @@ | |||
# Inline classes |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd add a section about our arrays struggles
The following restrictions are related to the usages of inline classes: | ||
|
||
- Referential equality (`===`) is prohibited for inline classes | ||
- vararg of inline class type is prohibited |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why vararg
is not allowed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In general would be good to provide some basic comments about particular limitations/restrictons
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because it's not clear what type should it have. I'll add a section about arrays and then cover this question
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @zarechenskiy, can you expose some clarification about List<T>
, Array<T>
and vararg T
?
Currently, inline classes must satisfy the following requirements: | ||
|
||
- Inline class must have a public primary constructor with a single value parameter | ||
- Inline class must have a single read-only (`val`) property as an underlying value, which is defined in primary constructor |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is it impossible to use var
? Is it technical limitation or design decision?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd say it's both :)
Short answer is that because inline classes are identityless, they don't have state
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
inline class Name(var s: String)
val name = Name("Alan")
name.s = "Bob"
Is name
read-only or not?
How estimate the impact of change the Name.s
property in a shared boxed type?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any problems with single-property class which violates other statements?
inline class Something(t: T) {
init {
// some init, checks
}
val value: U = transform(t)
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Miha-x64 This is the same as if we have private primary constructor + public secondary constructor, therefore we are going to prohibit such cases for now
proposals/inline-classes.md
Outdated
Currently, there is no performant way to create wrapper for a value of a corresponding type. The only way is to create a usual class, | ||
but the use of such classes would require additional heap allocations, which can be critical for many use cases. | ||
|
||
We propose to support identityless inline classes that would allow to introduce wrappers for a values without additional overhead related |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typo "a values"
|
||
- Native types like `size_t` for Kotlin/Native | ||
- Inline enum classes | ||
- Int enum for [Android IntDef](https://developer.android.com/reference/android/support/annotation/IntDef.html) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How does this suppose to work?
Of course, you can create one inline class for each int, but it's not clear how this help with enum case.
Some kind enums based on inline classes or sealed would be really helpful tho
Are inline enums part of this proposal?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe inline enum classes deserve another proposal, but I'll put here a bit information about them
proposals/inline-classes.md
Outdated
- Inline class must have a public primary constructor with a single value parameter | ||
- Inline class must have a single read-only (`val`) property as an underlying value, which is defined in primary constructor | ||
- Underlying value cannot be of the same type that is containing inline class | ||
- Inline class cannot have `init` block |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why would init
block be prohibited? Isn't it where checks for the single value parameter would take place?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can't really check anything if at runtime the class isn't actually instantiated. Remember, it's inline
after all.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because of Java interop. We can't ensure execution of init
block when value comes from Java. Consider this case:
// file: sample.kt
inline class Positive(val u: Int) {
init { assert(0 < u) }
}
fun foo(p: Positive) {} // in JVM bytecode `p` is just `int`
// Java
...
SampleKt.foo(-10) // it's OK from Java POV
Also, this is the reason for public primary constructor requirement.
I'll write about this in the proposal
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@zarechenskiy Why not rule out giving the underlying value of an inline class in Java at compile time? Java would only be allowed to receive inline classes values as the underlying type, but only Kotlin would be allowed to invoke the inline "constructor".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe this question should be discussed in the issue.
I see here several options:
- we relax this requirement and have inconsistent behaviour
- we introduce some kind of assertions as for nullable values that come from Java
- use conversions for values that come from Java
Each of these options have cons, but it's worth to discuss it anyway
} | ||
``` | ||
|
||
Compiler will generate original `equals` method that is delegated to the typed version. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It also makes sense to generate component1
function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I consider harmful expose the private value for the UInt
example, or the component1
must have the same visibility of the value
attribute
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you please elaborate? Do you have a use case? Maybe it's a point to allow inline data classes
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, you're right, auto component1
is useless.
Vice versa, it may be used by hand, for example:
inline class IntPair(private val value: Long) {
operator fun component1() = value ushr 32
operator fun component2() = value and 0xFFFFFFFF
}
} | ||
|
||
|
||
inline class InlinedDelegate<T>(var node: T) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Inline class must have a single read-only (val) property as an underlying value, which is defined in primary constructor
There is an inconsistency
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd say that this is an open question. We can support mutable inline classes if they will be used only with delegated properties. Currently we're looking for use cases
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @zarechenskiy,
having a delegate with a backing field is interesting, however rules exceptions are harder to explain and understand.
class Test { | ||
void test() { | ||
String name = SampleKt.simple(); | ||
List<Name> ls = Samplekt.generic(); // from Java POV it's List<Name>, not List<String> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the Kotlin code Name
is a parameter type.
In Java code Name
is a return type.
Name
is a parameter or a return type?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, indeed, Name
and List<Name>
should be used in return type position in Kotlin, thanks!
proposals/inline-classes.md
Outdated
@@ -128,31 +213,79 @@ Since boxing doesn't have side effects as is, it's possible to reuse various opt | |||
### Type mapping on JVM | |||
|
|||
Depending on the underlying type of inline class and its declaration site, inline class type can be mapped to the underlying type or to the | |||
wrapper type. | |||
|
|||
boxed type. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Annotate each parameter using an explicit annotation with a boxed type can really improve code readability.
This code should be injected by compiler in the same manner of @Nullable
annotation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Awesome 👍 👍 👍
I have a draft of same KEEP, will do couple more rounds of review, so far looks fantastic!
``` | ||
- Inline class cannot have `init` block | ||
- Inline class must be final | ||
- Inline class can implement only interfaces |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe explicitly clarify that inline class cannot extend other classes?
- Inline class cannot have backing fields | ||
- Hence, it follows that inline class can have only simple computable properties (no lateinit/delegated properties) | ||
- Inline class cannot have inner classes | ||
- Inline class must be a toplevel class |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hm, that's pretty limiting, why is that?
Use-case: inside a class I want to use an inline class as an implementation detail, but I don't want other classes to know about it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed, this is too limiting, thanks!
It's definitely can be a nested member, I'll think more about it
|
||
The following restrictions are related to the usages of inline classes: | ||
|
||
- Referential equality (`===`) is prohibited for inline classes |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But why? Shouldn't it just do referential equality check on underlying object?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@artem-zinnatullin That would be dangerous. Let's say two different inline classes both wrap an int which happens to have a value of 0
. It would be considered equals reference while not representing the same thing at all?
Allowing reference equality would defeat the purpose of inline classes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point 👍
I was thinking about it from Java perspective where I'll still be able to do reference comparison, I think it's fine to prohibit ===
from Kotlin side unless someone comes up with a good use-case.
fun test(vararg foos: Foo) { ... } // should be an error | ||
``` | ||
|
||
## Java interoperability |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess we need JS and Native interoperability sections as well :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At some point we'll have it :)
For now, I'm going to put more information about common and expect/actual inline classes
Again, method `foo` have type `int` from Java POV, so we can indirectly create values of type `Positive` | ||
even with the presence of private constructor. | ||
|
||
To make behaviour more predictable and consistent with Java, we demand public primary constructor and restrict `init` blocks. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not allow init blocks and private constructors by enforcing boxed type when used from Java? This would work similarly to inline functions that are inline when invoked in Kotlin, but not when called from Java or the debugger.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, this approach is nice, some kind of "closed world" for inline classes.
But note that inline functions cannot be virtual, while inline class types can be used everywhere. To enforce boxing for Java we'll have to duplicate methods, plus there are some issues with arrays (if we'll allow them, we'll have to box each element of an array on the border)
I'd say that we should think more about this approach, at least to understand our design space
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@zarechenskiy It should not duplicate methods, but rather, add a method that takes the boxed type that is visible in Java, which unboxes the value and call the static method that takes the unboxed value.
In case the defined method is also inline
, the method with the boxed type would contain all the logic directly, since no method for the unboxed type would be generated (since they would all be inlined at call places).
inline class A<T : A<T>>(val x: T) // error | ||
``` | ||
- Inline class cannot have `init` block | ||
- Inline class must be final |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why would open inline classes not be allowed?
Couldn't they be extended by other inline classes that would only be additional API wrappers that could add more restrictions and more functions?
Such restrictions added to an inline class
extending another, open
one could be:
- Narrowing down the underlying value to a child type of the one defined in the parent
inline class
- Adding additional checks/logic to
init
blocks (if they are allowed) - Overriding open methods from the parent
inline class
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How runtime should resolve which subtype of inline class it deals with?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Miha-x64 The inline classes have a boxed (non inline) type for cases where you use generics or super types like Any
, so either it can be done statically, without autoboxing, or it is boxed, so behaves like a regular class, until it reaches an auto-unboxing place again.
To make an analogy to something existing, it would work the same as primitive number types, who have their boxed types extending the Number
class.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@LouisCAD to assign Int value to a Number variable, you must box it, which eliminates the whole purpose of inline classes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Miha-x64 Does boxing an Int
somewhere in your program eliminates the whole purpose of primitive, non boxed integers in your whole program? I don't think so, and the same applies for inline classes.
If you always use them in ways that force autoboxing, then, yes, its pointless, but you'll usually not write autoboxing code everywhere.
|
||
The following restrictions are related to the usages of inline classes: | ||
|
||
- Referential equality (`===`) is prohibited for inline classes |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point 👍
I was thinking about it from Java perspective where I'll still be able to do reference comparison, I think it's fine to prohibit ===
from Kotlin side unless someone comes up with a good use-case.
|
||
Compiler will generate original `equals` method that is delegated to the typed version. | ||
|
||
By default, compiler will automatically generate `equals`, `hashCode` and `toString` same as for data classes. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is an interesting conflict with this section about equals/hashCode and implementing interfaces.
Let me quote:
Compiler will generate original
equals
method that is delegated to the typed version.
and
Inline class can implement only interfaces
Which means that comparing two different inline classes that use same underlying type and implement same interface can result in wrong behavior because of comparing underlying values which can be indeed equal.
Example:
interface Bytable {
fun toBytes(): ByteArray
}
inline class UserId(private val id: String): Bytable {
override fun toBytes(): ByteArray = "userId:$id".toByteArray()
}
inline class OrderId(private val id: String) : Bytable {
override fun toBytes(): ByteArray = "orderId:$id".toByteArray()
}
fun f(bytable1: Bytable, bytable2: Bytable) {
assert(bytable1 == bytable2)
}
fun test() {
f(bytable1 = UserId("123"), bytable2 = OrderId("123"))
}
fun main(args: Array<String>) {
test()
}
If you change inline
classes to data
classes, test will fail.
But I think it'll pass with inline
classes which is interesting.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm intrigued as to how exactly unsigned types will work under this proposal.
I can see how one could do something like this:
val u = UInt(42)
but how then would you access the value
property if it's private or is the idea that u
itself would be treated as the value?
Also, if you wanted to create a UInt that was greater than Int.MAX_VALUE what value would you present to the primary constructor? Presumably one would not be expected to work out the appropriate negative Int value corresponding to this so would Int literals be extended to permit higher values up to UInt.MAX_VALUE?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think number literals for unsigned values are part of this proposal, but I guess if they are implemented they need to allow for higher values than there signed parts. How else are you gonna get values for ULong
. For UInt
you could use a function converting a long
. That being said, maybe you are doing something wrong if you have to hardcode numbers that big.
Inline classes are indirectly inherited from `Any`, i.e. they can be assigned to a value of type `Any`, but only through boxing. | ||
|
||
Methods from `Any` (`toString`, `hashCode`, `equals`) can be useful for a user-defined inline classes and therefore should be customizable. | ||
Methods `toString` and `hashCode` can be overridden as usual methods from `Any`. For method `equals` we're going to introduce new operator |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wait, how will overridding toString()
, hashCode()
and equals()
work?
inline class UserId(private val id: String) {
override fun toString() = "UserId($id)"
}
fun test(userId: UserId) {
System.out.println(userId) // ?
}
It'll require a wrapper class which is what inline class
is trying to remove
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@artem-zinnatullin It'll be a static method that'll be called at invocation places, so still no additional allocation. If you put the inline class
instance in a more generic type, it'll automatically be boxed (like Int
and other primitives are), so it'll call the virtual version of toString()
and alike methods.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How would System.out.println()
or any other runtime-linked use-site know about any of these methods?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@artem-zinnatullin userId
will be boxed into an instance of UserId
wrapper class, then this object instance is being passed to println
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
aha, I guess it falls into nullable/platform
-type case.
Still don't like this whole wrapping story, gotta think about it more.
Currently, inline classes must satisfy the following requirements: | ||
|
||
- Inline class must have a public primary constructor with a single value parameter | ||
- Inline class must have a single read-only (`val`) property as an underlying value, which is defined in primary constructor |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the reason for the single-value restriction?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ricmf It can only work on a single reference. If you have more than one property, you need two references, and that only works with a real enclosing instance (that is allocated, not inline). However, you can perfectly make an inline class enclosing a Pair
if it makes sense.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe it is possible to use multiple values, I am thinking about it.
It should work like JPA Embeddable
class , JVM escape analysis should avoid extra allocation, probably this is out of scope and we should wait for Valhalla.
The problem become tricky in presence of annotations.
```kotlin | ||
inline class Name(val s: String) | ||
|
||
fun foo(n: Name?) { ... } // `Name?` here is mapped to String |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't it be String?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, here we know that null
can be associated only with the type Name?
, because underlying type of the inline class Name
is a non-null reference type.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is similar to what we have for IntArray
/LongArray
/...: IntArray?
is just an array of primitive types
Discussion of this proposal is held in this issue.