| finals | ||
| tests | ||
| .gitignore | ||
| finals.nim | ||
| finals.nimble | ||
| readme.md | ||
finals
(Mostly) Transparent single-set attributes for Nim types
Example
finals:
type Point = object
x*: int
y* {.final.}: int
# In another file...
var p: Point
p.x = 3 # fine
p.y = 4 # fine
p.x = 0 # fine
p.y = 1 # Error! `y` can only be set once!
Usage
Like in the example. Generalizes to variant types as well. In a finals: block, multiple typedefs may be present, as well as
non-typdefs, which will be ignored.
For debugging
finalsd may be used instead of finals to only have an effect with -d:debug, and otherwise be a 0-cost noop.
If Nim is run with -d:finalsOn, both finals and finalsd blocks will run. If Nim is run with -d:finalsOff,
neither finals nor finalsd blocks will run. If Nim is run with both -d:finalsOn and -d:finalsOff,
the entire world will explode.
Gotchas
Name conflicts
The finals macro works by generating, for an attribute a: A on type T, a getter proc a(o: T): A and setter
proc `a=`(o: T; v: A). If you define your own custom setters and getters, there will be a name conflict.
Module scoping
Unfortunately, the finals macro only has an effect outside of the module containing the typedef.
This is because getters and setters are ignored within the same module. It's due to a Nim feature and
is unavoidable.
# a.nim
finals:
type X = object
x* {.final.}: int
X.x will only be protected for .x= calls from outside a.nim. This means that the following will
not result in an error:
# a.nim (continued)
var o* = X()
o.x = 3
# b.nim
import a
o.x = 4
In order to circumvent this issue, an user may manually finalize an attribute X by calling ffinalizeX(o)
(the extra "f" stands for "finals" is added to avoid name conflicts). The previous example would be fixed
like so:
# a.nim (continued)
var o* = X()
o.x = 3
o.finalizeX();
# b.nim
import a
o.x = 4 # error!
Contructors and Object Hierarchies
In order for the finals macro to work, attributes marked with {.final.} are explicitely NOT exported.
Due to this, there is an issue with constructors and object hierarchies:
finals:
type Parent* = ref object
p* {.final.}: int
type Child* = ref object
c* {.final.}: int
let c = Child(c: 2, p: 3) # error! `p` is not accessible
Function Attributes
Because finals generates accessors for attriubutes, obj.finalAttr() will call the acceessor rather than
invoking the attribute.
finals:
type T = object
f* {.final.}: proc()
proc initT():
return T(f: proc() = echo "test")
# in another file...
let t = initT()
t.f() # calls the `f` accessor instead of the proc
t.f()() # need two ()'s to work
Known bugs
Intolerant of other pragmas
If an attribute is marked with any pragmas besides {.final.}, those pragmas will be removed.