Interesting proc overloads with parameter constraints in Nim

Overloading on constants

Let's say you have a function that returns a seq of N bytes, such as this one.

proc urandom*(size: Natural): seq[uint8]
  ## Returns a ``seq`` of random integers ``0 <= n < 256`` provided by
  ## the operating system's cryptographic source
  ## 
  ## POSIX: Reads and returns `size` bytes from the file ``/dev/urandom``.

It's a useful function, and all is well. But what if you want to give the result of urandom(32) to a function that takes an array of 32 bytes? Converting a seq to an array of fixed size is surprisingly complicated and dirty (but I may be missing something).

Long story short, you can use static[T] to provide an alternative generic function that returns arrays of fixed size.

proc urandom*(size: static[Natural]): array[size, uint8]

But if you want to keep the 2 versions of the function under the same name, you'll run into a problem: some of the calls, like urandom(32), can work with both versions, so the compiler will complain about ambiguity!

But we can work around this with parameter constraints! An argument (static[Natural]){lit} would allow only integer literals. {`const`} may be more appropriate here, though.

(This example also shows how to factor out a common part of two functions into a template, and how to read exactly N bytes from a file)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
template urandomImpl(): stmt {.immediate.} =
  var file: File
  if not file.open("/dev/urandom"):
    raise newException(OSError, "/dev/urandom is not available")

  # Read exactly `size` bytes from the file
  var index = 0
  while index < size:
    let bytesRead = file.readBuffer(addr result[index], size-index)
    if bytesRead <= 0:
      raise newException(OSError, "Can't read enough bytes from /dev/urandom")
    index += bytesRead

proc urandom*(size: Natural): seq[uint8] =
  newSeq(result, size)
  urandomImpl()

proc urandom*(size: (static[Natural]){lit}): array[size, uint8] =
  urandomImpl()

This code is part of my library nim-random: urandom.nim.

Overloading on var

Similarly, you may want to have two versions of a function: one that takes a normal argument, and the other one that works more optimally by taking a var argument (yes, I'm aware that Nim can do the right thing and pass arguments by a hidden pointer, but there are real use cases for using var directly even if you don't modify the argument, such as when you need to take its address, or when you wrap a C function as var instead of ptr)

Just providing two overloads (one var, one non-var) doesn't work because of ambiguity (this is something that may be improved in future versions of Nim).

UPDATE: This seems to be supported in Nim now.

We need to use parameter constraints again. {lvalue} will do the trick.

An example of this is in my library nim-csfml: csfml_graphics_gen.nim.

proc contains*(rect: (var FloatRect){lvalue}, x: cfloat, y: cfloat): BoolInt
  {.cdecl, importc: "sfFloatRect_contains".}
proc contains*(rect: FloatRect, x: cfloat, y: cfloat): BoolInt =
  var Crect = rect
  contains(Crect, x, y)

Here I wrap a ptr FloatRect as var but also provide a non-var version of this, which just copies the given argument and passes it to the var version.

Created
Comments powered by Disqus