Distinct type
A distinct type is a new type derived from a base type that is incompatible with its base type. In particular, it is an essential property of a distinct type that it does not imply a subtype relation between it and its base type. Explicit type conversions from a distinct type to its base type and vice versa are allowed. See also distinctBase to get the reverse operation.
A distinct type is an ordinal type if its base type is an ordinal type.
Modeling currencies
A distinct type can be used to model different physical units with a numerical base type, for example. The following example models currencies.
Different currencies should not be mixed in monetary calculations. Distinct types are a perfect tool to model different currencies:
type
Dollar = distinct int
Euro = distinct int
var
d: Dollar
e: Euro
echo d + 12
# Error: cannot add a number with no unit and a `Dollar`
Unfortunately, d + 12.Dollar is not allowed either, because + is defined for int (among others), not for Dollar. So a + for dollars needs to be defined:
proc `+` (x, y: Dollar): Dollar =
result = Dollar(int(x) + int(y))
It does not make sense to multiply a dollar with a dollar, but with a number without unit; and the same holds for division:
proc `*` (x: Dollar, y: int): Dollar =
result = Dollar(int(x) * y)
proc `*` (x: int, y: Dollar): Dollar =
result = Dollar(x * int(y))
proc `div` ...
This quickly gets tedious. The implementations are trivial and the compiler should not generate all this code only to optimize it away later - after all + for dollars should produce the same binary code as + for ints. The pragma borrow has been designed to solve this problem; in principle, it generates the above trivial implementations:
proc `*` (x: Dollar, y: int): Dollar {.borrow.}
proc `*` (x: int, y: Dollar): Dollar {.borrow.}
proc `div` (x: Dollar, y: int): Dollar {.borrow.}
The borrow pragma makes the compiler use the same implementation as the proc that deals with the distinct type’s base type, so no code is generated.
But it seems all this boilerplate code needs to be repeated for the Euro currency. This can be solved with templates.
template additive(typ: typedesc) =
proc `+` *(x, y: typ): typ {.borrow.}
proc `-` *(x, y: typ): typ {.borrow.}
# unary operators:
proc `+` *(x: typ): typ {.borrow.}
proc `-` *(x: typ): typ {.borrow.}
template multiplicative(typ, base: typedesc) =
proc `*` *(x: typ, y: base): typ {.borrow.}
proc `*` *(x: base, y: typ): typ {.borrow.}
proc `div` *(x: typ, y: base): typ {.borrow.}
proc `mod` *(x: typ, y: base): typ {.borrow.}
template comparable(typ: typedesc) =
proc `<` * (x, y: typ): bool {.borrow.}
proc `<=` * (x, y: typ): bool {.borrow.}
proc `==` * (x, y: typ): bool {.borrow.}
template defineCurrency(typ, base: untyped) =
type
typ* = distinct base
additive(typ)
multiplicative(typ, base)
comparable(typ)
defineCurrency(Dollar, int)
defineCurrency(Euro, int)
The borrow pragma can also be used to annotate the distinct type to allow certain builtin operations to be lifted:
type
Foo = object
a, b: int
s: string
Bar {.borrow: `.`.} = distinct Foo
var bb: ref Bar
new bb
# field access now valid
bb.a = 90
bb.s = "abc"
Currently, only the dot accessor can be borrowed in this way.
Avoiding SQL injection attacks
An SQL statement that is passed from Nim to an SQL database might be modeled as a string. However, using string templates and filling in the values is vulnerable to the famous SQL injection attack:
import std/strutils
proc query(db: DbHandle, statement: string) = ...
var
username: string
db.query("SELECT FROM users WHERE name = '$1'" % username)
# Horrible security hole, but the compiler does not mind!
This can be avoided by distinguishing strings that contain SQL from strings that don’t. Distinct types provide a means to introduce a new string type SQL that is incompatible with string:
type
SQL = distinct string
proc query(db: DbHandle, statement: SQL) = ...
var
username: string
db.query("SELECT FROM users WHERE name = '$1'" % username)
# Static error: `query` expects an SQL string!
It is an essential property of abstract types that they do not imply a subtype relation between the abstract type and its base type. Explicit type conversions from string to SQL are allowed:
import std/[strutils, sequtils]
proc properQuote(s: string): SQL =
# quotes a string properly for an SQL statement
return SQL(s)
proc `%` (frmt: SQL, values: openarray[string]): SQL =
# quote each argument:
let v = values.mapIt(properQuote(it))
# we need a temporary type for the type conversion :-(
type StrSeq = seq[string]
# call strutils.`%`:
result = SQL(string(frmt) % StrSeq(v))
db.query("SELECT FROM users WHERE name = '$1'".SQL % [username])
Now we have compile-time checking against SQL injection attacks. Since “”.SQL is transformed to SQL(“”) no new syntax is needed for nice looking SQL string literals. The hypothetical SQL type actually exists in the library as the SqlQuery type of modules like db_sqlite.