You are misunderstanding, there is no other way to write this code. Even in C++ you have to do a bit of awkward stuff to make the compiler do what you want. The main issue is that lifetimes and ownership do not and cannot follow the object model of the programming language, and both are intrinsically not fully observable at compile-time, being solely resolvable at runtime. Recent versions of C++ have started adding first-class support for object models with these properties so hopefully this will become easier.
The base case here is user-space virtual memory, which is de rigueur for high-scale data intensive applications. Objects not only don't have a fixed address over their lifetime even if you never (logically) move them, they often don't have an address at all, and when they next have an actual memory address it may materialize in another thread/process's address space. And hardware can own references to and operate on this memory outside the object model (e.g. DMA). The silicon doesn't respect the programming language's concept of an object because it doesn't know objects exist. You have to design non-trivial async scheduling and memory fix-up mechanics to make all of this reasonably transparent at the object level. It is actually a pretty elegant model, complex compiler negotiations notwithstanding.
And of course, the reason we put up with the implementation complexity is that there is a huge gap in scalability/performance between this and the alternatives. Same reason people use thread-per-core software architectures. It works around some fairly deep limitations in the silicon and OS.