Given the following examplar:
import std/options
type
Callback = Option[proc(name: string, length: int)]
TestOptions = object
name: string
length: int
callback: Callback
proc show(name: string, length: int) =
echo "Callback executed: ", name, " is ", length, " inches long"
let option1 = TestOptions(name: "foot", length: 12)
let option2 = TestOptions(name: "inch", length: 1)
#let option3 = TestOptions(name: "yard", length: 36, callback: some(show)) # FIXME: uncomment
proc test(options: TestOptions) =
echo "Options = ", options
if options.callback.isSome:
options.callback.get()(options.name, options.length)
else:
echo "No callback supplied"
test option1
test option2
#test option3 # FIXME: uncomment
Running the code gives:
nim r tests/test_options.nim
Options = (name: "foot", length: 12, callback: ...)
No callback supplied
Options = (name: "inch", length: 1, callback: ...)
No callback supplied
If I uncomment the two FIXME lines to test actually supplying a callback, I get the following compile error:
nim r tests/test_options.nim
/Users/DM/work/trunk/nim/tests/test_options.nim(16, 62)
Error: type mismatch: got 'Option[proc (name: string, length: int){.gcsafe.}]' for 'some(show)' but
expected 'Callback = Option[proc (name: string, length: int){.closure.}]'
Anyone know the magic incantation to allow the code to compile and hopefully run? I tried sprinkling {.closure.} and {.gcsafe.} pragmas around with no success.
Your problem is the type system and that there is different procedural types.
A normal proc in nim always is of type nimcall. A normal unnamed proc (like you have when you use it as a type to store it in TestOptions) is always of type closure so you can have an environment.
For 1 simply change your callback definition: ` Callback = Option[proc(name: string, length: int) {.nimcall.}]`
For 2 simply change your proc definition:
const show =proc(name: string, length: int) {.closure.} =
echo "Callback executed: ", name, " is ", length, " inches long"
Note also that procedures are a pointer type under the hood, so you don't really need Option:
import std/options
type
Callback = proc(name: string, length: int) {.nimcall.}
TestOptions = object
name: string
length: int
callback: Callback
proc show(name: string, length: int) =
echo "Callback executed: ", name, " is ", length, " inches long"
let
option1 = TestOptions(name: "foot", length: 12)
option2 = TestOptions(name: "inch", length: 1)
option3 = TestOptions(name: "yard", length: 36, callback: show)
proc test(options: TestOptions) =
echo "Options = ", options
if not options.callback.isNil:
options.callback(options.name, options.length)
else:
echo "No callback supplied"
test option1
test option2
test option3
The options module knows this, and basically just turns into a null-check in this scenario, so it's just a matter of ergonomics and preference which one you use.
Thank you very much for the excellent explanation. It all makes so much sense now.
I combined PMunch's suggustion plus a small variant of your second option. to yield:
type
Callback = proc(name: string, length: int)
TestOptions = object
name: string
length: int
callback: Callback
const show = proc(name: string, length: int) =
echo "Callback executed: ", name, " is ", length, " inches long"
let option1 = TestOptions(name: "foot", length: 12)
let option2 = TestOptions(name: "inch", length: 1)
let option3 = TestOptions(name: "yard", length: 36, callback: show)
proc test(options: TestOptions) =
if options.callback.isNil:
echo "No callback supplied"
else:
options.callback(options.name, options.length)
test option1
test option2
test option3
which gives the desired result:
nim r tests/test_options.nim
No callback supplied
No callback supplied
Callback executed: yard is 36 inches long
Lessons learned:
Excellent! ... Thanks ... I prefer the isNil to the option alternative... less syntactic clutter. I will be using that little piece of knowledge going forward.
See my revised code above and let feel free to comment on any other ergonomics and preference related suggestions. I really do learn from such feedback.
While syntactic clutter, using options also means you're less likely to accidentally blow up your application. Having to call "get" is a little stronger a reminder that a given proc may not exist and you may run into nil-access-defects which will crash and burn down your entire application. Thus I always tend to use Option to express something does not exist, never nil. I've had enough of nil checks for ref-types from my time in java.
That kind of safety I'd value more than the syntactic niceness of isNil.