Language has some dedicated syntax to support destructors. What is confusing that there is no constructors. Current way is to define a proc something like newObjectName(). Thats fine. But next person making lib may figure out that he likes makeObjectName() better. Another person would disagree and start using createObjectName(). Yet other developers could decide that one true way is initObjectName(). See where im going? It could get confusing pretty fast. I think it would be reasonable thing to have a standard way of defining a constructor which would be invoked by caling ObjectName(...). After all thats what lots of people are used to and it would not create additional space for confusion.
And since im posting ill ask one thing. I noticed destructors need {.override.} pragma. But why? Is it not rather obvious that if we declare our custom destructor we want that to be used? Seems like useless wrist exercising. Or does it do more besides stating obvious?
For you question on why the destructor pragma needs to be used, look closely at the above information on type bound operations
Regarding constructors, TypeName() can't be used, as that syntax is currently used for type conversions. I'm neither for nor against dedicated type construction mechanisms - some thought needs to be put into whether they're necessary, and how to implement the mechanisms.
I'll post more when I'm not on a phone.
Regarding constructors, TypeName() can't be used, as that syntax is currently used for type conversions
C++ manages...
How about permitting "new " (with a space) as the leading characters in a proc name?
Effectively, this would make constructor calls look similar to other languages, e.g.:
proc new Thing(x: int): Thing = Thing(x: x)
echo new Thing(123)
Unlike introducing an actual "new" keyword, this won't block you from having multiple constructors, e.g. "new ThingFromX" and "new ThingFromY" etc.
May be a tricky grammar change to implement at call sites, dunno... just a thought :-)
You can kind of do this with a template, mindplaydk.
template new*(obj: expr): expr =
var i = obj; i
type MyType = ref object
id: int
value: int
var item = new MyType(id: 5, value: 15)
echo item.id
This is a bad example, but you could make it more sophisticated by having it check if the expr yields a ref type or not, and if not turn it into a ref..
Right, like I said my example was bad; but you could extend that with non-ref types by automatically converting it to a ref type.
i.e.
template new*(obj: expr): expr =
var i : ref type(obj) = obj
Not going to actually do the work for that, but you get the picture.
My awesome and final macro to generate boilerplate for constructors that bind to the type. Simply put:
proc init(self: var Foo, n: int) {.ctor.}=
self.a = n
expands to:
proc init(self: var Foo, n: int) =
self.a = n
proc init(_: typedesc[Foo], n: int): Foo =
init(result, n)
proc new(_: typedesc[Foo], n: int): ref Foo =
new(result)
init(result[], n)
It also works with generics. However i discovered some issues when using generics in this way: https://github.com/Araq/Nim/issues/2898
Macro code, enjoy:
import macros
proc ctor_impl(allocator: NimNode, prc: NimNode): auto {.compiletime.} =
if prc[3][1][1].kind != nnkVarTy:
error("Constructor must have var non-ref type as first parameter")
if prc[3][0].kind != nnkEmpty:
error("Constructor must not have return type")
if $prc[0] != "init":
error("Constructor name must be `init`")
var type_identifier = prc[3][1][1][0]
var ctor = quote do:
proc init(_: typedesc[`type_identifier`]): `type_identifier` =
init(result)
ctor = ctor[0]
ctor[2] = prc[2]
# Extend ctor with parameters of constructor
for i in 2 ..< prc[3].len:
ctor[3].add(prc[3][i])
# Passes ctor params to main init proc
ctor[6][0][1] = new_ident_node("result") # otherwise result is taken from macro context. weird!
for i in 2 ..< prc[3].len:
ctor[6][0].add(prc[3][i][0])
var allc = quote do:
proc new(_: typedesc[`type_identifier`]): ref `type_identifier` =
`allocator`(result)
init(result[])
allc = allc[0]
allc[2] = prc[2]
# Extend allc with parameters of constructor
for i in 2 ..< prc[3].len:
allc[3].add(prc[3][i])
# Passes allc params to main init proc
allc[6][0][1] = new_ident_node("result") # otherwise result is taken from macro context. weird!
allc[6][1][1][0] = new_ident_node("result")
for i in 2 ..< prc[3].len:
allc[6][1].add(prc[3][i][0])
result = new_stmt_list(prc, ctor, allc)
macro ctor_alloc(allocator: typed{nkProcDef}, prc: typed{nkProcDef}): auto {.immediate.} = ctor_impl(allocator, prc)
macro ctor( prc: typed{nkProcDef}): auto {.immediate.} = ctor_impl(new_ident_node("new"), prc)
when is_main_module:
type
Foo = object
a: int
Bar = object
a: int
# proc init(self: var Foo, n: int) =
# self.a = n
# proc init(_: typedesc[Foo], n: int): Foo =
# init(result, n)
# proc new(_: typedesc[Foo], n: int): ref Foo =
# new(result)
# init(result[], n)
proc custom_new(res: var ref Foo) =
echo "allocating with custom allocator"
new(res)
proc init(self: var Foo, n: int) {.ctor_alloc: custom_new.} =
self.a = n
proc init(self: var Bar, n: int) {.ctor.} =
self.a = n
var f: Foo
f.init(1) # works
echo f.repr
echo Foo.init(2).repr # works
echo Foo.new(3).repr # works
That's nice, but it doesn't really solve the core concern for me:
It doesn't force the end-user to init the object; users can still use the object without ever calling 'init', meaning you could get weird behavior for users who misuse your library
Orion, what you need for this are basically opaque types. Opaque types do not expose their representation; anything representation-related can be done only from within the module, unless you export it.
That said, I don't think that's a terribly critical issue. If you use new(x) or T(), then you should know that you're doing a low-level allocation, and if you absolutely want that, you could always hack your way around limitations with unsafeNew and cast even if it were prevented. In the end, module systems in a system programming language can only protect against error, not malice or willful misuse.
I'd still like to have opaque types for other reasons (namely, so that code doesn't accidentally depend on implementation features such as whether a type is a ref or ptr or neither [1]), but it's not what I'd consider a do-or-die issue.
[1] For example, this can happen in generic code where representation-specific operations are being used.
Updated my post with macro code that allows using custom allocator: http://forum.nim-lang.org/t/703/10#8214
However there is a problem. We obviously want default allocator to be new, however macro param is passed as first thing to proc therefore default nil value does not work. Not even sure if it works at all on macros. Cant have two macros with same name overload each other either. So what is left either to specify nil manually when no custom allocator needed or have macro with different name. I opted in for latter. Meanign {.ctor.} uses system.new and {.ctor_alloc: allocator_proc.} uses custom allocator. If anyone knows of a way to have optional pragma param it would be great to hear it. I myself could not find a way to do it.