Nim newb here.
Was looking for some semblance of dependency injection in Nim. Search in this forum yielded this, which is just a mention.
Test code I came up with is:
type DbInterface = tuple[
create: proc(x: int): bool]
proc createMem(x: int): bool {.procvar.} =
if x > 0:
true
else:
false
let dbInstance: DbInterface = (create: createMem)
But I was informed by the compiler that: testinject.nim(10, 31) Error: type mismatch: got (tuple[create: proc (x: int): bool{.gcsafe, locks: 0.}]) but expected 'DbInterface'.
Any insight or sample code to make this work would be greatly appreciated.
... but wait! After toying with the code some more, this compiles:
let dbInstance = (create: createMem)
But I'd like dbInstance to have a declared type. So...
type DbInterface = tuple[
create: proc(x: int): bool]
proc createMem(x: int): bool {.procvar, gcsafe, locks:0.} =
if x > 0:
true
else:
false
let dbInstance = (create: createMem) # edit: dbInstance is still not of type
# DbInterface
... which made the code compile. My question now turns into: Is this the correct way to proceed? What's the best way to handle dependency injection?
First off, what version of Nim are you using? The development branch has had quite a number of fixes, and I definitely recall some of those were to tuple detection/conversion.
Dependency injection in Nim isn't quite as straightforward in Nim as in other languages, due to it's lower-level nature.
For runtime dependency injection, you generally have to use methods or vtables to inject runtime behavior. Our resident macro master, fowl, has been working on a set of vtable macros, located here that can simplify some aspects of vtable/interface construction.
For compile-time dependency injection, one must resort to compile-time mechanisms such as 'when' statements, templates, and generics. One general technique for dependency injection is to create multiple modules that conform to a specific 'interface', then use a 'when defined(...)' statement to decide which of the conforming modules to import. Another way is through use of generics and type classes/concepts, so that your procedures, types, and methods depend on as little concrete typing as possible. Templates can be used in the same way as generics to inject additional behavior.
@Varriount
Nim version is 11.2.
The simplest way (for me) then, given the options that you have enumerated, is to go the when defined() route.
I need to shed the higher-level language way of thinking for this one.
Thank you.
@Araq, thank you! This makes me very happy. Whee!
For the sake of everything good, here's how everything looks now:
type DbInterface = tuple[
create: proc(x: int): bool {.nimcall.}]
proc createMem(x: int): bool {.procvar.} =
if x > 0:
true
else:
false
let dbInstance: DbInterface = (create: createMem)
var ret = dbInstance.create 1
echo(ret)
@Varriount My mental exercise was about faking interfaces using tuples. If I have interfaces, I can do dependency injection easily. Just pass instances of the tuple and voila!
The database and compile-time choice were just details. :)
I think you can use concepts (used be user-defined type classes) for this.
The difference with respect to interfaces is that (usually) interfaces are opt-in (that is, you have to specify you plan to implement a given interface) while concepts are implicit (if you implement the right functions, there is no need to declare you implement the concept). But they should work fine for DI anyway.