Hello!
I am facing a little issue with object instances assigned to constants, as apparently the object address keeps changing through the program.
Shortly, my code looks like this:
abstract class Value
end
class ValueA < Value
end
class ValueB < Value
end
def init_a
return ValueA.new
end
def init_b
return ValueB.new
end
A = init_a
B = init_b
and I keep referencing A and B through the whole program. What I expect is that the object in both the constants remains the same through the execution. However, checking the addresses it seems that the instance keeps changing overtime as you can see from the below picture
This creates some issue when using the default operator â==â.
I wander if there is any compile-time optimization that leads to this behavior.
Does anyone know?
Hi @max-codeware and welcome to the forum!
Your code doesnât compile (Value
is an existing struct). If I change the name, I get the expected behavior: each inspection of A
returns the exact same address, so either your program is different or Iâm not understanding your problem.
Hi @beta-ziliani,
I wrote the code without considering names, but it was meant to simply explain the logic in a short way. I am a bit confused in front of this issue as I am not able to reproduce it outside my project.
You can check here the constant definition at the bottom of the page: LinCAS/Boolean.cr at feature/core_refactor · LinCAS-lang/LinCAS · GitHub
The code in the branch isnât compilable yet, as some stuff is missing as I havenât pushed it yet.
Again, the behavior I wish and expect is the one you saw from compiling the small snipped of code.
However, if you take a look at the picture, I get different addresses at different inspections.
Is it possible that the constant initializer defined by crystal is called multiple times?
May that be that youâre referring to different things? I see thereâs a pointerof(<constant>)
somewhere. Note that the following two are different:
class A
end
CA = A.new
p CA, pointerof(CA) # => #<A:0x7f8392aede90> Pointer(A)@0x560c9c784940
No, that should not happen. Initializers are guarded to run only once.
@straight-shoota @beta-ziliani That is what is apparently happening in my code: I added an initializer with a print to console and this was the result
The constant is initialized over and over every time I reference it. I am currently using MacOS. I need some time to see if this happens on Linux as well.
Do you have any idea on how I could inspect this? I tried to reproduce the same error on a separate file, but it worked correctly⊠I wander if some library may interfere with the single-initialization of the constants. I am compiling my project passing -lclang as link flag if this is of any utility
I think having a small code example that someone could copy/paste/run and see the problem would be quite helpful versus just pasting screenshots of a random program with some output only you are familiar with.
EDIT: Sorry saw this late, but the need for a smaller example is still warranted. Your best bet might just be to keep commenting code out until the error goes away, then undo the last change and try to move the remaining code into a single file to see if it still reproduces, then go from there.
3 Likes
If youâve installed it via brew
, then you could try the interpreter and some good old debugging. Check the Debugging a Program section in Crystal's interpreter â A very special holiday present - The Crystal Programming Language
(Although this might bring other issues⊠you can also debug more reliably using lldb Debug Crystal in VSCode via CodeLLDB - DEV Community đ©âđ»đšâđ»)
Define a constructor for the class you are getting duplicates for. Add puts caller.join("\n")
in that constructor. Then youâll find out where you are creating these duplicate instances.
I managed to find the criminal code, and âpointerofâ mentioned by @beta-ziliani was a cause of it
module M
class A
def initialize
@modified = false
puts "I'm being created"
end
property modified
end
def self.create_a
return A.new
end
def self.modify_a(a : A*)
a.value.modified = true
end
ConstA = create_a
puts ConstA
modify_a(pointerof(ConstA))
puts ConstA
end
puts M::ConstA
Which produces
I'm being created
I'm being created
#<M::A:0x7f4802a31e90>
I'm being created
I'm being created
#<M::A:0x7f4802a31e70>
I'm being created
#<M::A:0x7f4802a31e60>
The above code practically doesnât make sense when invoking modify_a
with a pointer to an instance. Passing the instance directly is instead more appropriate. This comes from an old version of the program when I used a struct instead of a class, and the two versions mixed up a bit.
However, it is interesting to notice that due to that pointer passing, the constant is initialized multiple times throughout the program. The behavior I expected was that, regardless the pointerof
, the address of ConstA
would be the same anyways.
If we remove any pointer reference, the program runs correctly.
Is the above code behaving correctly according to the crystal compilation?
1 Like
This seems to be a bug. Apparently, when you take a pointer of a constant the once_init
guard loses track of the initialization status.
Reduced reproduction:
Foo = begin
puts "Initializing Foo"
end
pointerof(Foo)
This prints âInitializing Fooâ twice.
I see. @straight-shoota Shall I open a bug in the GitHub repo?
1 Like
Yes, please. That said, Iâm very curious as to why you take the pointer of a constant.
Perfect, I will do that shortly.
Regarding the pointer, long story short. the original code was like:
class Obj
end
struct A
@obj = uninitialized Obj
property obj
end
ConstA = A.new
def initialize_a(a : A*)
a.value.obj = Obj.new
end
#... Later in another part of the code
initialize_a(pointerof(ConstA))
The member @obj
can be initialized only at runtime, so I created the instance first, and later fully initialized it with the proper object. But I was using a struct, that canât be passed by reference to a method, so thatâs why I was taking the pointer.
I did that with Windows:
lib LibC
FOLDERID_Profile = GUID.new(0x5e6c858f, 0x0e22, 0x4760, UInt8.static_array(0x9a, 0xfe, 0xea, 0x33, 0x17, 0xb6, 0x71, 0x73))
end
module Crystal::System::Path
def self.home : String
# ...
elsif LibC.SHGetKnownFolderPath(pointerof(LibC::FOLDERID_Profile), 0, nil, out path_ptr) == 0
# ...
end
end
end
This is not super necessary, and in fact similar code for Windows sockets assigns the constant to a variable (actually method parameter) first, but this should be pretty harmless because the original C example also did it.
Better make that @obj
nilable, and use the property!
macro.
If you are using uninitialized
and pointerof
just like that, you are bound to have a really bad time using Crystal. Please avoid using those at all cost! 
3 Likes
Yes. pointerof
and uninitialized
are explicitly unsafe. Their usage should be restricted to low-level operations related to C bindings or highly optimized algorithms. But they are definitely not good for working around the type system.
If you need pass-by-reference semantics and mutable instance variables, is there a reason not to use a class
instead? Pass-by-reference and mutable state are actually 2 of my 3 criteria for choosing whether to use class
vs struct
â the third being whether I need finalize
.
To be clear, I donât mean to pretend this isnât a bug. 
The struct usage was kinda an over optimization of the program. Now I refactored everything using classes.
I wasnât aware of the property!
macro. It is going to make some work much easier 
I got hit by this issue today with C bindings. I have to create a global struct, whose pointer is accessed by every function call (itâs a context). So what I did first was the natural
class Example
@@ctx = <init code>
def f1
c_call1 <params>, pointerof(@@ctx)
end
end
This, of course, brings the issue described here. Problem is: how to solve it efficiently? Given itâs a struct, I donât want it to be copied at every call.