How can I change initThing so that it stores in list a reference to original so that when original is changed, list reflects the change.
The current code just makes a copy :-(
var original = @[1, 2, 3, 4, 5]
type
Thing = object
# I would like `list` to be set to a *reference *of `original`
list: seq[int]
proc initThing(list: seq[int]): Thing =
result.list = list
var thing = initThing(original)
proc show(label: string) =
echo "\n", label
echo "Original: ", original.repr
echo "Reference: ", thing.list.repr
show "Initial:"
original[0] = 666
show "Updated:"
if thing.list[0] != 666:
echo "\nNot a reference!"
The above prints:
Initial:
Original: @[1, 2, 3, 4, 5]
Reference: @[1, 2, 3, 4, 5]
Updated:
Original: @[666, 2, 3, 4, 5]
Reference: @[1, 2, 3, 4, 5]
Not a reference!
What modifications to Thing and initThing are required to make it show:
Initial:
Original: @[1, 2, 3, 4, 5]
Reference: @[1, 2, 3, 4, 5]
Updated:
Original: @[666, 2, 3, 4, 5]
Reference: @[666, 2, 3, 4, 5]
This sounds simple but I tried all combinations of ref , var, cast[] and addr that I can think of to no avail :-(
Any assistance (as usual) would be appreciated).
Nim has no mechanism to safely reference a value type, as such you need to raise the original to a ref seq[int] like so
var original = new seq[int]
original[] = @[1, 2, 3, 4, 5]
type
Thing = object
# I would like `list` to be set to a *reference *of `original`
list: ref seq[int]
proc initThing(list: ref seq[int]): Thing =
result.list = list
var thing = initThing(original)
proc show(label: string) =
echo "\n", label
echo "Original: ", original.repr
echo "Reference: ", thing.list.repr
show "Initial:"
original[0] = 666
show "Updated:"
if thing.list[0] != 666:
echo "\nNot a reference!"
type
Thing = object
list: ptr seq[int]
proc initThing(list: var seq[int]): Thing =
result.list = list.addr
var original: ref seq[int]
new(original)
original[] = @[1, 2, 3, 4, 5]
type
Thing = object
list: ref seq[int]
proc initThing(list: ref seq[int]): Thing =
result.list = list
var original = @[1, 2, 3, 4, 5]
type
Thing = object
list: ref seq[int]
proc initThing(list: var seq[int]): Thing =
result.list = cast[ref seq[int]](list.addr)
type
Thing = object
list: ref seq[int]
proc initThing(list: seq[int]): Thing =
new(result.list)
result.list[] = list
var thing = initThing(@[1, 2, 3, 4, 5])
let original = thing.list
Example problem with #3:
type
Thing = object
list: ref seq[int]
proc initThing(list: var seq[int]): Thing =
result.list = cast[ref seq[int]](list.addr)
var thing: Thing
new(thing.list)
block:
var original = @[1, 2, 3, 4, 5]
thing = initThing(original)
original[0] = 666
echo thing.list.repr # use after free: ref @[666, 0, 3, 20, 3467820324424670016]
Thanks @ElegantBeef
Thanks @ElegantBeef] I keep forgetting
*Nim has no mechanism to safely reference a value type
There is probably a excellent reason for this. Perhaps my mental model about memory types is incomplete :-(
I thought:
I understand that it is the wild wild west with ptr's (as per @jrfonren's Example problem with #3) and try to avoid them as I don't need the additional complexity of managing memory myself at that level.
My current reservations about ref usage is the visually unappealing [] usage that clutter code -- Perhaps that's just a personal taste thing ( I wasn't a fan of sigils in Perl either :-) )
Just our of curiosity, is there a way to exempt a runtime value type from garbage collection -- perhaps with some of those advanced Nim memory pragmas (that I don't fully understand)
Thanks @jrfondren for your thoughtful and complete response. It was certainly above and beyond the call of duty!.
I tried all of the proposed alternatives and will be updating my production code code with option #2.
I've include a little test harness in case anyone wants to further play even more variants.
# How can I change initThing so that it stores in 'list' a *reference* to 'original'
# so that when 'original' is changed, 'list' reflects the change.
# I don't want it to make a copy.
#
# See: https://forum.nim-lang.org/t/11508#74371
template common*(title: string, original, thing: untyped) =
var thing = initThing(original)
proc show(label: string) =
echo "\n", label
echo "Original: ", original.repr
echo "Reference: ", thing.list.repr
echo "\n---", title, "---"
show "Initial:"
original[0] = 666
show "Updated:"
if thing.list[0] != 666:
echo "\nNot a reference!"
when defined BY_VALUE:
# Non reference version:
# - Makes a copy
block:
var original = @[1, 2, 3, 4, 5]
type
Thing = object
list: seq[int]
proc initThing(list: seq[int]): Thing =
result.list = list
common "BY_VALUE", original, thing
when defined BY_REF:
# Warning - fails if original *moves* or is *deleted*
block:
var original: ref seq[int]
new original
# or
# var original = new seq[int]
original[] = @[1, 2, 3, 4, 5]
type
Thing = object
list: ref seq[int]
proc initThing(list: ref seq[int]): Thing =
result.list = list
common "BY_REF", original, thing
when defined BY_POINTER:
block:
var original = @[1, 2, 3, 4, 5]
type
Thing = object
list: ptr seq[int]
proc initThing(list: var seq[int]): Thing =
result.list = list.addr
common "BY_POINTER", original, thing
Value types were managed memory
Nope value types are allocated on the stack when declared locally or contiguously at the pointer when there is a reference type involved. This means one cannot safely take an address to a value type unless they know the usage of that pointer does not outlive the value usage. As ptr seq[int] in this case does not keep the underlying seq[int] alive you need to know the lifetimes of your variable(in this case it's global so it's not much of a concern).
Just our of curiosity, is there a way to exempt a runtime value type from garbage collection
No cause they're not garbage collected. Sometimes they're on the stack, other times they're not. The data will be written over when the stack shrinks then regrows, no amount of compiler shenanigans will prevent that without raising the memory to the heap.