I've got an object where I would like the compiler to fail if I accidentally try to copy an instance of it. Overloading proc = seems to be the way to do this, but I'm struggling to get the exact incantation to also allow for instantiation. Here is my starting point:
type Person = object
first, last: string
proc newPerson(first, last: string): Person =
result.first = first
result.last = last
proc `=`*[T](d: var Person; src: Person) {.error.} =
discard
let person = newPerson("John", "Doe")
echo person.first, " ", person.last
This blows up on let person = because, as you might have guessed, that calls =.
What is the approach I should take here? And thanks for any help you can offer.
if you're ok with a var object, you can do:
type Person = object
first, last: string
proc init(self: var Person, first, last: string) =
self.first = first
self.last = last
proc `=`*[T](d: var Person; src: Person) {.error.} =
discard
var person : Person
person.init("John", "Doe")
echo person.first, " ", person.last
Well, the whole problem is that = can't be AST-overloaded. :-/ That would be the best and most nimish solution. However, I found three other solutions as well, one of which I will quote. Sadly, all three require patterns so if you use --patterns:off, the checks will be disabled. Here it comes, the best solution I found:
type Person = object
first, last: string
proc newPerson(first, last: string): Person =
result.first = first
result.last = last
# Surprisingly, this is needed for patterns to notice p=src as an operator call.
proc `=`*(d: var Person; src: Person) = system.`=`(d, src)
# We assume moving is a copy from call.
template personEqErr*{`=`(p, i)}(p: Person, i: Person{lit|lvalue|`let`}) =
{.error: "Person doesn't allow implicit copy".}
let person1: Person = newPerson("John", "Doe")
let person2: Person = Person(first: "John", last: "Doe")
#let person3 = person1 # error!
echo person1.first, " ", person1.last
echo person2.first, " ", person2.last
If you'd like to disable built-in constructors too (i.e. only call initialization), then here you go:
type Person = object
first, last: string
proc newPerson(first, last: string): Person =
result.first = first
result.last = last
# Surprisingly, this is needed for patterns to notice p=src as an operator call.
proc `=`*(d: var Person; src: Person) = system.`=`(d, src)
# We assume moving is a copy from call.
template personEqErr*{`=`(p, i)}(p: Person, i: Person{~call}) =
{.error: "Person doesn't allow implicit copy".}
let person1: Person = newPerson("John", "Doe")
#let person2: Person = Person(first: "John", last: "Doe") # error!
#let person3 = person1 # error!
echo person1.first, " ", person1.last
This is probably not applicable to what you are trying to do, but maybe you want to use a reference instead?
type Person = ref object
first, last: string
let person1 = Person(first: "John", last: "Doe") # calls `new` implicitly for ref types
echo person1.first, " ", person1.last
let person2 = person1
echo person1 == person2 # true
that's now heap allocated and gives you a pointer traced by the GC.
= merely copies that pointer and does not create a new instance.
new instances can only be created with new or the object constructor.
Proof of the first thesis:
type Person = ref object
first, last: string
let person1 = Person(first: "John", last: "Doe")
let person2 = person1 # the very same object
let person3 = Person(first: "John", last: "Doe") # essentially the same value
echo person1 == person2 # true
echo person1 == person3 # false
I wouldn't be all-overjoyed with these Java-like == semantics...
I've raised a similar feature request here: https://github.com/nim-lang/Nim/issues/6348
I would love to have different assignment semantics between let or var initialization and already initialized var.