Busy Developer's Guide to Nim

ted.neward@newardassociates.com | Blog: http://blogs.newardassociates.com | Github: tedneward | LinkedIn: tedneward

Objectives

Notes before we begin:

Objectives

What do we want to do today?

Nim Overview

What is this thing?

Nim Overview

Official description

Nim is a statically typed compiled systems programming language. It combines successful concepts from mature languages like Python, Ada and Modula.

https://nim-lang.org/

Nim Overview

Official description

Getting Started

From 0 to Hello World

Getting Started

Installation

Or, use the Playground

Getting Started

Hello world

# This is a comment
echo "What's your name? "
var name: string = readLine(stdin)
echo "Hi, ", name, "!"

Compiling, then executing

$ nim c hello.nim
$ ./hello
What's your name?
Ted
Hi, Ted!

Compiling and executing in one run

$ nim compile -r hello.nim

Getting Started

What's going on?

Basics

Some core elements

Basics

Lexical

Basics

Syntax

# This is a comment
    
#[ This is
    a multiline
    comment ]#
    
# var and let are both keywords but here we use them as identifiers
var `var` = 42
let `let` = 8
assert `var` + `let` == 50

Basics

Local bindings

Basics

Declarations in Nim

var a: int      # mutable integer, no value explicitly set (0)
var b = 7       # mutable inferred integer set to 7
var 
  c = -11       # mutable inferred integer set to -11
  d = "Hello"   # mutable inferred string set to "Hello"
  e = '/'       # mutable inferred character set to '/'
var f1, f2 = 37 # mutable inferred integer set to variables
    
const g = 35    # immutable integer set to compile-time value  
var k = 27      # mutable inferred integer set to 27 (runtime value)
let j = 2 * k   # immutable integer set to runtime value
let l = 128'i16 # mutable 16-bit integer

Basics

Primitive types: Boolean

Basics

Booleans in Nim

let
  rg = 31
  rh = 99
    
echo "rg is greater than rh: ", rg > rh
echo "rg is smaller than rh: ", rg < rh
echo "rg is equal to rh: ", rg == rh
echo "rg is not equal to rh: ", rg != rh
    
echo "T and T: ", true and true
echo "T or F: ", true or false
echo "F xor F: ", false xor false
echo "not F: ", not false

Basics

Primitive types: Integers

Basics

Primitive types: Floats

Basics

Primitive type conversions

Basics

Nim numerics

let na = 11
var nb1 = (na + 5 - (3 * 2)) / 3
var nb2 = (na + 5 - (3 * 2)) div 3
var nb3 = (na + 5 - (3 * 2)) mod 3
var nc = int(5.5)
var nd = float(5)
#var ne = nc + nd  # Error: type mismatch

Basics

Primitive types: Custom numeric literals

Basics

Nim custom numerics

import std/strutils
type u4 = distinct uint8 # a 4-bit unsigned integer aka "nibble"
proc `'u4`(n: string): u4 =
  # The leading ' is required.
  result = (parseInt(n) and 0x0F).u4
    
var x = 5'u4

Basics

Primitive types: Characters

Basics

Primitive types: Strings

Basics

Nim strings and characters

var sa = "Hello"
let sb = sa & "\nworld\n"
let sr = r"Hello\tworld"    # raw string: "Hello        world"
sa.add(" the world")
echo "The sa string equals", sa
    
let
  ci = 'a'
  cj = 'd'
echo ci < cj
    
let
  sm = "axyb"
  sn = "axyz"
  so = "ba"
  sp = "ba "
echo sm < sn  
echo sn < so  
echo so < sp  

Basics

Ordinal types

Basics

Distinct Types

Basics

Distinct Types

type
  Dollars* = distinct float
    
proc `*` *(a, b: Dollars): Dollars {.borrow.}
proc `+` *(a, b: Dollars): Dollars {.borrow.}
    
var usd = 20.Dollars
#usd = 25  # Doesn't compile
usd = 25.Dollars  # Works fine
    
usd = 20.Dollars * 20.Dollars
echo usd.float
# BTW, reminder: Never use floating-point values for money!

Composite Types

Molecules out of atoms

Composite Types

These are the types that are made up of other elements

Composite Types

Enums

Composite Types

Enums

type CompassDirections = enum cdNorth, cdEast, cdSouth, cdWest
    
type
  Colors {.pure.} = enum
    Red = "FF0000", Green = (1, "00FF00"), Blue = "0000FF"
    
  OtherColors {.pure.} = enum
    Red = 0xFF0000, Orange = 0xFFA500, Yellow = 0xFFFF00
    
  Signals = enum
    sigQuit = 3, sigAbort = 6, sigKill = 9
    
echo "Go ", cdWest, ", young man!"
# echo Red                 # Error: ambiguous identifier: 'Red'
echo Colors.Red                                       # FF0000
echo OtherColors.Red                                  # Red
echo OtherColors.Orange, " ", Orange                  # Orange Orange
echo int(sigQuit), " ", sigAbort, " ", float(sigKill) # 3 sigAbort 9.0

Composite Types

Subrange types

Composite Types

Subrange types

type Age = range[0..125]
    
var sra : Age
var srb = sra
var src = sra - 45  # Runtime error! (But only if checks are on)
#var srd : Age = 595 # Compile error!
echo src
    
type Grade = range['A'..'F']
var srg : Grade = 'F'

Composite Types

Arrays

Composite Types

Arrays

var
  a: array[3, int] = [5, 7, 9]
  b = [5, 7, 9]        
#  c = []  # error      
  d: array[7, string] 
    
const m = 3
let n = 5
    
var ba: array[m, char]

Composite Types

Sequences

Composite Types

Sequences

var
  e1: seq[int] = @[]   
  f = @["abc", "def"]  
  e = newSeq[int]()
  g = @['x', 'y']
  h = @['1', '2', '3']
    
g.add('z')  # x, y, z
h.add(g)    # 1, 2, 3, x, y, z
    
var i = @[9, 8, 7] # 3
i.add(6)    # 4

Composite Types

Openarrays

Composite Types

Indexing and Slicing

Composite Types

Indexing and slicing

let j = ['a', 'b', 'c', 'd', 'e']
    
echo j[1]   # a
echo j[^1]  # e
echo j[0 .. 3]  # @[a, b, c, d]
echo j[0 ..< 3] # @[a, b, c]

Composite Types

Tuples

Composite Types

Tuples

let t = ("Banana", 2, 'c')  
echo t    # (Field0: "Banana", Field1: 2, Field2: 'c')
    
var o = (name: "Banana", weight: 2, rating: 'c')
o[1] = 7          
o.name = "Apple"  
echo o    # (name: "Apple", weight: 7, rating: 'c')
    
type Fruit = tuple[name: string, weight: int, rating: char]
var u = (name: "Kumquat", weight: 5, rating: 'f')
echo u

Control Flow

Ifs and elses and loops, oh my!

Control Flow

Quick note: Expression lists

Control Flow

Statement list expression

a = if a > 15: (echo "Big!"; 5) else: 10
echo a

Control Flow

Branching: if

Control Flow

If

var x = 10
if x < 5:
    echo "x is less than 5"
elif x > 5:
    echo "x is greater than 5"
else:
    echo "x is equal to 5"
    
var y = if x > 8: 9 else: 10

Control Flow

Branching: case

Control Flow

Case

let i = 7
case i
  of 0:
    echo "i is zero"
  of 1, 3, 5, 7, 9:
    echo "i is odd"
  of 2, 4, 6, 8:
    echo "i is even"
  else:
    echo "i is too large"
# {{## END case-1 ##}}
# {{## BEGIN case-2 ##}}
var favoriteFood = case animal
  of "dog": "bones"
  of "cat": "mice"
  elif animal.endsWith("whale"): "plankton"
  else:
    echo "I'm not sure what to serve, but everybody loves ice cream"
    "ice cream"
# {{## END case-2 ##}}
echo animal, " likes ", favoriteFood
    
# {{## BEGIN for ##}}
for n in 5 ..< 9: 
  echo n
    
for n in countup(0, 16, 4):  
  echo n
    
for index, item in ["a","b"].pairs:
  echo item, " at index ", index
# {{## END for ##}}
    
# {{## BEGIN while ##}}
var a = 1
    
while a*a < 100: 
  echo "a is: ", a
  inc a         
    
echo "final value of a: ", a
# {{## END while ##}}
    
# {{## BEGIN when ##}}
when system.hostOS == "windows":
  echo "running on Windows!"
elif system.hostOS == "linux":
  echo "running on Linux!"
elif system.hostOS == "macosx":
  echo "running on Mac OS X!"
else:
  echo "unknown operating system"
# {{## END when ##}}
    
# {{## BEGIN block ##}}
block myblock:
  echo "entering block"
  while true:
    echo "looping"
    break # leaves the loop, but not the block
  echo "still in block"
echo "outside the block"
# {{## END block ##}}
    
# {{## BEGIN exception-handling ##}}
var f: File
if open(f, "quote.txt"):
  try:
    echo readLine(f)
  except IOError:
    echo "IO error!"
  except CatchableError:
    echo "Unknown exception!"
    # reraise the unknown exception:
    raise
  except: # catch all other exceptions
    echo "Other error!"
  finally:
    close(f)
# {{## END exception-handling ##}}
    
# {{## BEGIN exception-tracking ##}}
# {{## END exception-tracking ##}}
    
# {{## BEGIN statement-list ##}}
a = if a > 15: (echo "Big!"; 5) else: 10
echo a
# {{## END statement-list ##}}

Control Flow

Case

var favoriteFood = case animal
  of "dog": "bones"
  of "cat": "mice"
  elif animal.endsWith("whale"): "plankton"
  else:
    echo "I'm not sure what to serve, but everybody loves ice cream"
    "ice cream"

Control Flow

Branching: when

Control Flow

When

when system.hostOS == "windows":
  echo "running on Windows!"
elif system.hostOS == "linux":
  echo "running on Linux!"
elif system.hostOS == "macosx":
  echo "running on Mac OS X!"
else:
  echo "unknown operating system"

Control Flow

Looping: while

Control Flow

While

var a = 1
    
while a*a < 100: 
  echo "a is: ", a
  inc a         
    
echo "final value of a: ", a

Control Flow

Looping: for

Control Flow

For

for n in 5 ..< 9: 
  echo n
    
for n in countup(0, 16, 4):  
  echo n
    
for index, item in ["a","b"].pairs:
  echo item, " at index ", index

Control Flow

Loop manipulation

Control Flow

Explicit block statements

Control Flow

For

block myblock:
  echo "entering block"
  while true:
    echo "looping"
    break # leaves the loop, but not the block
  echo "still in block"
echo "outside the block"

Control Flow

Exception handling

Control Flow

For

var f: File
if open(f, "quote.txt"):
  try:
    echo readLine(f)
  except IOError:
    echo "IO error!"
  except CatchableError:
    echo "Unknown exception!"
    # reraise the unknown exception:
    raise
  except: # catch all other exceptions
    echo "Other error!"
  finally:
    close(f)

Control Flow

defer statement

Control Flow

Procedures

Named reusable blocks of code

Procedures

Definition syntax

Procedures

Procedures

proc fibonacci(n: int): int =
  if n < 2:
    return n
  else:
    return fibonacci(n - 1) + (n - 2).fibonacci
    
proc fibo2(n: int): int =
  proc innerfibo(n:int): int = innerfibo(n-1) + innerfibo(n-2)
  result = if n < 2: n else: innerfibo(n)
    
#echo innerfibo(5)       # Error: undeclared identifier 'innerfibo'

Procedures

Calling syntax

Procedures

Procedure call syntax

var f1 = fibonacci(5)
var f2 = 5.fibonacci()
echo "f1 == f2: ", f1 == f2
    
proc foo(a: string, b: string) =
  echo "foo ", a, " and ", b
    
var a = "hello"
var b = "world"
foo(a,b)
a.foo(b)

Procedures

Procedure call syntax

# Forward declaration
proc callme(x, y: int, s: string = "", c: char, b: bool = false)
    
# call with positional arguments      # parameter bindings:
callme(0, 1, "abc", '\t', true)       # (x=0, y=1, s="abc", c='\t', b=true)
# call with named and positional arguments:
callme(y=1, x=0, "def", '\t')         # (x=0, y=1, s="def", c='\t', b=false)
# call with named arguments (order is not relevant):
callme(c='\t', y=1, x=0)              # (x=0, y=1, s="", c='\t', b=false)
# call as a command statement: no () needed:
callme 0, 1, "ghi", '\t'              # (x=0, y=1, s="ghi", c='\t', b=false)

Procedures

Return values

Procedures

Return values and result

proc yes(question: string): bool =
  echo question, " (y/n)"
  var answered = false
  while answered != true:
    case readLine(stdin)
    of "y", "Y", "yes", "Yes": answered = true; result = true
    of "n", "N", "no", "No": answered = true; result = false
    else: echo "Please be clear: yes or no"
    
if yes("Should I delete all your important files?"):
  echo "I'm sorry Dave, I'm afraid I can't do that."
else:
  echo "I think you know what the problem is just as well as I do."
    
proc returnsInt(): auto = 1984

Procedures

Side effects

Procedures

Operators

Procedures

Operators

# The "UFO" operator, doing all the comparisons at once
proc `<==>`(lhs, rhs: int): int =
  if lhs < rhs:
    result = -1
  elif lhs > rhs: 
    result = 1
  else: 
    result = 0
    
echo 5 <==> 6
echo 7 <==> 6
echo 6 <==> 6
echo `<==>`(4, 5)

Procedures

Anonymous functions

Procedures

Anonymous procedures

import sequtils
    
let powersOfTwo = @[1, 2, 4, 8, 16, 32, 64, 128, 256]
    
echo(powersOfTwo.filter do (x: int) -> bool: x > 32)
echo powersOfTwo.filter(proc (x: int): bool = x > 32)
    
proc greaterThan32(x: int): bool = x > 32
echo powersOfTwo.filter(greaterThan32)
    
proc forEach(str: string, c: proc (x: int): int ) =
  for ch in str:
    echo c(ord(ch))

Procedures

stdlib.sugar macros

import sugar
    
# sugar provides a "->" macro that simplifies writing type declarations
# e.x. (char) -> char instead of 
proc map(str: string, fun: (char) -> char): string =
  for c in str:
    result &= fun(c)
    
# sugar also provides a "=>" macro for the actual lambda value
echo "foo".map((c) => char(ord(c) + 1))
# the following code is exactly equivalent:
echo "foo".map(proc (c: char): char = char(ord(c) + 1))

Procedures

Procedure calling conventions

Procedures

Procedure calling conventions

Procedures

"Routines"

Nim Objects

Just enough OOP to get by

Nim Objects

Class-oriented OO

Nim Objects

Syntax

Nim Objects

Usage

Nim Objects

Basic object syntax

type Person = object
    first_name: string
    last_name: string
    age: int
    
var ted = Person(first_name: "Ted", last_name: "Neward", age: 55)
echo ted    # "(first_name: "Ted", last_name: "Neward", age: 55)"
    
var bono: ref Person
new(bono)
bono.first_name = "Bono"
echo bono[]

Nim Objects

Properties

Nim Objects

Properties

# Person fields are all inaccessible due to non-exported syntax
    
proc firstName*(p: Person): string = p.first_name
proc lastName*(p: Person): string = p.last_name
proc age*(p: Person): int = p.age
    
echo ted.firstName()  # or ted.firstName would work
    
proc `firstName=`*(p: var Person, newName: string) = p.first_name = newName
proc `lastName=`*(p: var Person, newName: string) = p.last_name = newName
#proc `age=`*(p: var Person, newAge: int) = p.age = age # This causes problems
    
ted.firstName = "Theodore"
echo ted

Nim Objects

Inheritance

Nim Objects

Inheritance


Nim Objects

Methods

Nim Objects

Dynamic dispatch

type
  Expression = ref object of RootObj ## abstract base class for expressions
  Literal = ref object of Expression
    x: int
  PlusExpr = ref object of Expression
    a, b: Expression
proc newLit(x: int): Literal = Literal(x: x)
proc newPlus(a, b: Expression): PlusExpr = PlusExpr(a: a, b: b)
    
method eval(e: Expression): int {.base.} = quit "E_NOTIMPL"
method eval(e: Literal): int = e.x
method eval(e: PlusExpr): int = eval(e.a) + eval(e.b)
    
echo eval(newPlus(newPlus(newLit(1), newLit(2)), newLit(4)))

Nim Objects

Interfaces

Nim Objects

Interfaces

type IntFieldInterface = object
    getter: proc (): int
    setter: proc (x: int)
    
proc intField(init = 0): IntFieldInterface =
  var captureMe = init
  proc getter(): int = result = captureMe
  proc setter(x: int) = captureMe = x
  
  result = IntFieldInterface(getter: getter, setter: setter)
    
var anInt = intField(12)
echo anInt.getter()
anInt.setter(34)
echo anInt.getter()

Modules

Atomic units of deployment

Modules

Concept

Modules

Execution

Modules

Reference

Modules

Lexicographic incorporation

Nim FFI

The Foreign Function Interface

Nim FFI

Nim wants/needs to interact with the platform beneath it

Nim FFI

Definitions

FFI: foreign function interface

The interface by which control transitions between VM-controlled code and "native" code outside the VM's sight or control

Nim FFI

FFIs usually offer two things; sometimes three

Nim FFI

NOTE

Nim FFI

Nim FFI is multifaceted

Nim FFI to C

Integrating with the system

Nim FFI to C

Nim/C FFI

Nim FFI to C

Native linking

Nim FFI to C

Nim brings a lot of C types

Nim FFI to C

To call a C-exported function

Nim FFI to C

Simple C FFI

proc puts(str: cstring) {. importc, header: "stdio.h" .}
puts("Howdy world")
    
proc printf(format: cstring): cint {.importc, varargs, header: "stdio.h".}
discard printf("My name is %s and I am %d years old!\n", "Ted", 50)

Nim FFI to C

To use a C-declared structure/type

Nim FFI to C

Simple C types FFI

type
  CTime = int64
proc time(arg: ptr CTime): CTime {.importc, header: "<time.h>".}
    
type
  TM {.importc: "struct tm", header: "<time.h>".} = object
    tm_min: cint
    tm_hour: cint
proc localtime(time: ptr CTime): ptr TM {.importc, header: "<time.h>".}   #1
    
var seconds = time(nil)                                                   #2
let tm = localtime(addr seconds)                                          #3
echo(tm.tm_hour, ":", tm.tm_min)                                          #4

Summary

Nim is...

Nim Resources

Where to go to go get more

Nim Resources

Web

Nim Resources

Web

Nim Resources

Books

Credentials

Who is this guy?

Appendices

A little extra

Exceptions

What to do when code goes to the Bad Place

Exceptions

Concept

Exceptions

Syntax

Exceptions

Syntax

Exceptions

Exception object access

Exceptions

Annotating procedure signatures

Nim FFI to JS

Integrating with the browser