Every object with non-trivial initialization and deinitialization utilizes two functions: init() - initializes an object, deinit() deinitializes an object.
We separate initialization from allocation. Alas, we’re not adepts of RAII. We create an object first, then directly call object.init(). Calling further deinitialization can be done in a language, that supports defer statements, like Go:
func main() {
o := My_Object{}
e := o.init()
if e != 0 {
panic("We can panic on failed init")
}
defer o.deinit()
// ...do some stuff
// ...deinit will be called anyways
}
Init can fail, deinit must succeed. Due to this reason, init() returns an integer error code, which can be handled in any way desired. Deinit returns nothing - we must write it in an error-prone way. Deinit should always attempt to free the memory - or skip, if such memory is already freed.
Uncovering further: Zig taught me the good pattern of passing allocators to functions, rather than using some default allocator. We must have full control over memory operations. So, the init function can accept some allocator, save reference to it within object’s memory, and use it on the deinit.