• Distinct类型
    • 模拟货币
    • 避免SQL注入攻击

    Distinct类型

    distinct 类型是从 基类型 派生的新类型与它的基类型不兼容。 特别是,它是一种不同类型的基本属性,它 并不 意味着它和基本类型之间的子类型关系。 允许从不同类型到其基本类型的显式类型转换,反之亦然。另请参阅 distinctBase 以获得逆操作。

    如果基类型是序数类型,则不同类型是序数类型。

    模拟货币

    可以使用不同的类型来模拟不同的物理 单位 比如具有数字基类型。 以下示例模拟货币。

    货币计算中不应混合不同的货币。 不同类型是模拟不同货币的完美工具:

    1. type
    2. Dollar = distinct int
    3. Euro = distinct int
    4.  
    5. var
    6. d: Dollar
    7. e: Euro
    8.  
    9. echo d + 12
    10. # 错误:无法添加没有单位的数字和 ``美元``

    d + 12.Dollar 也不允许,因为 +int (在其它类型之中)定义, 而没有为 Dollar 定义。 因此需要定义美元的 +

    1. proc `+` (x, y: Dollar): Dollar =
    2. result = Dollar(int(x) + int(y))

    将一美元乘以一美元是没有意义的,但可以不带单位相乘;对除法同样成立:

    1. proc `*` (x: Dollar, y: int): Dollar =
    2. result = Dollar(int(x) * y)
    3.  
    4. proc `*` (x: int, y: Dollar): Dollar =
    5. result = Dollar(x * int(y))
    6.  
    7. proc `div` ...

    这很快变得乏味。 实现是琐碎的,编译器不应该生成所有这些代码只是为了以后优化它 - 毕竟美元 + 应该生成与整型 + 相同的二进制代码。 编译指示 borrow 旨在解决这个问题;原则上它会生成以上的琐碎实现:

    1. proc `*` (x: Dollar, y: int): Dollar {.borrow.}
    2. proc `*` (x: int, y: Dollar): Dollar {.borrow.}
    3. proc `div` (x: Dollar, y: int): Dollar {.borrow.}

    borrow 编译指示使编译器使用与处理distinct类型的基类型的proc相同的实现,因此不会生成任何代码。

    但似乎所有这些样板代码都需要为 欧元 货币重复。这可以通过 模板 解决。

    1. template additive(typ: typedesc) =
    2. proc `+` *(x, y: typ): typ {.borrow.}
    3. proc `-` *(x, y: typ): typ {.borrow.}
    4.  
    5. # 一元运算符:
    6. proc `+` *(x: typ): typ {.borrow.}
    7. proc `-` *(x: typ): typ {.borrow.}
    8.  
    9. template multiplicative(typ, base: typedesc) =
    10. proc `*` *(x: typ, y: base): typ {.borrow.}
    11. proc `*` *(x: base, y: typ): typ {.borrow.}
    12. proc `div` *(x: typ, y: base): typ {.borrow.}
    13. proc `mod` *(x: typ, y: base): typ {.borrow.}
    14.  
    15. template comparable(typ: typedesc) =
    16. proc `<` * (x, y: typ): bool {.borrow.}
    17. proc `<=` * (x, y: typ): bool {.borrow.}
    18. proc `==` * (x, y: typ): bool {.borrow.}
    19.  
    20. template defineCurrency(typ, base: untyped) =
    21. type
    22. typ* = distinct base
    23. additive(typ)
    24. multiplicative(typ, base)
    25. comparable(typ)
    26.  
    27. defineCurrency(Dollar, int)
    28. defineCurrency(Euro, int)

    借用编译指示还可用于注释不同类型以允许某些内置操作被提升:

    1. type
    2. Foo = object
    3. a, b: int
    4. s: string
    5.  
    6. Bar {.borrow: `.`.} = distinct Foo
    7.  
    8. var bb: ref Bar
    9. new bb
    10. # 字段访问有效
    11. bb.a = 90
    12. bb.s = "abc"

    目前只有点访问符可以用这种方式借用。

    避免SQL注入攻击

    从Nim传递到SQL数据库的SQL语句可能被模拟为字符串。 但是,使用字符串模板并填充值很容易受到 SQL注入攻击:

    1. import strutils
    2.  
    3. proc query(db: DbHandle, statement: string) = ...
    4.  
    5. var
    6. username: string
    7.  
    8. db.query("SELECT FROM users WHERE name = '$1'" % username)
    9. # 可怕的安全漏洞,但编译没有问题

    通过将包含SQL的字符串与不包含SQL的字符串区分开来可以避免这种情况。 不同类型提供了一种引入与 string 不兼容的新字符串类型 SQL 的方法:

    1. type
    2. SQL = distinct string
    3.  
    4. proc query(db: DbHandle, statement: SQL) = ...
    5.  
    6. var
    7. username: string
    8.  
    9. db.query("SELECT FROM users WHERE name = '$1'" % username)
    10. # 静态错误:`query` 需要一个SQL字符串!

    它是抽象类型的基本属性,它们 并不 意味着抽象类型与其基类型之间的子类型关系。 允许从 stringSQL 的显式类型转换:

    1. import strutils, sequtils
    2.  
    3. proc properQuote(s: string): SQL =
    4. # 为SQL语句正确引用字符串
    5. return SQL(s)
    6.  
    7. proc `%` (frmt: SQL, values: openarray[string]): SQL =
    8. # 引用每个论点:
    9. let v = values.mapIt(SQL, properQuote(it))
    10. # we need a temporary type for the type conversion :-(
    11. type StrSeq = seq[string]
    12. # 调用 strutils.`%`:
    13. result = SQL(string(frmt) % StrSeq(v))
    14.  
    15. db.query("SELECT FROM users WHERE name = '$1'".SQL % [username])

    现在我们有针对SQL注入攻击的编译时检查。 因为 "".SQL 转换为 SQL("") 不需要新的语法来获得漂亮的 SQL 字符串字面值。 假设的 SQL 类型实际上存在于库中,作为 db_sqlite 等模块的 TSqlQuery类型 。