What matters is the aliasing semantics, not how something is passed. If you pass a ref or ptr to an object, then changes to the object's fields will be seen by the caller. Ditto for var parameters--a change to the parameter, and to its fields if it is an object, will be seen in the caller's version. For non-var parameters, let the compiler figure out which is best, pass by address or pass by copy.
Assignments are copies, either of a ref or ptr to an object, in which case you're creating an alias, or of an object, in which case you're copying the whole object. Normally such a copy is deep (copied refs/ptrs within the object become refs/ptrs to copies), but you can make it shallow with shallowCopy (this will change when the language adopts move semantics).
A tricky case is when a proc has a var return type (e.g., the mitems iterator, or tables.mgetOrPut). The return value is aliased, so modifying it modifies the original (e.g., the elements of seq iterated over, or the table entry), but if you assign it you've made a copy and are no longer accessing the original. e.g.,
# for key not already in tab
mgetOrPut(tab, key, newEnt).field = 3 # sets newEnt.field
var ent = mgetOrPut(tab, key, newEnt)
ent.field = 3 # does not set newEnt.field; only changes the copy in ent
If you want to do complex operations on a value returned by var, then pass it to a proc with a var parameter, which provides the needed aliasing:
proc messWithEnt(ent: var MyEnt) =
ent.field = 3 # changes caller's version
...
messWithEnt(mgetOrPut(tab, key, newEnt)) # sets newEnt.field
(Note: a better API design than mgetOrPut would be getOrPut[K,V](tab: Table[K,V], key: K, getValIfKeyNotPresent: proc (): V) which avoids having to create a new object before knowing whether it is needed.)
We have the byRef pragma which we can use to pass Nim value objects as pointers to C libs without having to use ugly addr operator.
grep -A2 byRef ~/.nimble/pkgs/gintro-#head/gintro/gtk4.nim
TreeIter* {.pure, byRef.} = object
stamp*: int32
userData*: pointer
--
TextIter* {.pure, byRef.} = object
dummy1*: pointer
dummy2*: pointer
--
Requisition* {.pure, byRef.} = object
width*: int32
height*: int32
--
Border* {.pure, byRef.} = object
left*: int16
right*: int16
--
PageRange* {.pure, byRef.} = object
start*: int32
`end`*: int32
So we can allocate these simple structs on the stack and pass it directly to C libs. (Well I guess the pure pragma is not needed in this case, as Nim objects without inheritance have no hidden fields.)
@jibal I just copied the pre-existing mget interface to do something more useful. That said, another way to avoid unnecessary object construction compatible with the existing interface is:
const iniOb = whatever
...in some proc or loop:
mgetOrPut(key, iniOb).doSomething
That way the generic assign copy of the val is only done on rawInsert after the key is known to be absent (in the OrPut branch). The const could also be a let or just before a loop to do many, etc.No, in fact, U just purchased "Nim in action" at Manning's, but it leaves a lot of questions unanswered (which is normal for a introductory manual).
However, who knows, it is not a bad idea if you really like a language and want to promote said language.
The most efficient way to promote Nim would probably be a collaborative book, with every participant writing a chapter.
Are you working on a Nim book?
The thought has crossed my mind ... but that's just how I write normally.
@cblake > const iniOb = whatever
Sure, but then you need some field in the object that you can check for whether it was previously set, in order to avoid the expense of calculating its new value, and that's not always available. The anonymous proc solution avoids that--it's only called when needed.
You mostly seemed unaware of the easy workaround to avoid unnecessary object construction. Your second criticism is more valid. While not logically required without extra space as you say, it is often easy to tell a default value from a later value. So, I think mgetOrPut covers a very large fraction of use cases. But not all.
Non-inlined functions can also take more time than the whole lookup process for integer keys in L1 cache tables. { Not necessary, but a potential hazard. }
In my home grown C libraries I just do if (cell=find(t, key, &insertSpot)) cnt++; else { cell = preIni(&t, key, insertSpot); cell->cnt=1; } with less expert all-in-one put/get/set-type interfaces. The expert interface has no problem of any kind except that you need to know more to use it.
A safer way to go in Nim might be like template withValue[A, B](t: var Table[A, B] maybe called template updateOrInit or withInit, but where the 2nd clause lets you init a just allocated value. That'd avoid tricky var passing conventions, the need to define let or const objects, or worry about if people's functions get inlined, but introduce template conventions which may be viewed as "more tricky". I think I would personally prefer that 2-body template, but I may be weird.
Beats me what would be most popular, risky, or even which is "simplest". I always think "Big Tent"/"all of the above". If it was up to me I'd say both your callback proc and the template, but then people complain about too large APIs or ask for new things without fully understanding existing stuff. (That is not intended as a jab at you..partial learning just tracks big APIs and people already complain Table is too big.)
Anyway, you should open some PR and see what Araq says. He gives far more attention to concrete code additions than forum theorizing.
You mostly seemed unaware of the easy workaround to avoid unnecessary object construction.
Well, appearances can be deceiving ... I find instances of that usage in my code. Still, my parenthetical comment was quite correct and was not meant as a personal attack on the designer of the API (the identify of which I was not aware of).