I hope someone can explain this to me.
I have modified the code to make it clear so that is easier to understand what is doing.
I’ve implemneted some changes where I was populating latLong fields.
However, I noticed that this didn’t work.
No errors, simply the latLong fields in item was null
The issue that fixed it was changing
location?.copy to item.location?.copy(
address?.copy( to item.location.address?.copy(
This is confusing me. I started reading about it and understand that it has something to do with the basic mechanism in Kotlin with shalow copies and val/vars
However, my understanding is that in both cases item gets a new copy of the location and address copies(with the latLong values) irrespective of whether it’s val/var and item itself should be a new modified copy.
However, this only happens when the local variable ISN’T used.
Can you fix your code? Perhaps split it into two code blocks since you seem to have two versions of the same function?
Also, I don’t understand how you can have this code:
val location = item.location
val address = location?.address
val location = if (address?.latLong == null && postcode != null) {
getLatLong()
} else {
null
}
The compiler should complain about the location variable being declared twice. Is this actually your code? Or did you make some changes after pasting it here?
This has nothing to do with val/var or local variables. In Kotlin (and I believe in most modern languages) objects don’t contain other objects - they reference them. Every object is independent, but can connect to other objects. If you copy an object, you don’t copy its connected objects - you simply create more references to same objects.
It is not clear to me what the question being asked is.
The copy function is not black magic—it is synthesized (i.e., auto-generated at compile time) for all data classes, but it’s not intrinsic (i.e., whose semantics are inexpressible within the language). It has the same parameters as the constructor, and constructs a new instance by calling the constructor.
Given a class like this:
data class Foo(
val a: A,
val b: B
)
…a copy method like this is generated:
fun copy(
a: A = this.a,
b: B = this.b
) = Foo(
a = a,
b = b
)
As can be seen, calling copy on an instance is semantically no different than calling the constructor of the type directly, with the added nicety that parameters can be omitted and default to the value they have in the instance the method was called on. This is nothing special—just the behavior of optional parameters in Kotlin. If you’re interested in how that works, you might want to decompile a function declaration with optional parameters.
As @broot noted, objects don’t contain other objects—all objects live indiscriminately in the heap, where they’re referenced from. This is crucial in understanding mutability.
I will add for clarity: val and var declarations are bindings, not storage. Kotlin being an applications programming language (not a systems programming one like C or Rust), it doesn’t deal with storage at all.
That’s why it’s also said to be a “managed” language—because memory is managed by the runtime (e.g. the JVM, JS runtimes, or Kotlin/Native’s own runtime). This is done using a tracing garbage collector, which periodically scans memory and deletes objects with no references, which are guaranteed to be unreachable. The heap is practically the only form of storage to be aware of, regardless of any optimizations around short-lived objects that a runtime might transparently use to reduce the GC’s young generation pressure (e.g., escape analysis). Things like these are only relevant when needing to squeeze out as much performance as possible, but at that point I would wonder whether to just use Rust and manage memory on my own (see algebraic lifetimes, or arena allocators for bulk allocation and better memory locality).
Bindings simply state “this name references this value.” The difference between val and var is that only the latter can be reassigned (i.e., updated to reference another value). When var is local, reassignments happen within the execution flow of the current function, unless they’re captured in a closure, which extends their scope and makes them non-local (that is, properties).
FWIW, lifetimes are being talked about for Kotlin, and arena allocators already exist for Kotlin/Native! On the JVM, I think there are some APIs that let you bulk-allocate memory as well.
I think it’s also good to be aware of the stack since stackoverflows can happen.
I think it’s also good to be aware of the stack since stackoverflows can happen.
You’re right, @kyay10, but that mostly comes down to making recursive calls or having a call chain that is extremely deep. In fact, since values normally reside in the heap, that makes stack overflow a lot less likely as compared to languages where you might be allowed to simply store too much data in the stack.
I wasn’t aware of lifetimes being talked about for Kotlin. I’m quite curious to see where that leads the language.
memScoped is, last I checked, not an arena allocator. All objects in Kotlin/Native still happen on the heap. It’s just that memScoped keeps track of them and deleted them at the end—but it doesn’t make the allocation local or bulk.
As for low-level memory allocation on the JVM, it is both more unsafe and more weakly typed than C, so it’s a big no-no for me. I’d rather use Rust if I need that much control over memory access patterns, which provides safety and better ergonomics for that.
Take a look at the Unsafe class and decide for yourself! The JVM gives you a choice: be safe and accept extreme memory indirection and GC pressure (and maybe pray for escape analysis to work) or give up all kind of safety and work in a way not really unlike assembly code.