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.

2017-12-29 22:38:50

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

2017-12-30 04:12:10

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

2017-12-30 12:15:52
Udiknedormin, nice trick thanks for sharing 2017-12-30 19:06:57
You're welcome. I mostly use Nim for metaprogramming fun so I'm kind of used to these kind of tricks.
2018-01-01 12:00:19
I think this is implicitly covered by https://github.com/nim-lang/Nim/wiki/Destructors where it would produce a =sink or a raw copy.
2018-01-10 08:14:03
It is. The tricks I showed are for just until the introduction of =sink.
2018-01-10 22:44:43

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.

2018-01-11 18:30:26
@Serenitor It's different in many ways:
  • also changes semantics of == ("is it actually the same object" instead of "is this (other) object the same")
  • no dynamic dispatch is usable anyway unless {.inheritable.} is applied first
  • poorer performance due to dynamic heap allocation (and GC)
  • poorer performance due to memory fragmentation

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...

2018-01-12 14:33:28

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.

2018-01-14 18:39:02