Inline classes: 'var' to be deprecated?

So in Kotlin 1.4.30, some changes occurred with inline classes, mostly for futureproofing with regard to JVM value classes. However, among these changes is a new warning issued whenever you declare a ‘var’ type property on an inline class:

Kotlin: ‘var’ properties with value class receivers are reserved for future use.

This implies that a very helpful use-case may be deprecated in the future. Suppose I’m working with JNI, where addresses are frequently passed and returned via Longs. In such a context, this syntax is very intuitive and helpful:

inline class Grid(val ptr: Long) {
    var width: Int
        get() = GridNative.getWidth(ptr)
        set(v) { GridNative.setWidth(ptr, v) }
}

Another example is in the context of sun.misc.Unsafe. (I know what you’re thinking, but bear with me.) In my case, it’s necessary in the context of game development, communicating with C++ libraries, etc, in a way that’s reasonably fast enough for a game engine. The popular library LWJGL, included in Minecraft and which makes use of Kotlin, also makes abundant use of Unsafe. So granted that, suppose I were to generate code like this:

inline class Vertex(val ptr: Long) {
    var x get() = UNSAFE.getFloat(ptr + 0); set(v) { UNSAFE.putFloat(ptr + 0) }
    var y get() = UNSAFE.getFloat(ptr + 4); set(v) { UNSAFE.putFloat(ptr + 4) }
    var z get() = UNSAFE.getFloat(ptr + 8); set(v) { UNSAFE.putFloat(ptr + 8) }
}

Suddenly I could work with “arrays of structs” in a relatively safe and intuitive fashion, all while staying performant due to JVM method inlining! And this is in fact what I’m currently doing. The alternative of defining a bunch of methods would be quite stifling, especially considering the utility of compound assignment operators.

Now you might be tempted to argue that value classes would fill this void, but so long as they’re allocated on the JVM heap, they cannot be passed to OpenGL or other graphics libraries to transfer the data to the graphics card. (Since value classes can refer to non-value classes, one would assume that they will not in fact be allocatable in native memory, at least not without an ‘unsafe’ construct similar to C#.) There are also currently no JVM guarantees on the ordering of fields in memory!

All this is to say: What can we expect from this warning? Will ‘var’ be converted to a compile-time error in Kotlin 1.5, or can I safely suppress this warning and go along my merry, hacky way? As long as it doesn’t become completely disallowed, I’ll be happy.

1 Like

If they do completely disallow, or break your use case with updates, I think it’d be just as happy of an outcome if they provide their standard nice migration facilities. A lot of times it’s an auto migration combined with some ABI backward compatibility.

When you say migration facilities, you mean right-clicking the line in the IDE and converting the properties to methods? That would certainly be helpful to others, but it’s definitely still a syntactic downgrade.

From a syntax perspective, I’d really prefer this:

vertex.x += 5f

Over this:

vertex.x(vertex.x() + 5f)

And as the property names get longer, the worse this gets:

something.mySpecialProperty(something.mySpecialProperty() * 2f)

There are probably other use cases for var properties on inlines, maybe where the wrapped object is an index into a static list, etc. It’d be appreciated if we as the developers could choose the best syntax, rather than have the language enforce what it deems best. That’s of course assuming there’s no technical reason for the new limitation. I certainly don’t see this as being more obtuse than the existing DSL/implicit-receiver syntax.

As for ABI compatibility - I suppose I could require users linger on an older Kotlin version, but of course that’d be an absolute last resort for a new library. I could also try generating bytecode directly rather than generating Kotlin, but that sounds like a whole new layer of complexity that’s best avoided. Not sure I’d get the same level of code completion support either. Just a lot of work for what seems like a simple warning.

1 Like

What I mean is that you will probably want to migrate to the new/better/fixed thing. I doubt it will be var properties being used as Java style like in those examples–but I don’t know what ideas are floating around for mutation methods.

I suspect the reason the warning exists is for future use in or overlapping with “mutation” function design.

1 Like

I see, I suppose I’d need more information to have an opinion. But as it stands, I dislike the idea of losing custom setters to accompany our getters :stuck_out_tongue:

I did some searching on the warning and dug into the Kotlin commit history, and eventually wound up here: KEEP/value-classes.md at master · Kotlin/KEEP · GitHub

Apparently there’s an issue for this specifically which I suppose I should comment on: https://youtrack.jetbrains.com/issue/KT-44653

It seems like they want to preserve the ‘var’ syntax for copying immutable types, but that puts a massive wrench in this particular scheme. It’s a shame since this unlocks a lot in terms of JNI and gamedev (i.e. working with pointers easily, which is nice for GPU data, ECS setups in native memory, and more.)

2 Likes

This is not the final game-plan yet, just some tentative design thinking, hence the warning. The use-case you’ve quoted where inline value classes are used as a “handle” to some mutable data storage is indeed important and nicely shows how regular var can be used on immutable value classes for a good reason.

We’d love to rescue and keep this use-case. We don’t have an alternative plan that will make it possible yet, but we are looking for it. The bummer here is that in addition to two kinds of properties that we already have in Kotlin (val for a read-only property with a getter and var for a writable property with a getter and a setter) we need to find a different name for the 3rd kind of a property – a mutating property with a getter and a “wither” (mutating setter).

5 Likes

What has become out of the plan to enforce (deep) immutability at the language level? Could it be the solution to define that var means “create a mutated copy” if the it is part of a constant class?

Something like this:

immutable data class Point(var x: Int, var y: Int)
val p1 = Point(1, 1)
val p2 = p1.x = 2

assert(p1 == Point(1,  1)) // original values after "mutation"
assert(p2.x == 2) // only the copy has the new value

immutable would mean “strongly immutable”, what would imply a different meaning of var.

Whether the respective class would be an inline class or not wouldn’t make a difference.

1 Like

Please see details on deep immutability here: KEEP/value-classes.md at master · Kotlin/KEEP · GitHub

2 Likes

Thanks!

Short summary of the KEEP:

The first approach is magical implicit copying:

fun addTag(tag: String) {
    state.lastUpdate = now()
    state.tags += tag
    notifyOnChange(state)    
}

The above syntax is conceptually a sugar for the explicit state = state.copy(...)

The other approach is basically what I’ve thought of with reusing var:

value class State(
    var lastUpdate: Instant,
    // ...
}

we can see that value classes, being immutable, cannot have regular var (mutable) properties anyway, so we can safely reuse var keyword for this purpose.

Personally I think the first approach might be a bit confusing, but after learning this syntax it might be a pretty smart solution.

Maybe I’m missing something but wouldn’t this use-case be fine if those var’s setters become “wither” setters? You’d still be copying the backing Long but as far as I can tell that is the only downside.

@frizzil Could you elaborate why you’re using an inline class for this case? Since a normal class would work fine within the Kotlin context, I’m assuming you’re using it as inline in order to define the external functions’ return type to be something other than a Long.

So the real use-case might be more along the lines of coercing an external return type (e.x. Long) into an inline class?

It’s mainly to avoid GC allocations in high frequency code, which is a huge no no for game development. (Every time the GC sweeps, the game will pause. Huge complaint in Minecraft, for example.) It’s also to ensure cache-friendliness, which is a primary reason to use an ECS framework. You could circumvent with reusable “temporaries”, but that’s awkward and doesn’t account for composition:

inline class Foo(val ptr: Long) {
    val i get() = ...
    val x get() = Bar(x + 4)
}

inline class Bar(val ptr: Long) {
    var f get() = ...
}

GC sweeps will generally slow anything down, it’s just specifically in gamedev where letting one allocation happen every “frame” will be a nuisance to users. GPU data and especially ECS components are things you’d update every frame. The Shenandoah GC may be usable if you have a spare thread, unsure, but you still have to avoid overworking it, which allocations in an ECS framework probably would.

I’m not sure what the cost of returning the copy from a “wither” are. If the JVM will inline it out, then I don’t care at all. I wouldn’t be surprised if the composition I’m doing currently gets inlined into pure arithmetic, in which case, the wither changes might somehow interfere. I suppose the new “withfield” bytecode op will be used, but the unchanged case sounds like you could avoid emitting that instruction altogether. Also, the op would probably be cheap for entirely “in-register” types.

I think part of the issue with “withers” is that optimizing the unchanged case would imply optimizing every single call chained above it. E.g. ‘foo.bar.x = 12’ in my case would mean ‘foo’ should also not be copied, which is a very subtle but potentially important difference to the user. Lots of semantic magic going on, may make the compiler logic more complex. It also implies making the mutability of the setter part of the ABI. Even so, this feature would probably be appreciated for users of larger types. Not really my area of expertise.

As far as solutions go, I’m flexible to anything semantically because I’m just generating code, but to normal users, I’m sure an annotation or context-sensitive keyword somewhere to differentiate would work just fine. You could even use contracts, which specify interface with a similar level trust in the function author.

contract { doesNotChange(this) }

Just spitballing :stuck_out_tongue:

Just to go over some possibilities:

immutable value class Foo ... // As suggested by @medium. Issue is, we might like both in one class

const var foo ... // Means the same as C++, but different meaning than Kotlin's existing const.
external var foo ... // Matches target use case, but If something belongs to us by composition, is it really "external"? May be unintuitive.

// Clearer imo, understood to only affect setter:
var foo get() = ... ; const set(v) { ... }
var foo get() = ... ; external set(v) { ... }
var foo get() = ... ; set(v) { contract { doesNotMutate(this) }; ... } // More verbose, but potentially unambiguous. Could use in regular functions for similar purposes, not sure.

Between those, I probably like const set the best, but its relation to C++ raises questions. (“Why only on the setter?”) On the other hand, the contract version is super clear, and may be preferable if these methods are only written infrequently.

EDIT:

Having gone more thoroughly through the KEEP, perhaps making the case of this thread the default and using mutating for shallow mutating methods would be safer (basically the inverse of C++ const). However, I fear the mutating keyword would be needed far more than without in the case of setters. In a similar vein, public could be viewed as less safe than private, but Kotlin still makes that the default, for convenience and a light goading towards good class design. So perhaps methods on the value class should require a mutating prefix, whereas custom setters imply mutating unless overriden via the const keyword like I have above.

1 Like

I think that prior to concern about performance, we should consider deeper the escape analysis and the stack allocation already present on JVM.
So, a new Something() doesn’t enforce an allocation on TLAB.

I already posted my other considerations here, in short: might a value class declare a var reference?

Is it enough the follow declaration?

value class Grid(ptr: Long)

So, if all proprerties in a value class is val, we really a new concept?

Is it possible to write the above code as

data class Grid(val ptr: Long) {
    var width: Int
        get() = GridNative.getWidth(ptr)
        set(v) { GridNative.setWidth(ptr, v) }
}

How should this var differ?

Regarding Escape Analysis/Scalar Replacement:

As a dev, I want control over when this happens. I want to know without profiling that this piece of code will not allocate something. From experience, I know cases where it certainly will, but it’s difficult to know when it certainly won’t. The JIT-er hitting a certain compilation phase may even determine whether this happens, I’m not sure. But as it stands, it’s an implementation detail of the JVM, not something guaranteed by a spec to my knowledge. HotSpot isn’t the only game in town, given Amazon Coretto, and I’m sure others. (EDIT: this is helpful in understanding what we’re talking about: Beyond Java .)

On the second point, I’m not sure I follow, but do you mean the need for using inline/value instead of a regular class? I think I covered that in my other comments, minus the point on EA/SR.

Otherwise, I saw your linked example using an extension property: I believe you meant to change the proposed syntax of having var on a value class more generally, not just the case in this thread? I’m not sure what that buys aside from the syntax reflecting a sort of abstract purity - maybe others would like that, but to me, it goes against the pragmatic side of Kotlin’s core philosophy, disallowing something already allowed on a similar construct.

If you’re talking about val just meaning final and that the copy-constructor can set those anyway, then that’s actually an interesting point. You could promote all existing vars to vals, then reserve var for something else. However, being able to write foo.x = 12 when x is a val would be really confusing to beginners imo.

This is reminiscent of ‘tailrec’ — an existing keyword that has no effect on code except to generate a compile error if the code doesn’t get optimised in the expected way.

Could it be handled in a similar way, with another keyword?⠀(Perhaps something like ‘noheap’?)⠀That way, there’s nothing else to learn or remember, and the benefit to you doesn’t have a cost for anyone else.

(I won’t comment on the rest, as I don’t follow all of it.⠀But I’m a Keep-It-Simple,-Stupid kind of guy, and this thread is making me nervous…)

This is reminiscent of ‘ tailrec ’ — an existing keyword that has no effect on code except to generate a compile error if the code doesn’t get optimised in the expected way. Could it be handled in a similar way, with another keyword?

Although that’d be a great feature, there are still important cases that cannot be optimized, and even then, it wouldn’t be feasible to implement. Like I said above,

…it’s an implementation detail of the JVM, not something guaranteed by a spec to my knowledge. HotSpot isn’t the only game in town, given Amazon Coretto, and I’m sure others.

That means there’s no standard, cross-VM or futureproof definition of what noheap should even be checking for. Also, the compiler couldn’t guarantee function calls were noheap without requiring library authors marking all their declarations as such, which would be overbearingly tedious.

Honestly, I mentioned the Unsafe case to try and drive the point home, but in retrospect I think it’s distracting from the original issue of “What is this warning and how should Kotlin go forward from here?” Non-mutating custom setters do seem like an infrequent case to be authoring, but supporting them doesn’t have to mean any downside to those unaware of them.

Does invoking a function instead of a property achieve the same thing for you, except the slightly more verbose syntax?

1 Like

Kotlin is not a good language for that, in my opinion. It performs many implicit allocation under the hood (lambda, capturing variable, coroutine invocation), nor it makes explicit when and how the memory has to be allocated.
“Reduce allocation” and “improve performance” do not provide any control over allocation.

No, I don’t.
Regular class cannot replace value class.

I don’t propose to change the current var, I’m asking if Kotlin needs a new var and how this differs.

I agree, this point require more considerations.

But allows a mutable state in an immutable type can be really hard to justify.

Please consider the imaginary numbers:

This is a reasonable implementation:

value class Imaginary(val r: Double, val i: Double)

Is this a reasonable implementation?

value class Imaginary(var r: Double, var i: Double)

How I can mutate an immutable value (like a number)?

So, if it is impossible to define a mutable state in an immutable type, we can define it as:

value class Imaginary(r: Double, i: Double)

val is superfluous and there is no var here.

1 Like

I’m aware that Kotlin is not everyone’s first choice for game development, but this syntax would make memory layout much more feasible and with minimal tradeoff (basically just an extra keyword for those who care to use it.) The benefits are getting normal, elegant syntax without having to write it in C++, including all the associated JNI boilerplate, redesigning all your data structures to facilitate it, etc - pretty huge imo. JNI and cross-language development are important use cases, are they not?

The difference between the allocations you listed and the ones removed by SR is that you can guarantee they will not occur by adhering to convention, whereas SR is really a gamble. In the context of real-time systems, it’s just not an acceptable tradeoff. The thing is, not all game code is actually time/performance critical - for that code, you can relax the conventions by a lot.

Let’s move discussion of var/val being superfluous to the other thread, I’ll respond over there.

1 Like