I thought maybe someone would find this interesting: the following macro lets you to define functions and containers that accept objects of any type, as long as they have a desired set of fields. The objects may have additional fields, and the order of the fields in the type definition doesn't matter. It's different from generics in that it allows for runtime polymorphism. There's a usage example at the bottom. It's quite cool that Nim allows you to do stuff like this.
import macros
import os


proc `-`(a: pointer, b: pointer): int =
  cast[int](a) - cast[int](b)

proc `+`(a: pointer, b: int): pointer =
  cast[pointer](cast[int](a) + cast[int](b))

macro structural(con, inter, fieldDefs): untyped =
  let dummySym = genSym(nskVar, "dummy")
  let vtableType = newNimNode(nnkTupleTy)
  let vtableValRef = newNimNode(nnkPar)
  let vtableValPtr = newNimNode(nnkPar)
  let conTests = newNimNode(nnkStmtList)
  for fieldDef in fieldDefs.children:
    # Build up vtable tuple type and value, as well as concept tests, from the fields:
    let fname = fieldDef[0]
    let ftype = fieldDef[1][0]
    var a = newNimNode(nnkIdentDefs)
    a.add(quote do: tuple[offs: int, tmark: `ftype`])
    var b = newNimNode(nnkExprColonExpr)
    b.add(quote do: (addr(`dummySym`.`fname`) - addr(`dummySym`[]), `dummySym`.`fname`))
    var c = newNimNode(nnkExprColonExpr)
    c.add(quote do: (addr(`dummySym`.`fname`) - addr(`dummySym`), `dummySym`.`fname`))
    conTests.add(quote do: T.`fname` is `ftype`)
  let node = quote do:
      `con` = concept g, type T
      `inter`[T: pointer or RootRef] = object
        base: T
        vtable: ptr[`vtableType`]
    # Converter for ref object types:
    # (typedesc is explicitly ruled out to prevent infinite loops in the concept tests)
    converter conToInter[T: not typedesc and ref object and `con`](x: T): `inter`[RootRef] =
      var vt {.global.} = (proc(): `vtableType` =
        var `dummySym` = T()
        return `vtableValRef`
        # Questionable cast, but sizeof and repr suggest refs are glorified pointers anyway:
        base: cast[RootRef](x), # Keeps the proxied object alive, provides base address
        vtable: addr vt
    # Converter for ptr object types:
    converter conToInter[T: not typedesc and object and `con`](x: ptr T): `inter`[pointer] =
      var vt {.global.} = (proc(): `vtableType` =
        var `dummySym` = T()
        return `vtableValPtr`
        base: x,
        vtable: addr vt
  let node2 = quote("@") do:
    # Syntax sugar for referring to target fields through the proxy
    # This must be separated from the first quote due to issues with the backtick splice
    # Uses the vtable's dummy tmarks to cast to the correct field type
    template `.`(x: @inter, field: untyped): untyped =
      cast[ptr[(@vtableType).field.tmark]](cast[pointer](x.base) + x.vtable.field.offs)[]
    template `.=`(x: @inter, field: untyped, value: untyped): untyped =
      cast[ptr[(@vtableType).field.tmark]](cast[pointer](x.base) + x.vtable.field.offs)[] = value
  let node3 = newStmtList()

# ---------------------------------------
# Usage example:

  Vec2 = tuple[x, y: float]
  Box = object
    pos: Vec2
    vel: Vec2
  Circle = object
    vel: Vec2
    pos: Vec2
    rad: float
  BoxRef = ref Box
  CircleRef = ref Circle
  Turtle = ref object
    speed: float
    pos: Vec2

structural(Kinetic, IKinetic): # Magic happens here
  pos: Vec2
  vel: Vec2

var ptrSeq: seq[IKinetic[pointer]] = @[]
var refSeq: seq[IKinetic[RootRef]] = @[]

proc test1(x: IKinetic[RootRef]) =
  echo x.pos
  echo x.vel

proc test2(x: IKinetic[pointer]) =
  echo x.pos
  echo x.vel

test1(BoxRef(vel: (1.0, 2.0), pos: (3.0, 4.0)))
test1(CircleRef(vel: (5.0, 6.0), pos: (7.0, 8.0), rad: 125.0))
test2(cast[ptr Circle](alloc0(sizeof Circle)))

echo refSeq[1].vel
echo ptrSeq[0].vel

#test1(Turtle()) # Gives "Kinetic: concept predicate failed" error
2018-03-13 17:57:39

Very cool!

(Still ... can't wait for concepts + vtrefs! )

2018-03-14 02:03:24
Thanks! What's a vtref, though? 2018-03-14 06:15:07

vtref is virtual table ref.

I don't know much about C++, what's the advantage of virtual table?

2018-03-14 06:19:24

vtref is virtual table ref.

I think what I did is already a virtual table ref of sorts, so I'm not sure what boia01 meant. Maybe that something like this (or better) is already being planned for the Nim core? That's always preferable to improvised solutions, I guess.

what's the advantage of virtual table?

Over what?

2018-03-14 06:46:37
@StasB, the future VTable and vtptr and vtref are hidden here.
2018-03-14 11:08:58
@mratsim: That sounds quite similar to what I made, except with classic vtables of proc pointers, while mine contain field offsets. Is there any chance that similar functionality could be supported by this vtref feature? The compiler could additionally generate getters and setters and add them to the proc vtable. 2018-03-14 11:55:24
@StasB - Sorry didn't mean to diss your hack - it's very cool and very similar to the upcoming vtrefs. Concepts should support field access too. Being an OO junky, I'm just looking forward to this kind of stuff supported as a first-class feature in Nim. 2018-03-15 00:46:49
@boia01: Oh, I wasn't trying to imply you're dissing it. Just wasn't sure what you meant, but now I'm also looking forward to the built-in solution, which is always preferable for this sort of thing. Nim is still the only language I know of that has all the elements needed to implement it on your own, though, which is impressive. 2018-03-15 06:38:26

This is not my work, butif you are interested, I had already collected something by @Krux02 similar to your work and published as a nimble package, now improved by @RSDuck


2018-03-15 10:53:26