I'd like to implement a simple {.persistence.} template macro ... but I can't even get the following hardcoded version to run without resorting to removing history as a parameter (i.e. just including it as a global). While that works... it isn't particulary re-use friendly :-(.
Any better suggestions for working around generated error:
Error: 'history' is of type <var History> which cannot be captured as
it would violate memory safety, declared here:
/Users/DM/work/trunk/nim/test_persistence.nim(10, 11); using
'-d:nimNoLentIterators' helps in some cases
# Example of proc memory capture violation
import
flatty, # https://github.com/treeform/flatty - for actual persistence
std/[exitprocs, os, times]
type
HistoryEntry = string
History = seq[HistoryEntry]
proc save(history: var History, filename: string) =
writeFile(filename, history.toFlatty)
proc persistence(history: var History, filename: string) =
if fileExists filename:
history = filename.readFile.fromFlatty History
addExitProc(
proc() =
save(history, filename)
# Error: 'history' is of type <var History> which cannot be captured as
# it would violate memory safety, declared here:
# /Users/DM/work/trunk/nim/test_persistence.nim(10, 11); using
# '-d:nimNoLentIterators' helps in some cases
)
# Example usage
const historyFilename = "history.his"
var history: History = @[]
history.persistence historyFilename
echo history # so far
history.add "Another entry: " & $now() # Add another entry on each run
Short answer: You need to use ref History instead of var History.
Longer answer: A var argument is only valid for the duration of the call to the proc. This is why it cannot be captured by a closure (your nested proc) -- there's no guarantee that the argument would still be available upon exit. Using a ref allows multiple references to the same data structure and guarantees that the data won't be released (garbage collected) until all of the references go out of scope.
Hope this helps!
Thanks... your explanation was indeed helpful. It now seems to work :-). The reference casting I used to get the closure capture working seems a bit awkward. Any suggestions for improvement would be appreciated.
import flatty, std/[exitprocs, os, times]
type
HistoryEntry = string
History = seq[HistoryEntry]
template reference(thing:untyped): untyped = cast[ref thing.type](addr thing)
proc persistence(history: var History, filename: string) =
proc save(history: ref History, filename: string) =
writeFile(filename, history[].toFlatty)
if fileExists filename: history = filename.readFile.fromFlatty History
let historyRef = history.reference
addExitProc( proc() = save(historyRef, filename) )
proc show(history: History) =
echo "History:"
for entry in history: echo entry
# Example usage
var history: History
history.persistence "history.his"
show history
history.add "Another entry: " & $now() # Add another entry on each run
This isn't what @boia01 meant at all, you need to make a safe reference, not cast the raw pointer to a traced Nim reference, that's invalid code and it'll likely crash.
An example of working code would be this:
# Example of proc memory capture violation
import
flatty, # https://github.com/treeform/flatty - for actual persistence
std/[exitprocs, os, times]
type
HistoryEntry = string
History = ref object
entries: seq[HistoryEntry]
proc save(history: History, filename: string) =
writeFile(filename, history.toFlatty)
proc persistence(history: var History, filename: string) =
if fileExists filename:
history = filename.readFile.fromFlatty History
let history = history # make a non-var history so we can capture it
addExitProc(
proc() =
save(history, filename)
# Error: 'history' is of type <var History> which cannot be captured as
# it would violate memory safety, declared here:
# /Users/DM/work/trunk/nim/test_persistence.nim(10, 11); using
# '-d:nimNoLentIterators' helps in some cases
)
# Example usage
const historyFilename = "history.his"
var history = History()
history.persistence historyFilename
echo history.entries # so far
history.entries.add "Another entry: " & $now() # Add another entry on each run
There are probably other ways, but this is what I came up with.
Thanks... the previous casting version did work... but certainly had a bad smell to it :-). I think I'll steer clear of the ref parameters for now in my pure nim code... It's probably contraindicated except for mixed language interop type code??
I had tried many iterations before posting... but I missed the
let history = history # make a non-var history so we can capture it trick
Nice... and in restrospect obvious :-)
I certainly apreciated the feedback from both of you. Thanks for for your patience.
It all depends on the behavior that you want.
Do you want what's saved upon exit to be the value of your global history variable at the time of exit? Or do you want it to be the value at the time you called the persistence() proc?
If you use the let history = history trick, you're making a local copy, so it will be the value when persistence() is called.
If you want the value of the global history variable, then you either need to use the global variable itself or a ref.
Oh sorry I missed that in your code you had changed History to be a ref.
Ignore my last message then...