Is there any performance/memory/anything else tradeoffs I should be aware of between those two type declarations? (note the place of the ref)
type
Context*[T] = object
nodes: ref seq[Node[T]]
And
type
Context*[T] = ref object
nodes: seq[Node[T]]
Yes, when you use it. The following example has quite some performance difference depending on the type chosen:
var context: Context = ...
[...]
let nodes = context.nodes
But without context it's hard to give advice. My guess is that the performance difference doesn't matter, and when it does, you should do performance test. The best performance is by the way the type that has the fewest indirections:
type
Context*[T] = object
nodes: seq[Node[T]]
but be aware that if you assign Context to another variable, you will create a deep copy. So when you want to have a reference stored in another variable, you should use the addr operator.
Krux02: It would be better to store the index. Using addr is unsafe - the object won't have the same address if it's copied from place-to-place on the stack.
mratsim: First, I would look at the section of the manual regarding reference and pointer types . The should provide some information on how references work in general. As a summary, object types are equivalent to C structs, while references types are somewhat equivalent to C pointers (but safer).
Objects will are stored on the stack or directly within the memory allocated for another object, references always point to memory allocated on the heap (and never memory within the bounds of another block of reference memory).
@Varriount: What I originally meant was a pointer to the Context struct itself. But you are right I was not clear on that. For elements in a seq an index works pretty well.
But to your comment on ret types I like to disagree. I don't like to say out in the blue that reference types are like C pointers but safer. If that would be the full truth, then ref would be generally better than ptr types, and that is not true. My take on explaining ref types is, a ref is like a ptr but with lifetime management. A ref keeps it's target object alive, as long as there is still a ref to it, and ensures it's destruction when it is not referenced anymore. Objects on the stack already have a defined lifetime, therefore you should reference them with pointers. Function parameters are when it makes sense passed as hidden pointers, therefore it is neven an optimization to pass a ptr to a function instead of a value type. This basically removes 90% of all cases where you would use a pointer in the C context, and therefore you only need ptr here and there. And ref types, well I didn't need it for anything yet, but they are probably very useful for arbitrarily shaped graphs.
@Lando here an example for dynamic dispatch on value types.
type
A = object of RootObj
a: string
B = object of RootObj
a:int
method foo(arg: RootObj): void =
discard
method foo(arg: A): void =
echo "A ", arg.a
method foo(arg: B): void =
echo "B ", arg.a
var a = A(a: "abc")
var b = B(a: 12345)
var pointers : array[2, ptr RootObj] = [a.addr, b.addr]
for p in pointers:
p[].foo
var references : array[2, ref RootObj]= [new(A), new(B)]
for p in references:
p[].foo
Thank you Krux02 and Varriount.
I do need the indirection. The "Context" is used in other objects. When the seq of Nodes it wraps is modified it should be modified for all.
I'm only wondering if I should put the indirection at the object level or at the field level. There is no other field so my naive guess is that it doesn't matter in term of perf/memory.
@Krux02, I'm using this "Context" object here
I skimmed this discussion and didn't see anyone mention that a sequence is already a reference type. I'm rather surprised by this.
Just remember: writing ref seq[Node[T]] never makes sense, it's simply redundant.
@dom96 I am well aware that seq is implemented as a pointer, but it has value semantics. That is the reason I did not mention it.
var a = @[1,2,3]
var b = a # b is now a new copy of a
@mratsim: sry for telling you bs.
Did I misunderstand the manual section about multi-methods? It says:
"For dynamic dispatch to work on an object it should be a reference type as well."
For dynamic dispatch to work on an object it should be a reference type as well.
That is, this will call RootObj's method, not A's and B's (continuing Krux02's example):
var values : array[2, RootObj]= [a, b]
for p in values:
p.foo
Aah, thx. So maybe a more precise statement would be:
"For dynamic dispatch to work on an object argument, it must be passed as a reference to the multi-method."
The original sentence from the manual seems a bit ambiguous, especially with the code example below it, which uses ref types.
@LeuGim well when you have an array of RootObj, not a ref/ptr to RootObj in the array, the array actually doesn't contain the subtype anymore. So sure when you iterate and call foo on each object. It does call the method from RootObj, but only because you actually have a true RootObj value there.
@Lando That is correct when you mean by reference type all variants of reference types, including the hidden pointer that is used on function arguments. What I want to say is that the restriction on reference types is almost irrelevant.