Object-Oriented Programming in Nim

17.1 Inheritance

Inheritance requires of RootObj (or another inheritable object) or the {.inheritable.} pragma.

Mark leaf types {.final.} to prevent further inheritance.

type
  Animal = ref object of RootObj
    name: string
  Dog = ref object of Animal
    breed: string
  Cat = ref object of Animal

17.2 Dynamic Dispatch with method

proc = static dispatch (resolved at compile time). method = dynamic dispatch (vtable).

Nim 2 compiles methods using vtables (--multimethods:off by default; multi-methods are opt-in).

method speak(a: Animal): string {.base.} = "..."
method speak(d: Dog): string = d.name & " says Woof"
method speak(c: Cat): string = c.name & " says Meow"

let pets: seq[Animal] = @[Dog(name: "Rex"), Cat(name: "Tom")]
for p in pets: echo p.speak()

# Calling the parent implementation
method speak(d: Dog): string = procCall(speak(Animal(d))) & " / Woof"

17.3 Runtime Type Checks and Conversion

for p in pets:
  if p of Dog: echo "Dog breed: ", Dog(p).breed     # checked conversion (ObjectConversionDefect if wrong)

17.4 Composition over Inheritance

Prefer composition, object variants, and generics/concepts. Nim isn't class-centric. Procedures live outside types, and UFCS makes them feel like methods.

17.5 Encapsulation

Module-level visibility with *. Getters and setters:

type Account* = object
  balance: float                    # private
proc balance*(a: Account): float = a.balance
proc `balance=`*(a: var Account, v: float) =
  if v < 0: raise newException(ValueError, "negative")
  a.balance = v

17.6 Interfaces Without Inheritance

Tuples of closures, concepts (Module 30), or vtable-like objects of procs.