Feature request: Syntactic sugar for weak references in lambda bodies

It is often a concern to avoid memory leaks in scenarios where a child object takes a lambda as handler that strongly references the parent object, creating a cyclic reference unless the lambda body uses a weak reference to the parent object. For instance, consider the following minimal example:

class Example {
    val weakThis = WeakReference(this)
    val widget = Widget(0, 0) { x -> weakThis.get()?.action(x) }

    fun action(x: Any) {}
}

If we used this. instead of weakThis.get()?., or the method reference this::action, the instances of Example would never be collected by the GC, since their widget properties would hold cyclic references to them.

I think the language would benefit from a simple and straightforward syntax to use weak references within lambdas.

My first idea was giving a meaning to the expression this?., since this can never be null, to create a weak reference when constructing the lambda, and use it if still accessible when executing the lambda. This would simplify the previous example to:

class Example {
    val widget = Widget(0, 0) { x -> this?.action(x) }

    fun action(x: Any) {}
}

Of course, using a weak reference in a lambda would force it to not be inlined, since the weak reference must be stored somewhere.

For complex lambdas, using this?.apply to enclose the optional parts of the body could become a frequent construct that could easily wrap existing code (an inspection could automatically wrap lambdas like this).

For simple method reference lambdas, like the example above, perhaps the syntax could be extended as well for something like this?::action, although I’ve noticed the IDE warns about this syntax being reserved for future releases, so something different may be planned for it.

In regards of actually omitting the this keyword in these kind of references and allowing to simply use ?., I feel it would only lead to confusing code and also syntax ambiguity when semicolons are omitted.

However, I can see how the this?. idea could be considered confusing, since it uses the null-safety construct ?. for something unrelated with nullity, so another possibility would be assigning a new symbol operator for this purpose, such as : (for example, I’m not sure of the conflicts this choice would have with other language features).

Using a new symbol operator would have the benefit of extending the syntactic sugar to weak references of other objects than this, though I don’t think these use-cases are as relevant.

Outside of lambda declarations, I don’t think this syntax should have any meaning, since the only problem it addresses is the type of reference that is created within the lambda object.

Currently, I’m not aware of a simpler way to create these weak referencing lambdas than the first example, neither from the receiver side, since the method that takes a lambda cannot change its references, nor from the caller side without having a weak reference to use within the lambda body, that needs to be declared somewhere else, breaking the flow of the code.

Personally, I think that giving this?. a meaning would be an elegant solution, but I can only speak for myself. On the other side, picking a new symbol operator is always a hard task, which I doubt would ever succeed without controversy. I understand that, since there is already a way to solve the problem at hand, a new feature changing the syntax may not be deemed worth the effort.

Another benefit of having an idiomatic way to create these weak-referencing lambdas is that language users would be encouraged to prevent this frequent type of memory leaks. Perhaps the IDE could also offer a new inspection, suggesting to replace a strong reference inside a lambda for a weak one when a cyclic reference is detected, or wrap an entire lambda using this?.apply.

1 Like

It is often a concern to avoid memory leaks in scenarios where a child object takes a lambda as handler that strongly references the parent object, creating a cyclic reference unless the lambda body uses a weak reference to the parent object.

I have never encountered that problem. The garbage collector in JVM is pretty good at working with garbage containing cycles.

For instance, consider the following minimal example:

class Example {
   val weakThis = WeakReference(this)
   val widget = Widget(0, 0) { x -> weakThis.get()?.action(x) }

   fun action(x: Any) {}
}

If we used this. instead of weakThis.get()?. , or the method reference this::action , the instances of Example would never be collected by the GC, since their widget properties would hold cyclic references to them.

Do you mean like this? Because the garbage collector handles this just fine:

class Widget(val f: (Any) -> Unit) {
    fun finalize() {
        println("Widget is being collected by garbage collector")
    }
}

class Example {
    val widget = Widget {x -> this.action(x) }

    fun action(x: Any) {}

    fun finalize() {
        println("Example is being collected by garbage collector")
    }
}

fun createGarbage() {
    Example()
}

fun main() {
    createGarbage()
    java.lang.System.gc()
    java.lang.Thread.sleep(500)
    println("Exiting")
}

I think you are making some false assumptions about the JVM. The garbage collector is very good at detecting virtually all objects that are not strongly reachable - so much so that you as a programmer never have to worry about it. Weak references are not intended to be used to break cycles. According the the Javadoc:

Weak references are most often used to implement canonicalizing mappings

Weak references are such a niche tool that I definitely do not think we need any syntactical support for them.

8 Likes

2 other concerns to add onto Varia’s answer:

  1. Not all platforms that Kotlin supports have weak references
  2. About this:

That statement is absolutely false when it comes to extension functions. For example:

fun SomeType?.doSomething() = this?.apply {
    println(this)
}

And so, your proposal would add in syntactic sugar for a rarely-used feature, and that sugar would have to only work on the JVM (well, K/N does have something like weak references, but still) and it’d only work in class bodies, not in extension functions. That, to me, is absolutely too much, really.

1 Like

I wonder whether OP might be confusing JVM GC with the simpler GC schemes that some other languages/platforms have used, such as Automatic Reference Counting (ARC)?

The first JVMs used mark-and-sweep collectors, which have no trouble with circular references. Since then, more sophisticated algorithms have been added — adaptive, generational, parallel, low-latency, concurrent, and more recently the Garbage First (G1) collector have all increased performance and scalability.

I don’t know the details of GC in Kotlin/JS, but I believe it too can handle circular references.

Kotlin/Native originally used a simple deferred reference-counting GC, but JetBrains are working on a full GC infrastructure to support multi-threaded tracing collectors behaving more like JVM ones.

So no, in Kotlin it’s not possible to ‘leak memory’, rendering it permanently inaccessible, as you can in some other languages.

(That doesn’t mean you can’t hold on to memory for longer than intended, though. It’s always worth being aware of what impact your code might have on the heap, and reducing your heap use where it’s easy to do.)

4 Likes

After some research, it seems I was indeed confused on this topic and this feature is not actually beneficial.
Thank you so much for the detailed replies.

3 Likes