Hello everyone,
I am new in Nim and I am trying to develop a software with it. I was experiencing some estrange (to me) behavior in my code: arc and orc garbage collectors lead to different results. I'm not sure if this is because I am doing something incorrect, as I'm not familiar with reference objects or pointers. Maybe the underlying logic of my program is wrong.
Y defined several related Object types: System, Polyhedra, Surface, Point, and a few more (but no ref Object was defined).
The flow goes like this:
The problem arises when performing step 6. The triangles attribute of mySystem.surfaces[0] changes after adding the second reference to a surface object to mySystem.polyhedra[0].surfaces.
For example, I set: mySystem.surfaces[0].triangles to @[[1,2,3][4,5,6]] by performing two add operations.
After doing mySystem.polyhedra[0].surfaces.add(cast[ref Surface](addr mySystem.surfaces[0])) it is still @[[1,2,3][4,5,6]]. But if I immediately add another ref to a surface: mySystem.polyhedra[0].surfaces.add(cast[ref Surface](addr mySystem.surfaces[1])) , then mySystem.surfaces[0].triangles turns to be: @[[3, 4, 5], [6, 0, 2]] if using orc, using arc lets mySystem.surfaces[0].triangles untouched, that is @[[1,2,3][4,5,6]].
I expect the latter behavior because adding to a sequence a ref to a surface should not modify the attributes of another surface. I though that I may be a issue with bad initialization of variables, until I found that using -mm:arc solved it. I realize that arc gives the "correct" results after hours of trying to make it work. In some tests, I get a correct result when I removed some unrelated (and unused attribute of Surface).
Questions:
Thank you for your help!
When you have an object type variable, you cannot make ref objects to point it. Unlike pointers, ref types cannot point to arbitrary memory location. It can points to specific object created with RefObjTypeName(param0: x) or new proc. I wrote about a difference between ref object and plain object: https://internet-of-tomohiro.pages.dev/nim/faq.en#type-when-to-use-ref-object-vs-plain-object-qmark
Example code:
type
Point = ref object
x, y: float
Triangle = ref object
points: array[3, Point]
Mesh = object
points: seq[Point]
triangles: seq[Triangle]
var mesh: Mesh
mesh.points.add Point(x: 0.0, y: 1.0)
mesh.points.add Point(x: 1.0, y: 0.0)
mesh.points.add Point(x: 0.0, y: 1.0)
var tri = Triangle(points: [mesh.points[0], mesh.points[1], mesh.points[2]])
mesh.triangles.add tri
echo mesh
Or use index to indirectly refer objects in seq. Example code:
type
Point = object
x, y: float
Triangle = object
points: array[3, int]
Mesh = object
points: seq[Point]
triangles: seq[Triangle]
var mesh: Mesh
mesh.points.add Point(x: 0.0, y: 1.0)
mesh.points.add Point(x: 1.0, y: 0.0)
mesh.points.add Point(x: 0.0, y: 1.0)
var tri = Triangle(points: [0, 1, 2])
mesh.triangles.add tri
echo mesh
echo mesh.points[mesh.triangles[0].points[0]]
cast and addr are unsafe features in Nim. They are rarely used unless you use C libraries.
Thank you Araq.
What would be a better alternative then? cast[ref Point](addr mySystem.points[indexOfThePoint]) was the only way I found to create objects and then point to those objects using ref objects. I also read about ptr, but according to most references I've found, they are more error-prone (that's why I tried to go with ref objects instead).
I've also read about using new for ref objects, but I guess it would be wiser to have normal objects and references to them. This way, I can (for example) 'move' a Point and automatically update the geometric objects to which it belongs.
Any recommendations or guidance from the community on how to approach this would be greatly appreciated.
Thank you demotomohiro!
I think I now understand a little more what is going on, your first code do behaves as I want.
echo mesh.triangles[0].points[0].x # prints 0.0
mesh.points[0].x = 2.0
echo mesh.points[0].x # prints 2.0
echo mesh.triangles[0].points[0].x # prints 2.0
mesh.triangles[0].points[0].x = 0.0
echo mesh.triangles[0].points[0].x # prints 0.0
echo mesh.points[0].x # prints 0.0
If I get it right, this can be done faster using ptr instead of ref, and all I have to care is deallocate normal objects (I think that in a code like this should not be too difficult, right?).
I do not pretend to be too picky nor a purist, is just that the final program must perform a huge number of geometrical transformations, and I need it to be very fast.
var
s = @[1]
pi = s[0].addr
echo pi[]
s.add 2
s[0] = 100
echo pi[]
echo s[0]
I think using indices instead of pointers is safer and almost as fast as pointers. Pointer size is fixed (8bytes in 64bit CPU), but index can be 16 or 32 bits int as long as a number of points is smaller than 2^16 or 2^32.
I recommend you to write simple and working code first. If you write unsafe code, you might spend a lot of time for debugging and give up the project before you write correctly working code. Correctly working code is much better than faster code that produces wrong result. When fast but unsafe code produced output that looks like correct, how do you know whether it is actually correct or not? It is not easy to write fastest code as modern CPU and compiler are complex. Sometimes, I tried to write fastest code but it was slow. Data structure that I thought slow was actually not so slow.