Viktor Kirilov
C++ dev
by Viktor Kirilov
subtitle: every talk I make is a lightning talk
and this one is going to be intense - sorry :|
echo "Hello World" # code
nim c hello.nim # compile
./hello # run
Hello World # stdout
import strformat # can also import specific parts and give aliases
type
Person = object
name: string
age: Natural # Ensures the age is positive
# Types in Nim: arrays, sequences, tuples, sets & objects
let people = [
Person(name: "John", age: 45),
Person(name: "Kate", age: 30)
]
# Proc means function
proc doWork() =
# Indentation based - like Python
for person in people:
# Type-safe string interpolation, evaluated at compile time.
echo(fmt"{person.name} is {person.age} years old")
doWork() # and we get some output
# This is an example how an abstract syntax tree could be modelled in Nim
type
NodeKind = enum # the different node types
Int, # a leaf with an integer value
String, # a leaf with a string value
Add, # an addition
Sub, # a subtraction
If # an if statement
Node = ref object
case kind: NodeKind # the ``kind`` field is the discriminator
of Int: int_val: int
of String: str_val: string
of Add, Sub: # '+' or '-'
left, right: Node
of If:
condition, then_part, else_part: Node
var n = Node(kind: Int, int_val: 42)
if n.kind notin {Add, Sub, If}: # easy work with sets/flags/enumerations
echo "it's a value!"
# raises a `FieldError` exception, because n.kind is Int and not String
n.str_val = ""
type
Note* = ref object of RootObj
text: string
TaskNote* = ref object of Note
completed: bool
method render*(note: Note): string = # the object is the first argument
return note.text
method render*(note: TaskNote): string =
case note.completed
of true: return "☑ " & note.text
of false: return "☐ " & note.text
var baseType: Note = TaskNote(text: "Do me soon!", completed: false)
echo baseType.render() # polymorphic call - outputs "☐ Do me soon!"
proc writeToConsole() =
echo "is IO a side effect?"
var glob = 5
proc touchGlobal() =
glob = 6
proc impossible() {.noSideEffect.} = # will be checked within the whole program
writeToConsole() # this won't compile because of the "echo"
touchGlobal() # this won't compile either - because of the global var access
proc complex() {.raises: [IOError, ArithmeticError].} = # only these can be thrown
#...
proc simple() {.raises: [].} = # no exceptions can pass through
#...
# example defining a currency
type
# or use {.borrow.} here to reuse everything
Dollars = distinct float
proc `+`(a, b: Dollars): Dollars {.borrow.}
var a = 20.Dollars
a = 25 # Doesn't compile
a = 25.Dollars # Works fine
a = 20.Dollars * 20.Dollars # Doesn't compile
a = 20.Dollars + 20.Dollars # Works fine
while b ≠ 0
if a > b
a := a − b
else
b := b − a
return a
template withLock(lock: Lock, body: untyped) =
acquire lock
try:
body # <<< this is where the 'block' of code will go
finally:
release lock
var ourLock: Lock # create a lock
initLock ourLock # init the lock
withLock ourLock: # here we use the template and pass a code block
echo "Do something which requires locking"
echo "This might throw an exception"
var ourLock: Lock # this code was written out of the template
initLock ourLock # this code was written out of the template
acquire ourLock # the result after the substitution - no "template call"
try:
echo "Do something that requires locking"
echo "This might throw an exception"
finally:
release ourLock
template withFile(f_var: untyped, # name of the file variable
filename: string, # file to open
mode: FileMode, # file open mode
body: untyped) = # the block of code
let fn = filename # to prevent double evaluation of 'filename'
var f_var: File
if open(f_var, fn, mode): # first use of 'fn'
try:
body # <<< this is where the 'block' of code will go
finally:
close(f_var)
else:
quit("cannot open: " & fn) # second use of 'fn'
withFile(txt, "ttempl3.txt", fmWrite): # f_var will be 'txt'
txt.writeLine("line 1")
txt.writeLine("line 2")
# match multiplication of integers and the literal '2'
template optimMultiply{`*`(a, 2)}(a: int): int = a + a
let x = 3
# will use addition instead of multiplication - not
# really useful since the C/C++ optimizer would handle
# this specific case, but this showcases the power of Nim
echo x * 2
# not going to match this because it's '3' and not '2'
echo x * 3
# this definition exists in the System module
template `!=` (a, b: untyped): untyped =
not (a == b)
assert(5 != 6) # the compiler rewrites that to: assert(not (5 == 6))
import macros # 'macros' module for working with the AST
# inspect the AST hierarchy of any block of code
dumpTree:
var mt: MyType = MyType(a:123.456, b:"abcdef")
# the output from dumpTree
StmtList # list ot statements
VarSection # var statement
IdentDefs # identifier definition
Ident "mt" # name of var - an identifier
Ident "MyType" # type of var - an identifier
ObjConstr # constructor
Ident "MyType" # identifier to call for construction
ExprColonExpr # ... you get the picture
Ident "a"
FloatLit 123.456
ExprColonExpr
Ident "b"
StrLit "abcdef"
import macros, strutils # 'macros' module for manipulating the AST
macro toEnum(words: static[string]): untyped =
# in Nim procs and macros have an implicit "result" variable
# here we create a new tree node of type 'Enumeration'
result = newTree(nnkEnumTy, newEmptyNode())
# we split the words string on whitespace and iterate over them
for w in splitWhitespace(words):
# and we add identifier nodes as children to the enumeration
result.add ident(w)
type
Color = toEnum"Red Green Blue Indigo"
# Indigo is a valid identifier after the NimVM
# has gone through the call to "toEnum"
var color = Indigo
import macros # 'macros' module for working with the AST
dumpAstGen: # this will dump Nim code to create the code we have passed it
proc hello() =
echo "hi"
nnkStmtList.newTree( # the procedure definition statement
nnkProcDef.newTree( # the procedure definition
newIdentNode(!"hello"), # identifier - the name
newEmptyNode(),
newEmptyNode(),
nnkFormalParams.newTree( # no parameters
newEmptyNode()
),
newEmptyNode(), # empty (for pragmas, etc.) - our 'hello' proc is too simple
newEmptyNode(),
nnkStmtList.newTree( # the list of statements in the proc
nnkCommand.newTree( # a function call
newIdentNode(!"echo"), # we call 'echo'
newLit("hi") # and we pass it 'hi'
)
)
)
)
import macros # 'macros' module for working with the AST
macro generate_hello(): typed =
result = nnkStmtList.newTree( # the output from dumpAstGen (from last slide)
nnkProcDef.newTree(
newIdentNode(!"hello"),
newEmptyNode(),
newEmptyNode(),
nnkFormalParams.newTree(
newEmptyNode()
),
newEmptyNode(),
newEmptyNode(),
nnkStmtList.newTree(
nnkCommand.newTree(
newIdentNode(!"echo"),
newLit("hi")
)
)
)
)
generate_hello() # create the hello() proc from the last slide
hello() # hello() now exists!!! and when called will print "hi"
import macros # 'macros' module for manipulating the AST
type
MyType = object # some random type with 2 fields
a: float
b: string
macro myMacro(arg: untyped): untyped =
var mt: MyType = MyType(a:123.456, b:"abcdef") # an arbitrary value
let mtLit = newLit(mt) # convert the value into a NimNode tree
# here we put literally the code we want
# we inject NimNode symbols with backticks
result = quote do:
echo `arg`
echo `mtLit`
myMacro("Hallo") # call the bad boy
# The call to myMacro will generate the following code:
echo "Hallo"
echo MyType(a: 123.456'f64, b: "abcdef")
import html_dsl
# no need for third-party
# templating engines
html page:
head:
title("Title")
body:
p("Hello")
p("World")
dv:
# we can mix code with
# the HTML view
for i in 0..3:
p "Example"
echo render(page())
<!DOCTYPE html>
<html class='has-navbar-fixed-top'>
<head>
<meta charset="utf-8">
<meta name="viewport"
content="width=device-width">
<title>Title</title>
</head>
<body class='has-navbar-fixed-top'>
<p >Hello</p>
<p >World</p>
<div>
<p>Example</p>
<p>Example</p>
<p>Example</p>
</div>
</body>
</html>
import protobuf # module can be fetched from the package manager
# Define our Protobuf specification
const protoSpec = """syntax = "proto3";
message ExampleMessage {
int32 number = 1;
string text = 2;
SubMessage nested = 3;
message SubMessage {
int32 a_field = 1;
}
}
"""
# Generate Nim code to use it - invoking a full Protobuf parser
parseProto(protoSpec)
# Create messages using the already constructed Nim types
var msg = new ExampleMessage
msg.number = 10
msg.text = "Hello world"
# We even have helper functions for working with sub-messages
msg.nested = initExampleMessage_SubMessage(aField = 100)
# main.nim
import foo
# there is no main() function
echo from_foo()
echo "main" # global scope code
# foo.nim
import bar
proc from_foo*(): string =
return from_bar
echo "foo" # global scope code
# bar.nim
let local = "BAR!"
let from_bar* = local
^
# '*' means 'exported'
# foo.nim
proc foo() =
echo "hello"
foo()
// == includes section
#include <nimbase.h> // nimbase is always present
// == type definitions section
struct TGenericSeq { int len; int reserved; };
struct NimStringDesc : public TGenericSeq { ... };
// == constants & globals section
STRING_LITERAL(the_string_literal, "hello", 5);
NimStringDesc the_string_constant(the_string_literal);
// == forward declarations section
void foo_iineYNh8S9cE6Ry7dr2Tz2A(); // mangled name
// == definitions section
void foo_iineYNh8S9cE6Ry7dr2Tz2A() {
echoBinSafe(the_string_constant, 1); // the echo call
}
// == init section
void init_module_foo() {
foo_iineYNh8S9cE6Ry7dr2Tz2A(); // << call
}
// == other sections - omitted for simplicity
// ...
type
MyData = object
answer: int
ready: bool
proc newData(): MyData = return MyData(answer: 42, ready: true)
echo newData().answer
// == type definitions section
struct MyData {
int answer;
bool ready;
};
// == definitions section
tyObject_MyData newData() {
MyData result; // always an implicit "result"
result.answer = ((int) 42);
result.ready = true;
return result;
}
// == init section
void init_module_foo() {
MyData T2_;
T2_ = newData(); // << call the construction
echoBinSafe(T2_.answer, 1); // the echo call
}
proc printf(formatstr: cstring)
{.header: "<stdio.h>", importc: "printf", varargs.}
{.emit: """
using namespace core;
""".} # emits C++ code! can also do inline assembly
{.compile: "logic.c".} # compile & link this .c file
other pragmas - for use in Nim:
We can also call Nim code from C/C++:
# fib.nim
proc fib(a: cint): cint {.exportc.} # do not mangle
nim c --noMain --noLinking --header:fib.h fib.nim
// user.c
#include <fib.h>
type # here we define a generic type which maps directly to std::map
StdMap {.importcpp: "std::map", header: "<map>".} [K, V] = object
proc `[]=`[K, V](this: var StdMap[K, V]; key: K; val: V) {.
importcpp: "#[#] = #", header: "<map>".} # we import the [] operator
var x: StdMap[cint, cdouble] # and we use it directly...
x[6] = 91.4
std::map<int, double> x;
x[6] = 91.4; // no binding layer - C++ types/functions are called directly
C++ template constructs
Generated C++
import dom # only for the javascript backend
proc onLoad(event: Event) =
let p = document.createElement("p")
p.innerHTML = "Click me!"
p.style.fontFamily = "Helvetica"
p.style.color = "red"
p.addEventListener("click",
proc (event: Event) =
window.alert("Hello World!")
)
document.body.appendChild(p)
window.onload = onLoad
import htmlgen, jester, re # html, server & regular expressions
routes:
get "/hello/@name?":
# This matches "/hello/fred" and "/hello/bob" => ``@"name"`` will
# be either "fred" or "bob". It will also match "/hello/".
if @"name" == "":
resp "No name received :("
else:
resp "Hello " & @"name"
# Matches URLs of the form /15.html => request.matches[0] will be 15.
get re"^\/([0-9]{2})\.html$":
resp request.matches[0]
# A greeting for the root
get "/":
resp h1("Hello world")
Nim is written in Nim
~150 .nim files => 24 sec
(4-5 sec Nim, 20 sec C compilation)
Rust is just being Rust :|
A bit more curved than Rust - same 9 year span - more momentum!
Can you understand my bias now? :)
guilty of not talking about functional languages :|
By Viktor Kirilov
The case for Nim as the successor of C++ ... and every other language!