-
Notifications
You must be signed in to change notification settings - Fork 360
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
Expose boxed inline value classes in JVM #394
Comments
Exposing Kotlin inline classes to Java prior to the release of Valhalla value classes is dangerous as it will expose a new category of defects regarding their identity. While Kotlin marks these usages as errors preventing compilation, the Java world only does the same for Valhalla value classes as they also don't have object identity. If inline classes are exposed before Valhalla value classes then the following new categories of defects will be introduced (assume Equality Checks
The above fails to compile in Kotlin but this would be allowed in Java (unless it's exposed as a Valhalla value class). Java would auto-box each access so that the reference equality fails even though they come from the same value. Even worse is that if the reference to a Kotlin inline class instance is passed around in the Java world then reference equality checks will pass but will start to fail when crossing the Kotlin-Java boundary such as when passing the reference to a Kotlin function that stores it in a local unboxed variable before passing it to some other Java code that auto-boxes it again. While reference equality checks aren't too common in Kotlin, they are quite common in Java code. Identity hashCode
Synchronization
For this hypothetical scenario, we want to allow concurrent access when performing unrelated operations so we only synchronize on the bankId when performing banking operations etc. Conclusion |
I didn't have time to dig in deeply today, but I'll just echo that the timing of this in relation to the forthcoming changes around Valhalla as presented at JVMLS recently is risky. Surely little harm would come from waiting to see what they ship first? |
Thanks for the fast feedback! We're indeed monitoring the upcoming changes in Valhalla, but there are a few comments. The goal of this KEEP is to expose a Kotlin API better to the JVM world, something in which we were lacking. Since right now Kotlin inlines the uses of Once value classes land (and the corresponding JVM version is somehow common, which could take years), this problem does not go away completely, if the inlining mechanism is still in place. One possible future in that case is:
The good news is that we can turn the "boxed variants" of the inlined classes also into a In summary: this KEEP moves the needle a bit into improved compatibility with Java. The best solution would indeed involve value classes, but there is a risk on waiting for that JVM to be common; so I think a good approximation is to expose the boxed variant with the same caveats that exist now, but mark then as |
I think it's important to mention that the improved performance of inline classes is the only reason they are used since regular classes or data classes provide a better experience in pretty much every way when performance isn't the primary concern. As users, we need to be extra careful with inline classes to avoid auto-boxing otherwise the performance can be worse than regular classes negating the only purpose for their use. Inline classes provide fewer capabilities than regular classes, they add surprises such as JVM conflicts (eg. constructor that accepts inline class vs constructor that accepts underlying value etc.), and introduce restrictions such as avoiding operations that rely on their missing identity. So when inline classes are chosen, companies are choosing to pay the price of increased complexity with the only benefit of gaining performance. The need to wait for Valhalla value classes to become common seems to be a misconception because that refers to a different target audience. The type of places that use bleeding-edge features like inline classes are the type of places that upgrade to the latest JVM release. After all, inline classes are strictly a performance choice, and upgrading the JVM improves performance aligning with the target audience of inline classes since performance is so important for this audience. The decision should be based on when Valhalla value classes are released on the JVM instead of when that version of the JVM becomes common. From a design perspective, it doesn't make sense to choose inline classes if we want to expose these to the Java world without project Valhalla. That's because using these values from Java would auto-box them and negate any performance benefit of inline classes likely resulting in worse performance than regular classes (as that would have just passed a reference) and thus nullifying our reason for choosing inline classes in the first place. However, if these were exposed as Valhalla value classes, they could take advantage of Valhalla JVM optimizations so they should continue to have a performance benefit and reason to exist. Regarding backward compatibility, unlike inline classes, it's my understanding that the Java team plans to enumerate the classes that are intended to be converted to Valhalla value classes (such as the primitive wrapper classes) and add warnings whenever they are used in a future incompatible way like performing operations that rely on their identity. The behavior of some of these operations will change. For example, Regarding compatibility with Java, exposing inline classes to Java without Project Valhalla would provide a worse compatibility experience as it would introduce the large swath of defect categories that I talked about in my first comment. When we choose inline classes, we know that they will meet our performance reasons for choosing them and are fairly safe since the Kotlin compiler helps reduce dangerous usage patterns but these implicit requirements will no longer be valid if they are exposed in Java without the safety and performance of Project Valhalla. |
Just a quick note that the On a similar note regarding the first 2 design goals:
Hopefully these new abilities won't be accessible from Kotlin code. While they are needed by Java to maintain its real type, any usages from Kotlin would only encourage dangerous identity-related defects and also introduce performance issues since the boxed variants will be automatically unboxed by the Kotlin compiler at the first opportunity such as when passing the boxed value to a function that operates on the inline class type etc. |
Let me reiterate why I think that, regardless of the upcoming value class support in the JVM, exposing a "non-inlined" version of Kotlin's People want to have a way to use the best representation they can get in Kotlin, while keeping "good enough" compatibility with other languages in the JVM, especially Java. This can be seen by the many comments in the corresponding YouTrack ticket and the ones related to it. Right now, if you want to expose your API with a wrapper type and it being accessible in Java, you can use a data class StudentId(val id: Long)
class Student(val id: StudentId, val name: String, ...) This KEEP amounts to proposing that if you decide to use a As I mentioned above, it is possible for us to generate a "real" JVM value class, which shall keep the identity-based behavior on JVMs which do not support the feature. The design document specifies that value classes are distinguished by setting the
|
On this discussion, I would like to distinguish two different positions which I feel are getting mixed up:
On that note, this KEEP gives you the option to expose boxed variants (well, in all truth, exposing the constructor is not optional in the current design, but any operations are). If your target with a library, or your application, uses only Kotlin, you may not want to do this. But again, there are library authors which want this boxed variant to the available, knowing the risks that it entails with respect to identity. |
I was more coming from the angle of that boxing of a value class is an already problematic concern in just Kotlin (as mentioned above), that this is only going to increase that problem since they're generally thought of as a performance optimization but can actually be the opposite. But if we're just going to say that |
Just to clarify: this KEEP changes nothing about the view Kotlin has of an inline value class. All of the “boxed variants” of callables are only available to Java, the Kotlin compiler hides them in the same way that it hides the boxed class it currently creates. Update: clarification added in the KEEP text. |
This is an issue to discuss exposing boxed inline value classes in JVM. The current full text of the proposal can be found here.
We propose modifications to how value classes are exposed in JVM, with the goal of easier consumption from Java. This includes exposing the constructor publicly and giving the ability to expose boxed variants of operations.
The text was updated successfully, but these errors were encountered: