Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork1.5k
Nim for Python Programmers
Feature | 🐍 Python | 👑 Nim |
---|---|---|
Execution model | Virtual Machine (Interpreter) | Machine code via C/C++ (Compiler) |
Written using | C (CPython) | Nim |
License | Python Software Foundation License | MIT |
Version (Major) | 3.x | 2.x |
Metaprogramming | ✔️ metaclass, exec, eval,ast (Run-time code expansion) | ✔️template, macros (Compile-time code expansion) |
Memory management | Garbage collector | Multi-paradigm memory management (garbage collectors,ARC/ORC, manual) |
Typing | Dynamic, Duck Typing | Static |
Dependent types | ❎ | ✔️ Partial support |
Generics | Duck Typing | ✔️ |
int8/16/32/64 types | ❎ | ✔️ |
uint8/16/32/64 types | ❎ | ✔️ |
float32/float64 types | ❎ | ✔️ |
Char types | ❎ | ✔️ |
Subrange types | ✔️ | ✔️ |
Enum types | ✔️ | ✔️ |
Bigints (arbitrary size) | ✔️ | ✔️jsbigints,#14696(bigints (official yet not stdlib pkg)) |
Biggest built-in integer | Unknown, limited by free memory | 18_446_744_073_709_551_615 foruint64 type |
Arrays | ✔️ | ✔️ |
Type inference | Duck typing | ✔️ |
Closures | ✔️ | ✔️ |
Operator overloading | ✔️ | ✔️ on any type |
Custom operators | ❎ | ✔️ |
Object-Oriented | ✔️ | ✔️ |
Methods | ✔️ | ✔️ |
Exceptions | ✔️ | ✔️ |
Anonymous functions | ✔️ multi-line, single-expression | ✔️ multi-line, multi-expression |
List comprehensions | ✔️ | ✔️ |
Dict comprehensions | ✔️ | ✔️ |
Set comprehensions | ✔️ | ✔️ |
Custom object comprehensions | ✔️ generator expression | ✔️ |
Pattern Matching builtin | ✔️ As of Python 3.10 | ✔️ |
Immutability of types | Basic types (number, string, bool), tuple, frozenset | ✔️ |
Immutability of variables | ❎ | ✔️ |
Function arguments immutability | Depending on type | Immutable |
Formatted string literals | ✔️ f-strings | ✔️strformat |
FFI | ✔️ ctypes, C extension API (Cython via pip) | ✔️ C, C++, Objective C, JS (depending on used backend) |
Async | ✔️ | ✔️ |
Threads | ✔️ Global Interpreter Lock | ✔️ |
Regex | ✔️ Perl-compatible | ✔️ Perl-compatible |
Documentation comments | ✔️ plain-text multi-line strings (reStructuredText via Sphinx) | ✔️ReStructuredText/Markdown |
Package publishing | ✔️ not built-in, requirestwine | ✔️ built-in,nimble |
Package manager | ✔️pip | ✔️nimble |
Code autoformatter | ✔️black and others via pip | ✔️nimpretty built-in,nimlint |
File extensions | .py, .pyw, .pyc, .pyd, .so | .nim, .nims |
Temporary intermediate representation (IR) format | .pyc (CPython VM bytecode) | C, C++, Objective C (LLVM IR via nlvm) |
Uses #!shebang on files | ✔️ | ✔️nimr ,nimcr |
REPL | ✔️ | inim,Nim4Colab,JupyterNim |
Indentation | Tabs and spaces, uniform per code block, 4 spaces by convention | Spaces only, uniform per code block, 2 spaces by convention |
Notes:
- Python anonymous function (lambdas) are known to be slow compared to normal functions.
- Python Regex claims to be PCRE compatible, but in practice PCRE Regexes may not work.
- Python "multi-line" anonymous functions may require using
;
and Linters/IDE may complain about it.
Creating a new variable usesvar
orlet
orconst
.Nim has immutability and compile-time function execution.You can assign functions to variables.
Feature | const | let | var |
---|---|---|---|
Run-Time | NO | ✔️ YES | ✔️ YES |
Compile-Time | ✔️ YES | NO | NO |
Immutable | ✔️ YES | ✔️ YES | NO |
AutoInitialized | ✔️ YES | ✔️ YES | ✔️ YES |
Reassignable | NO | NO | ✔️ YES |
Requires Assignment | ✔️ YES | ✔️ YES | NO |
Can be Global | ✔️ YES | ✔️ YES | ✔️ YES |
For advanced users, it is possible to skip variable Auto-Initialization.
Variables can be multi-line without "escaping" them or using parentheses. This is useful for long lines and long ternary operators. Minimal example:
variable=666+ \420* \42- \9assertvariable==18297
⬆️ Python ⬆️ ⬇️ Nim ⬇️
var variable=666+420*42-9assert variable==18297
This works with function calls too:
import std/strutilsvar variable=" 12345" .strip .parseIntassert variable==12345
You can use underscores, new lines, and whitespace in variable names:
let `this must be positive`: Positive=42assert this_must_be_positive==42const `this is my nice named variable`=42
You can use reserved keywords as variable names.
It's okay to usevar
while learning Nim or for quick prototyping, although it's much better to learn the difference between different variable declarations.
Spaces must be consistent on your code, mainly around operators:
echo2-1# OKecho2-1# OK
Bad inconsistent spaces:
echo2-1# Error# ^ parses as "-1"
Omitting spaces on your code has no effect on performance.
All operators are functions in Nim.
- Scope "leaks", "bugs", "glitches", etc.
forxinrange(0,9):ifx==6:print(x)print(x)
Output:
68 # Leak!
⬆️ Python ⬆️ ⬇️ Nim ⬇️
for xin0..9:if x==6: echo xecho x
Output:
Error: undeclared identifier: 'x'
Note that in the example above we use a simpleint
, so the problem may not seem severe. But ifx
were a few gigabytes of RAM in size,it would "leak" out of thefor
loop to the rest of the outer or main scope instead of being reclaimed. Nim avoids this problem.
Another example:
x=0y=0defexample():x=1y=1classC:nonlocalx,yassertx==1andy==1x=2example()
⬆️ Python ⬆️ ⬇️ Nim ⬇️
var x=0var y=0proc example()=var x=1var y=1type C=object assert x==1and y==1 x=2example()
Another example:
x=0y=0defexample():x=1y=1classC:nonlocalx,yassertx==1andy==1x=2try:raiseexceptExceptionas_:passexample()
⬆️ Python ⬆️ ⬇️ Nim ⬇️
var x=0var y=0proc example()=var x=1var y=1type C=object assert x==1and y==1 x=2try:raiseexcept Exceptionas y:discardexample()
- Boolean comparisons "bugs", "glitches", etc.
assertTrue==notFalse
Fails with the error:
SyntaxError: invalid syntax.
⬆️ Python ⬆️ ⬇️ Nim ⬇️
The Nim example compiles and runs without incident; the operator precedence is resolved correctly:
asserttrue==notfalse
Another example:
assertFalse+1assertnotTrue-1
This runs becausebool
is a subtype ofint
in Python, so it supports the same mathematical operations. In Nim this is not the case:
⬆️ Python ⬆️ ⬇️ Nim ⬇️
assertfalse+1assertnottrue-1
Does not Compile:
Error: type mismatch: got <bool, int>
block
explicitly creates a new scope, without the overhead of a function. It can have a "name" without the name polluting the local namespace, and can be interrupted anywhere without requiringreturn
.
block
can be used withvar
,let
andconst
too.
Imagine that you need to get out of a nestedif
, without executing any other code from otherif
andelse
blocks. You can do:
print("Before")# this is a function, has overhead, pollutes namespace, must return to interrupt, etc.defexample():ifTrue:print("Inside if true")if42>0:print("Inside if 42 > 0")if'z'>'a':print("Inside if z > a")return# Must return to interruptif3.14>0.0:print("Inside if 3.14 > 0.0")else:print("else of z > a")else:print("else of 42 > 0")else:print("else of true")example()# example in namespaceprint("After")
⬆️ Python ⬆️ ⬇️ Nim ⬇️
echo"Before"block example:# Creates a new explicit named scope. This is not a function; there is no overhead.iftrue: echo"Inside if true"if42>0: echo"Inside if 42 > 0"if'z'>'a': echo"Inside if z > a"break example# Gets out of block example.if3.14>0.0: echo"Inside if 3.14 > 0.0"else: echo"else of z > a"else: echo"else of 42 > 0"else: echo"else of true"# No function call. "example" is not polluting the local namespace.echo"After"
- https://nim-lang.github.io/Nim/manual.html#statements-and-expressions-block-statement
- https://nim-lang.github.io/Nim/manual.html#statements-and-expressions-block-expression
defexample(argument= [0]):argument.append(42)returnargumentprint(example())print(example())print(example())
Output:
[0,42][0,42,42][0,42,42,42]
⬆️ Python ⬆️ ⬇️ Nim ⬇️
func example(argument=@[0]):auto= argument.add42return argumentecho example()echo example()echo example()
Output:
Error:type mismatch: got<seq[int],int literal(42)>but expected oneof:proc add[T](x: varseq[T]; y:sink T) firsttype mismatch at position: 1 requiredtype for x: varseq[T] but expression 'argument' is immutable, not 'var'
When it comes to default values of function's parameters, there's an important different point to note:
Python's default values will only initialize once, while Nim's will initialize each time being called.
Import | 🐍 Python | 👑 Nim |
---|---|---|
Only one symbol, use unqualified | from math import sin | from std/math import sin |
All symbols, use unqualified | from math import * | import std/math (recommended) |
All symbols, use fully qualified | import math (recommended) | from std/math import nil |
"import as" another name | import math as potato | import std/math as potato |
Both of the above at the same time | ❎ | from std/math as m import nil |
All symbols except one, use unqualified | ❎ | import std/math except sin |
All symbols except several, use unqualified | ❎ | import std/math except sin, tan, PI |
Include another module in this module | ❎ | include somemodule |
Your modules and types are not going to collide!, even if you have types named like modules, just chill and keep coding...
In Nim,import std/math
imports all of the symbols from themath
module (sin
,cos
, etc) so that they can be used unqualified. The Python equivalent isfrom math import *
.
If you prefer to not import all the symbols, and always use qualified names instead, the Nim code isfrom std/math import nil
. Then you can callmath.sin()
,math.cos()
, etc. The Python equivalent isimport math
.
It is generally safe to import all names in Nim because the compiler will not actually compile any unused functions (so there's no overhead). Furthermore, since Nim is statically typed, it can usually distinguish between the two imported functions with the same names based on the types of the arguments they are called with. In the rare cases where the types are the same, you can still fully qualify the name to disambiguate.
The prefixstd/
enforces that the module is imported from the standard library. If a Nimble package has a module with the same name, the compiler can resolve the ambiguity and it is explicit in the code.
There is also another pseudo-prefix,pkg/
, which indices such modules are installed vianimble
.
In addition, other path-relative prefix is supported, such as./
,../
, etc. What's more, if quoted by"
, any path can be given, fox example, you may seeimport "C:\\User\\xxx\\nimcache\\xxx_d\x.nim"
in some cases.
Prefixes for Stdlib, Local modules and Nimble modules are not necessary, however, they are recommended to addstd/
,./
,pkg/
prefixes.
Python's lack of such mechanism sometimes leads to confusion. For example, considering you occasionally create a local module namedtypes.py
, then the stdlib of such a name is shallowed, which is annoying, especially if you do not know such a stdlib. Thus you just have to be very careful not to name the same with any stdlib. When is comes to Nim, just writeimport ./types
and then everything works fine.
Python and Nim share these import statements:
# Python and Nimimport foo, bar, bazimport fooimport barimport baz
Alternative syntaxes:
# Pythonimportfoo, \bar, \baz
# Nimimport foo, bar, baz# Useful for small diffs when adding/removing importsimport foo, bar, bazimport foo, bar, baz, more, imports
The variant with oneimport
statement per line is common in Python and Nim, but in Nim the formimport foo, bar, baz
is also seen often.
More examples:
## This is documentation for the module.# This is a comment.include preludeimport std/sugaras steviafrom std/mathimportnilfrom std/withas whatimportnil
__import__("math")
⬆️ Python ⬆️ ⬇️ Nim ⬇️
template imports(s)=import simports math
Sometimes in the wild you may see code samples or fileswithout the imports but they somehow work anyway. The reason is that Nim can useimport
from the compile command, or from a.nims
file:
nim c --import:sugar file.nim
nim c --import:folder/mymodule file.nim
nim js --import:strutils --include:mymodule file.nim
Sometimes projects or quick code examples use this to save some typing. Thanks to Dead Code Elimination, if the imported symbols are not used they will not exist on the compiled output.
See also:
Sometimes you may feel that Python has more symbols available by default without anyimport
compared to Nim. To get a similar experience of having the basic data structures and most common imports ready so you can get coding right away, you can useprelude:
include preludeecho now() echo getCurrentDir() echo"Hello $1".format("World")
prelude is aninclude
filethat simply imports common modules for your convenience, to save some typing.prelude works for JavaScript target too.
- If symbols are unqualified, how do you know where symbols come from?
Givenfoo()
is a symbol:
- Nim: you typically have
foo()
,with UFCS support. - Python: you typically have
object.foo()
rather thanmodule.foo()
, no UFCS.
Typically theEditor/IDEshould hint where the symbols come from, like in any other programming language:
Nim comes built-in withNimSuggest for Editor/IDE integrations.
Contrary to Python, Nim's type system has all the information about all of the symbols:
import std/macrosmacro findSym(thing:typed)= echo thing.getType.lineInfofindSym: echo# Where echo comes from?.
echo
comes from:
lib/system.nim(1929, 12)
When learning Nim, or for quick prototyping, it is okay to use the symbols fully qualified. Doing so produces no errors, but idiomatic Nim avoids this.
In Python all symbols in the module are visible and mutable from modules that import it, including symbols that should not be used or mutated outside the module.
In Nim everything is private by default and therefore is not visible from other modules. To make symbols public and visible in other modules, you have to use the asterisk*
:
let variable*=42const constant*=0.0proc someFunction*()=discardtemplate someTemplate*()=discardtype Platypus*=object fluffyness*:int
The asterisk not only makes the symbol visible to the outside world,the symbol will also appear in the generated documentation (nim doc
).When you import the module, the symbol will be automatically added to the namespace,but private (not exported) symbols without*
will not be visible.The asterisk is like avisual cue for humans. You can immediately understand what symbols are a part of the "the public API" just by looking at the module's source code.The asterisk*
is pronounced as "star".
For more information, read:https://narimiran.github.io/2019/07/01/nim-import.html
In Python, imports are a runtime operation and can fail. It is a fairly common pattern that platform-dependent imports are placed inside a try block, and an alternative or fallback inside the except:
try:importmoduleexceptImportError:importothermoduleasmoduletry:frommoduleimportsome_funcexceptImportError:# Fallback implementationdefsomefunc():returnsome_value
Nim resolves all imports at compile-time, so something like anImportError
does not exist. There's no need to handle import errors at runtime.
Arrays in Nim are fixed size, start at index0
, and must contain elements of the same type.
When passing an array to a function in Nim, the argument is an immutable reference. Nim will include run-time checks on the bounds of the arrays.
You can use anopenarray
to accept an array of any size on the function arguments,and you can uselow(your_array)
andhigh(your_array)
to query the bounds of the array.
Nimstring
is compatible withopenArray[char]
to avoid unneeded copies for optimization, andchar
is compatible withint
. Thereforestring
manipulation can be done with math in-place transparently. A function that takesopenArray[char]
accepts"abcd"
and['a', 'b', 'c', 'd']
.
Array contents are always contiguous in memory, as are arrays of arrays.
See also:
- Whats the size of the different data types?.
import std/jsontype Foo=objecttype Bar=enumtrue,false# (Weird spacing intended)assert sizeOf( Foo )==1assert sizeOf( Bar )==1assert sizeOf(bool )==1assert sizeOf( {true} )==1assert sizeOf( [true] )==1assert sizeOf( (true) )==1assert sizeOf(int8 )==1assert sizeOf( {'k':'v'} )==2assert sizeOf(int16 )==2assert sizeOf(int32 )==4assert sizeOf(float32 )==4assert sizeOf(int )==8assert sizeOf(float )==8assert sizeOf(@[true] )==8assert sizeOf(%*{} )==8assert sizeOf(pointer )==8
This is only an approximation for the empty primitives on 64-bit.
Objects in Nim behave quite differently from classes in Python.Objects support inheritance and OOP. Classes are named types in Nim.Functions (procs) are free floating functions, not bound to objects(however, you can use them in a very similar way to Python).You can call a function on objects with theobject.function()
syntax as well asfunction(object)
;these are entirely equivalent.Nim does not have an implicitself
northis
.It is best practice to put all your types near the top of the file, but this is not mandatory.
A way to imagine this is thatprocs get "glued" to the types of their arguments at compile-time, and then you can use them at runtime as if they were Python classes and methods.
From Python to Nim,as minimal as possible example:
classKitten:""" Documentation Here """defpurr(self):print("Purr Purr")Kitten().purr()
⬆️ Python ⬆️ ⬇️ Nim ⬇️
type Kitten=object## Documentation Hereproc purr(self: Kitten)= echo"Purr Purr"Kitten().purr()
Minimal inheritance example:
type Animal=objectof RootObjtype Kitten=objectof Animalassert Kittenis Animal
Python-like object orientation examples:
type Animal=refobjectof RootObj## Animal base object. age:int name:string## Attributes of base object.type Cat=refobjectof Animal## Cat inherited object. playfulness:float## Attributes of inherited object.func increase_age(self: Cat)= self.age.inc()# Cat object function, access and *modify* object.var kitten= Cat(name:"Tom")# Cat object instance.kitten.increase_age()# Cat object function used.assert kitten.name=="Tom"# Assert on Cat object.assert kitten.age==1
Inheritance example:
type LUCA=refobjectof RootObj Archea=refobjectof LUCA Prokaryota=refobjectof Archea Eukaryota=refobjectof Prokaryota Animalia=refobjectof Eukaryota Chordata=refobjectof Animalia Mammalia=refobjectof Chordata Primates=refobjectof Mammalia Haplorhini=refobjectof Primates Simiiformes=refobjectof Haplorhini Hominidae=refobjectof Simiiformes Homininae=refobjectof Hominidae Hominini=refobjectof Homininae Homo=refobjectof Hominini Homosapiens=refobjectof Homoassert Homosapiens()is LUCAassert LUCA()isnot Homosapiensassert sizeOf(Homosapiens)== sizeOf(LUCA)let human= Homosapiens()assert humanis Homosapiens
See also:
After the Cat example you are probably wondering how to dodef __init__(self, arg):
.
Python__init__()
is NimnewObject()
orinitObject()
. Lets make an__init__()
for the Cat:
type Cat=object# Cat object. age:int name:string# Attributes of Cat object.func initCat(age=2): Cat=# Cat.__init__(self, age=2)result.age= age# self.age = ageresult.name="adopt_me"# self.name = "adopt_me"var kitten= initCat()# Cat object instance.assert kitten.name=="adopt_me"# Assert on Cat object.assert kitten.age==2
Naming is a convention and best practice. When you want init forFoo
just makenewFoo()
orinitFoo()
.As you may noticeinitCat
is just a function that returns aCat
.
initFoo()
forobject
.newFoo()
forref object
.
Read the documentation for Naming things following conventions and best practices.
The object constructor is also the way to set custom default values to the attributes of your objects:
type Cat=object age:int# AutoInitialized to 0 name:string# AutoInitialized to "" playfulness:float# AutoInitialized to 0.0 sleeping:bool# AutoInitialized to falsefunc initCat(): Cat=result.age=1# Set default value to 1result.name="Bastet"# Set default value to "Bastet"result.playfulness=9.0# Set default value to 9.0result.sleeping=true# Set default value to true
A more complete structure for a basic program can be something like:
## Simple application to do Foo with the Bar.type Animal=refobjectof RootObj age:int name:string Cat=refobjectof Animal playfulness:floatfunc initCat(age=2): Cat=result= new Cat# must initialize a ref objectresult.age= ageresult.name="adopt_me"func increase_age(self: Cat)= self.age.inc()proc main()=var kitten= Cat(name:"Tom") kitten.increase_age() assert kitten.name=="Tom" assert kitten.age==1var kitten2= initCat()# new kitten assert kitten2.age==2 assert kitten2.name=="adopt_me"whenisMainModule: main()runnableExamples: echo"Optionally some documentation code examples here" assert42==42
Python objects thatinternally use code generation are very very slow,scaling with size. The more you use it the slower it runs.dataclass
,metaclass
, decorators, etc can be more than 25 ~ 50x slower than a normalclass
.pathlib.Path
and its methods can be more than 25 ~ 50x slower than a normalstr
,and defeats any optimization, including a.pyc
file.Cython does not have CTFE, so it does not help with this specifically.
- Nim code expansion is done at compile-time, making its code generation zero cost at run-time.
For example, you can see the result of ARC code expansion during compilation using--expandArc
. This is how Nim does compile-time memory management (approximation):
See also:
Python "type hints" can be almost anything and are implicitly executed at run-time. Needless to say, this can be very unsafe:
$cat example.pyclass X: _: "print('PWNED')" # os.system("rm -rf /folder ")__import__("typing").get_type_hints(X)$python3 example.py'PWNED'$
Nim types must be a valid Nim type, types are type checked at compile-time:
$cat example.nimtype X = object a: "echo('PWNED')"echo X()$nim r example.nim# Will not compile.Error: type expected, but got: "echo('PWNED')"$
Another example
$cat example.nimvar example: "echo('PWNED')"echo example$nim r example.nim# Will not compile.Error: type expected, but got: "echo('PWNED')"$
- Does Nim passes data around "by value" or "by reference"? It depends ...
The Nim compiler automatically determines whether a parameter is passed by-value or by-reference based on the parameter type's size.
If a parameter must be passed by-value or by-reference (such as when interfacing with a C library),then use the{.bycopy.}
or{.byref.}
pragmas.
Nim passes objects larger than3 * sizeOf(int)
by reference for performance,but this is architecture and implementation defined.So the following information is just anapproximation forx86_64
:
Declaration | Value or Reference? | Implicit or Explicit? | Managed or Unmanaged? | Observations |
---|---|---|---|---|
symbol: int | By value | Implicit | Managed | Frequent use |
symbol: var int | By reference | Implicit | Managed | Frequent use |
symbol: ref int | By reference | Explicit | Managed | Rare |
symbol: ptr int | By reference | Explicit | Unmanaged | C/C++ FFI |
symbol: var ref int | By reference | Implicit | Managed | Rare |
symbol: var ptr int | By reference | Implicit | Unmanaged | Rare |
symbol: pointer | By reference | Explicit | Unmanaged Pointer | C/C++ FFI |
- Iff a "by value" symbol is big, then it is passed "by reference" automatically.
- You can disable this optimization using
{.bycopy.}
pragma on the symbol. - The
{.byref.}
forces passing "by reference", the reverse of{.bycopy.}
. - Nim
seq
is passed around "by reference" by default. - Nim
string
isCopy-On-Write COW (ARC/ORC). - Pointer Arithmetic can be performed with pointer.
In Python, simple integer for loops use therange
generator function. For the 1- and 2- argument forms of this function, nim's..
iterator works almost the same way:
for iin0..10: echo i# Prints 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10for iin5..10: echo i# Prints 5, 6, 7, 8, 9, 10
Note that the..
operator includes the end of the range, whereas Python'srange(a, b)
does not includeb
. If you prefer this behavior, use the..<
iterator instead:
for iin0..<10: echo i# Prints 0, 1, 2, 3, 4, 5, 6, 7, 8, 9
Pythonrange()
also has an optional third parameter,which is the value to increment by each step. This can be positive or negative.If you need this behavior, use thecountup
orcountdown
iterators:
for iin countup(1,10,2): echo i# Prints 1, 3, 5, 7, 9for iin countdown(9,0,2): echo i# Prints 9, 7, 5, 3, 1
Convert fromrange
toseq
:
import sequtilsconst subrange=0..9const seqrange= toSeq(subrange)assert seqrangeisseq[int]
See also:
The syntax for slice ranges is different. Python'sa[x:y]
is Nim'sa[x ..< y]
.
let variable= [1,2,3,4]assert variable[0..0]==@[1]assert variable[0..1]==@[1,2]assert variable[0..<2]==@[1,2]assert variable[0..3]==@[1,2,3,4]
In Nim a reverse index or backwards index uses^
with the number, like^1
. Backwards indexes have a specific typeBackwardsIndex
,and they can also be "prepared" at compile-time as aconst
:
const lastOne=^1# Compile-timeassert lastOneis BackwardsIndexassert [1,2,3,4,5][2.. lastOne]==@[3,4,5]assert [1,2,3,4,5][2..^1]==@[3,4,5]var another=^3# Run-timeassert [1,2,3,4,5][0.. another]==@[1,2,3]assert [1,2,3,4,5][^3..^1]==@[3,4,5]# 2 Reverse index
Lets compare very simplified examples:
[0,1,2][9]# No Index 9
This crashes at run-time because there is no index 9:
$python3 example.pyTraceback (most recent call last): File "example.py", line 1, in <module> [0, 1, 2][9]IndexError: list index out of range$
Let's see Nim:
discard [0,1,2][9]# No Index 9
Compile and run:
$nim compile --run example.nimexample.nim(1, 19) Warning: can prove: 9 > 2 [IndexCheck]example.nim(1, 18) Error: index 9 not in 0..2 [0, 1, 2][9]$
Nim checks at compile-time that[0, 1, 2]
has no index9
, because9 > 2
. Therefore, it won't compile nor run.
This also works with Subrange. Let's say you have a integer variable thatmust be positive:
let must_be_positive: Positive=-9
Compile and run:
$nim compile --run example.nimexample.nim(1, 34) Warning: can prove: 1 > -9 [IndexCheck]example.nim(1, 34) Error: conversion from int literal -9 to Positive is invalid.$
Nim checks at compile-time thatmust_be_positive
is notPositive
because1 > -9
. It won't compile nor run.
Another example:
var variable0:5..8=5# int range type, value must be between '5' and '8'.variable0=8variable0=7assertnot compiles(variable0=4)assertnot compiles(variable0=9)assertnot compiles(variable0=0)assertnot compiles(variable0=-1)assertnot compiles(variable0=-9)var variable1:3.3..7.5=3.3# float range type, value must be between '3.3' and '7.5'.variable1=7.5variable1=5.5assertnot compiles(variable1=3.2)assertnot compiles(variable1=7.6)assertnot compiles(variable1=0.0)assertnot compiles(variable1=-1.0)assertnot compiles(variable1=-9.0)var variable2:'b'..'f'='b'# char range type, value must be between 'b' and 'f'.variable2='f'variable2='c'assertnot compiles(variable2='g')assertnot compiles(variable2='a')assertnot compiles(variable2='z')assertnot compiles(variable2='0')assertnot compiles(variable2='9')var variable3: Positive=1# Positive type, value must be > 0.variable3=1variable3=999assertnot compiles(variable3=0)assertnot compiles(variable2=-1)assertnot compiles(variable2=-9)var variable4: Natural=0# Natural type, value must be >= 0.variable4=1variable4=999assertnot compiles(variable4=-1)assertnot compiles(variable4=-9)
You can control this with--staticBoundChecks:on
or--staticBoundChecks:off
.
With--staticBoundChecks:off
it may raise an error at run-time like Python does.
- For better documentation see:https://nim-lang.github.io/Nim/drnim.html
Python does not have anull-coalescing operator (at the time of writing).
Python programmers use theor
operator instead:
other=baror"default value"
Nim has awrapnils module with a?.
null-coalescing operator,which simplifies code by reducing the need forif..elif...else
branches aroundintermediate values that may be null.
assert?.foo.bar.baz==""# "bar" may be Null?, or not ?.
Null isNone
in Python. Null isnil
in Nim.
See:https://nim-lang.github.io/Nim/wrapnils.html
There is no direct built-in equivalent to Python'swith
construct. In Nim there are the following options:
See the Templates section for examples.
Lang | String | Multi-line string | Raw String | Multi-line Raw string | Formatted Raw Literals | Quote |
---|---|---|---|---|---|---|
🐍 Python | "foo" | """foo""" | r"foo" | r"""foo""" | fr"""{1 + 2}""" | " ' |
👑 Nim | "foo" | - | r"foo" | """foo""" /r"""foo""" | fmt"""{1 + 2}""" | " |
Nim string operations requireimport
:
import std/strutils
. for ASCII.import std/unicode
. for Unicode.
For the table below, in ' 👑 Nim' column:
- if there are two items, written as
A/B
, whereB
being~
means it's the same asA
,it means the left one is fromstd/strutils
and the right fromstd/unicode
.
Ops | 🐍 Python | 👑 Nim |
---|---|---|
Lower | "ABCD".lower() | "ABCD".toLowerAscii() /"ABCD".tolower() |
Strip | " ab ".strip() | " ab ".strip() /~ |
Split | "a,b,c".split(",") | "a,b,c".split(",") /~ |
Concatenation | "a" + "b" | "a" & "b" |
Find | "abcd".find("c") | "abcd".find("c") |
Starts With | "abc".startswith("ab") | "abc".startswith("ab") |
Ends With | "abc".endswith("ab") | "abc".endswith("ab") |
Split Lines | "1\n2\n3".splitlines() | "1\n2\n3".splitlines() |
Slicing | "abcd"[0:2] | "abcd"[0 ..< 2] |
Slicing 1 char | "abcd"[2] | "abcd"[2] |
Reverse Slicing | "abcd"[-1] | "abcd"[^1] |
Count Lines | len("1\n2\n3".splitlines()) | "1\n2\n3".countLines() |
Repeat | "foo" * 9 | "foo".repeat(9) |
Indent | textwrap.indent("foo", " " * 9) | "foo".indent(9) |
Unindent | textwrap.dedent("foo") | "foo".unindent(9) |
Parse Bool | bool(distutils.util.strtobool("fALse")) ❓ | parseBool("fALse") |
Parse Int | int("42") | parseInt("42") |
Parse Float | float("3.14") | parseFloat("3.14") |
Formatted String Literals | f"foo {1 + 2} bar {variable}" | fmt"foo {1 + 2} bar {variable}" |
Levenshtein distance | ❎ | editDistance("Kitten", "Bitten") |
- The string ops in
std/strutils
is in fact more like Python's bytes methods, while those instd/unicode
is for Python'sstr
methods. - Avery detailed comparison.
Single memory allocation strings can be done withnewStringOfCap(capacity = 42)
,that returns a new empty string""
but with allocatedcapacity
of42
. If you pass beyond thecapacity
it will not crash nor buffer overflow:
variable=""assertvariable==""# length is 0, capacity is 0, 1 allocations, 0 copiesvariable+="a"# length is 1, capacity is 1, 2 allocations, 1 copiesvariable+="b"# length is 2, capacity is 2, 3 allocations, 2 copiesvariable+="c"# length is 3, capacity is 3, 4 allocations, 3 copiesvariable+="d"# length is 4, capacity is 4, 5 allocations, 4 copiesassertvariable=="abcd"# TOTAL: 5 allocations, 4 copies
⬆️ Python ⬆️ ⬇️ Nim ⬇️
var variable= newStringOfCap(2)assert variable==""# length is 0, capacity is 2, 1 allocations, 0 copiesvariable.add"a"# length is 1, capacity is 2, 1 allocations, 0 copiesvariable.add"b"# length is 2, capacity is 2, 1 allocations, 0 copiesvariable.add"c"# length is 3, capacity is 3, 2 allocations, 0 copiesvariable.add"d"# length is 4, capacity is 4, 3 allocations, 0 copiesassert variable=="abcd"# TOTAL: 3 allocations, 0 copies
This difference may get bigger for strings inside for loops or while loops.
Nimstrformat
implements formatted string literals inspired by Python f-strings.strformat
is implemented using metaprogramming and the code expansion is done at compile-time.It also works for the JavaScript target.
Similar to Python f-strings, you candebug the key-value inside the string using an equal sign.fmt"{key=}"
expands tofmt"key={value}"
:
let x="hello"assert fmt"{x=}"=="x=hello"assert fmt"{x=}"=="x = hello"
Nimstrformat
supports backslash, while Python's f-strings do not till Python3.12:
>>>print(f"""{"yep\nope"}""" )# Run-time error.Error:f-stringexpressionpartcannotincludeabackslash.
⬆️ Python ⬆️ ⬇️ Nim ⬇️
echo fmt"""{"yep\nope"}"""# Nim works.yepope
You can choose a custom character pair to open and close the formatting inside the string by passing thechar
as argument:
import std/strformatlet variable=42assert fmt("( variable ) { variable }",'(',')')=="42 { variable }"assert fmt("< variable > { variable }",'<','>')=="42 { variable }"
Using characters like backtick and space' '
works:
import std/strformatlet variable=42assert fmt(" variable`{variable}",' ','`')=="42{variable}"
fmt"xxx"
is almostly equal tofr"xxx"
, whilefmt "xxx"
or&"xxx"
is equal tof"xxx"
. (why?)- It is recommended to read the full documentation of
strformat
.
Python raw string can not end in"\"
, but Nim raw string works just fine:
>>>print(r"\")# Run-time error.SyntaxError:EOLwhilescanningstringliteral.
⬆️ Python ⬆️ ⬇️ Nim ⬇️
nim> echor"\""\"
This may be relevant when working with strings that use"\"
, like filesystems pathsr"C:\mypath\"
, etc.
In column of ' 👑 Nim', if one item has a prefix, like
db_connector/db_sqlite
, it means such lib has been removed from Nim's stdlib since version 2.0, thus requiring being installed vianimble
before used.
Use | 🐍 Python | 👑 Nim |
---|---|---|
Operating System | os | os |
String operations | string | strutils |
Date & time | datetime | times |
Random | random | random |
Regular expressions (Backend) | re | re |
Regular expressions (Frontend) | ❎ | jsre |
HTTP | urllib | httpclient |
Logging | logging | logging |
Run external commands | subprocess | osproc |
Path manipulation | pathlib, os.path | os,files,paths |
Mathematic | math, cmath | math,complex |
MIME Types | mimetypes | mimetypes |
SQLite SQL | sqlite3 | db_connector/db_sqlite |
Postgres SQL | ❎ | db_connector/db_postgres |
Levenshtein Distance | ❎ | editdistance |
Serialization | pickle | json,jsonutils,marshal |
Base64 | base64 | base64 |
Open web browser URL | webbrowser | browsers |
Async | asyncio | asyncdispatch,asyncfile,asyncnet,asyncstreams |
Unittests | unittest | unittest |
Diff | difflib | diff |
Colors | colorsys | colors |
MD5 | hashlib.md5 | checksums/md5 |
SHA1 | hashlib.sha1 | checksums/sha1 |
HTTP Server | http.server | asynchttpserver |
Lexer | shlex | lexbase |
Multi-Threading | threading | threadpool |
URL & URI | urllib.parse | uri |
CSV | csv | parsecsv |
Parse command line arguments | argparse | parseopt |
SMTP | smtplib | smtp |
HTTP Cookies | http.cookies | cookies |
Statistics | statistics | stats |
Text wrapping | textwrap | wordwrap |
Windows Registry | winreg | registry |
POSIX | posix | posix,posix_utils |
SSL | ssl | openssl |
CGI | cgi | cgi |
Profiler | cprofile, profile | nimprof |
Monotonic time | time.monotonic | monotimes |
Run functions at exit | atexit | exitprocs |
Set file permissions | os, stat | os,filepermissions |
Recursive walk of filesystem | os.walk | os.walkDirRec,globs.walkDirRecFilter |
Templating engine | string.Template | Source Code Filters |
Deques | collections.deque | deques |
Critical Bit Tree Dict/Set | ❎ | critbits |
Parse JSON | json | parsejson,json |
Parse INI | configparser | parsecfg |
Parse XML | xml | parsexml,xmltree |
Parse HTML | html.parser | htmlparser |
Parse SQL | ❎ | parsesql |
Colors on the Terminal | ❎ | terminal |
Linux Distro Detection | ❎ | distros |
HTML Generator | ❎ | htmlgen |
Arrow Functions | ❎ | sugar |
In-Place to Work-on-Copy | ❎ | sugar.dup |
Syntax Sugar | ❎ | sugar |
JavaScript & Frontend | ❎ | dom,asyncjs,jscore,jsffi,dom_extensions,jsre |
- This is not complete, but just a quick overview. For more information seehttps://nim-lang.org/docs/lib.html
Tuples are fixed size, start at index0
, can contain mixed types, and can be anonymous or named. Named tuples have no extra overhead over anonymous tuples.
(1,2,3)
⬆️ Python ⬆️ ⬇️ Nim ⬇️
(1,2,3)
- Nim allows the fields to be named, without requiring the tuple itself to be named. Python NamedTuple requires
import collections
, and we need to give it a dummy underscore name:
collections.namedtuple("_","key0 key1")("foo",42)
⬆️ Python ⬆️ ⬇️ Nim ⬇️
(key0:"foo", key1:42)
- We can also name both the fields and the tuple:
collections.namedtuple("NameHere","key0 key1")("foo",42)
⬆️ Python ⬆️ ⬇️ Nim ⬇️
type NameHere=tuple[key0:string, key1:int]var variable: NameHere= (key0:"foo", key1:42)
tuple[key0: string, key1: int]
is thetypedesc
for declarations.(key0: "foo", key1: 42)
is the literal for assignements.
Nim tuples are a lot like Python NamedTuple in that the tuple members have names.
Do NOT use named tuples to "mimic" proper objects (the compiler re-uses generic instantiations for "identical" tuples).
Seemanual for a more in depth look at tuples.
Nim sequences arenot fixed size. They can grow and shrink, start at index0
, and must contain elements all of the same type.
["foo","bar","baz"]
⬆️ Python ⬆️ ⬇️ Nim ⬇️
@["foo","bar","baz"]
@is a function that converts fromarray
toseq
.
variable= [itemforitemin (-9,1,42,0,-1,9)]
⬆️ Python ⬆️ ⬇️ Nim ⬇️
let variable= collect(newSeq):for itemin@[-9,1,42,0,-1,9]: item
A comprehension can be assigned toconst
too, and it will run at compile-time.
The comprehension is implemented as amacro
that is expanded at compile-time,you can see the expanded code using the--expandMacro
compiler option:
let variable=var collectResult= newSeq(Natural(0))for itemin items(@[-9,1,42,0,-1,9]): add(collectResult, item) collectResult
The comprehensions can be nested, multi-line, multi-expression, all with zero overhead:
import std/sugarlet values= collect(newSeq):for valin [1,2]: collect(newSeq):for val2in [3,4]:if (val, val2)!= (1,2): (val, val2) assert values==@[@[(1,3), (1,4)],@[(2,3), (2,4)]]
Single-line example:
print([iforiinrange(0,9)])
⬆️ Python ⬆️ ⬇️ Nim ⬇️
echo(block: collect newSeq: (for iin0..9: i))
Python comprehensions convert the code to a generator, but Nim comprehensions do not convert the code to an iterator:
import std/sugarfunc example()=discard collect(newSeq):for itemin@[-9,1,42,0,-1,9]:if item==0:return itemexample()
⬆️ Nim ⬆️ ⬇️ Python ⬇️
defexample(): [itemforitemin [-9,1,42,0,-1,9]ifitem==0:return]example()
Python complains:
SyntaxError: invalid syntax.
Some things that in Python are syntactically disallowed inside comprehensions (likereturn
) are allowed in Nim. This is because Nim comprehensions are just macros that expand to normal code.
- Whats
collect()
?.
collecttakes as argument whatever your returning type uses as the constructor.
variable= {key:valueforkey,valueinenumerate((-9,1,42,0,-1,9))}
⬆️ Python ⬆️ ⬇️ Nim ⬇️
let variable= collect(initTable(4)):for key, valuein@[-9,1,42,0,-1,9]: {key: value}
collect()
requiresimport std/sugar
.
variable= {itemforitemin (-9,1,42,0,-1,9)}
⬆️ Python ⬆️ ⬇️ Nim ⬇️
let variable= collect(initHashSet):for itemin@[-9,1,42,0,-1,9]: {item}
collect()
requiresimport std/sugar
.
Lang | Set | Ordered Set | Bitset | Bit Fields | Imports |
---|---|---|---|---|---|
🐍 Python | set() | ❎ | ❎ | ❎ | |
👑 Nim | HashSet() | OrderedSet() | set | Bit Fields | import std/sets |
- Python Set can be replaced withHashSet.
Python sets are not like theNim set type. The "default" set is abitset. For every possible value of the contained type, it stores 1 bit indicating whether it is present in the set. This means you should use it if the type has a finite, limited range of possible values. If all of the possible values are also known at compile-time, you can create anEnum
for them.
The biggest integer you can fit on a set normally is65535
equals tohigh(uint16)
.
You can fit bigger integers using an integer Subrange, if you don't need small integers. An example really stressing set to fit2_147_483_647
equals tohigh(int32)
on a set at compile-time:
const x= {range[2147483640..2147483647](2147483647)}assert xisset# Equals to {2147483647}
The basic Nim set type is fast and memory-efficient, compared to theHashSet which is implemented as a dictionary. For simple flag types and small mathematical sets, use set. For larger collections, or if you are just learning, use HashSet.
Usetables for Python-like dicts.
Lang | Dictionary | Ordered Dictionary | Counter | Imports |
---|---|---|---|---|
🐍 Python | dict() | OrderedDict() | Counter() | import collections |
👑 Nim | Table() | OrderedTable() | CountTable() | import std/tables |
dict(key="value",other="things")
⬆️ Python ⬆️ ⬇️ Nim ⬇️
to_table({"key":"value","other":"things"})
collections.OrderedDict([(8,"hp"), (4,"laser"), (9,"engine")])
⬆️ Python ⬆️ ⬇️ Nim ⬇️
to_ordered_table({8:"hp",4:"laser",9:"engine"})
collections.Counter(["a","b","c","a","b","b"])
⬆️ Python ⬆️ ⬇️ Nim ⬇️
to_count_table("abcabb")
Examples:
import std/tablesvar dictionary= to_table({"hi":1,"there":2})assert dictionary["hi"]==1dictionary["hi"]=42assert dictionary["hi"]==42assert len(dictionary)==2assert dictionary.has_key("hi")for key, valuein dictionary: echo key, value
B-Tree based generic sorted Tables using the same API.
See also:
"result0"ifconditionalelse"result1"
⬆️ Python ⬆️ ⬇️ Nim ⬇️
if conditional:"result0"else:"result1"
In Nim the "ternary operator" is simply anif..else
inline. Unlike Python, the ordinaryif..else
is an expression, so it can be assigned to a variable. These snippets are equivalent:
var foo=if3<10:50else:100
var foo=if3<10:50else:100
Reading files line by line
withopen("yourfile.txt","r")asf:forlineinf:print(line)
⬆️ Python ⬆️ ⬇️ Nim ⬇️
for linein lines("yourfile.txt"): echo line
lines()
Documentationhttps://nim-lang.org/docs/syncio.html#lines.i%2Cstring
Reading and writing files:
write_file("yourfile.txt","this string simulates data")assert read_file("yourfile.txt")=="this string simulates data"
Reading files at compile-time:
const constant= static_read("yourfile.txt")# Returns a string at compile-time
importosos.chmod("file.txt",0o777)
⬆️ Python ⬆️ ⬇️ Nim ⬇️
import fusion/filepermissionschmod"file.txt",0o777
These examples assume a file"file.txt"
exists.Both use the octal Unix file permissions.Also a lower level API is available onos
module.
Seehttps://nim-lang.github.io/fusion/src/fusion/filepermissions.html
importosclasswithDir:# Unsafe without a __del__()def__init__(self,newPath):self.newPath=os.path.expanduser(newPath)def__enter__(self):self.savedPath=os.getcwd()os.chdir(self.newPath)def__exit__(self,etype,value,traceback):os.chdir(self.savedPath)withwithDir("subfolder"):print("Inside subfolder")print("Go back outside subfolder")
⬆️ Python ⬆️ ⬇️ Nim ⬇️
import fusion/scriptingwithDir"subfolder": echo"Inside subfolder"echo"Go back outside subfolder"
These examples assume a folder"subfolder"
exists.Python optionally has third-party dependencies which do the same thing; the examples use the standard library.Some Python third-party dependencies may convert the code insidewithDir
to a generator, forcing you to change the code (likereturn
toyield
etc), examples use standard library.
Seehttps://nim-lang.github.io/fusion/src/fusion/scripting.html
defisPositive(arg:int)->bool:returnarg>0map(isPositive, [1,2,-3,5,-9])filter(isPositive, [1,2,-3,5,-9])
⬆️ Python ⬆️ ⬇️ Nim ⬇️
proc isPositive(arg:int):bool=return arg>0 echo map([1,2,-3,5,-9], isPositive)echo filter([1,2,-3,5,-9], isPositive)
- map and filter operations require
import std/sequtils
.
variable:typing.Callable[[int,int],int]=lambdavar1,var2:var1+var2
⬆️ Python ⬆️ ⬇️ Nim ⬇️
var variable=proc (var1, var2:int):int= var1+ var2
Multi-line example:
var anon=func (x:int):bool=if x>0:result=trueelse:result=falseassert anon(9)
Python anonymous functions can not usereturn
, but it just works in Nim:
example=lambda:return42assertexample()==42
ComplainsSyntaxError: invalid syntax
.
⬆️ Python ⬆️ ⬇️ Nim ⬇️
let example=func:int=return42assert example()==42
Python anonymous functions can not useyield
, but it just works in Nim:
example=lambda:foriinrange(0,9):yieldifor_inexample():pass
ComplainsSyntaxError: invalid syntax
.
⬆️ Python ⬆️ ⬇️ Nim ⬇️
let example=iterator:int=for iin0..9:yield ifor _in example():discard
Anonymous procs in Nim are basically functions without a name.
- Templates and macros can be used similarly to Python's decorators.
defdecorator(argument):print("This is a Decorator")returnargument@decoratordeffunction_with_decorator()->int:return42print(function_with_decorator())
⬆️ Python ⬆️ ⬇️ Nim ⬇️
template decorator(argument:untyped)= echo"This mimics a Decorator" argumentfunc function_with_decorator():int {.decorator.}=return42echo function_with_decorator()
- Why doesn't Nim use
@decorator
syntax?.
Nim uses{.
and.}
because it can have several decorators together.
Also in Nim one works on variables and types:
func function_with_decorator():int {.discardable, inline, compiletime.}=return42let variable {.compiletime.}=1000/2type Colors {.pure.}=enum Red, Green, Blue
@is a function that converts fromarray
toseq
.
Python uses multi-line strings with JSON inside, Nim uses literal JSON directly in the code.
importjsonvariable="""{ "key": "value", "other": true}"""variable=json.loads(variable)print(variable)
⬆️ Python ⬆️ ⬇️ Nim ⬇️
import std/jsonvar variable=%*{"key":"value","other":true}echo variable
%*
converts everything inside the braces to JSON, JSON has a typeJsonNode
.%*
can have variables and literals inside the braces.- JSON can have Nim comments inside the braces of
%*
. - If the JSON is not valid JSON, the code will not compile.
JsonNode
can be useful in Nim because is a type that can havemixed types and can grow/shrink.- You can read JSON at compile-time, and store it in a constant as a string.
- To parse JSON from a string you can use
parseJson("{}")
. - To parse JSON from a file use
parseFile("file.json")
. - JSON documentation
if__name__=="__main__":main()
⬆️ Python ⬆️ ⬇️ Nim ⬇️
whenisMainModule: main()
importunittestdefsetUpModule():"""Setup: Run once before all tests in this module."""passdeftearDownModule():"""Teardown: Run once after all tests in this module."""passclassTestName(unittest.TestCase):"""Test case description"""defsetUp(self):"""Setup: Run once before each tests."""passdeftearDown(self):"""Teardown: Run once after each test."""passdeftest_example(self):self.assertEqual(42,42)if__name__=="__main__":unittest.main()
⬆️ Python ⬆️ ⬇️ Nim ⬇️
import std/unittestsuite"Test Name": echo"Setup: Run once before all tests in this suite." setup: echo"Setup: Run once before each test." teardown: echo"Teardown: Run once after each test." test"example": assert42==42 echo"Teardown: Run once after all tests in this suite."
- Unittest documentation.
- The Nimble package manager can also run Unittests.
- NimScript can also run Unittests.
- You can run documentation as Unittests with
runnableExamples
.
assert
can take ablock
. You can customize the message for better user experience:
let a=42let b=666doAssert a== b,block: ("\nCustom Error Message!:"&"\n a equals to"&$a&"\n b equals to"&$b)
An alternative tounittest
. It is prepared for big projects and has more features.
- https://nim-lang.github.io/Nim/testament.html(Recommended)
Docstrings in Nim are ReSTructuredTextand MarkDown comments starting with##
. ReSTructuredText and MarkDown can be mixed together if you want.
Generate HTML, Latex (PDF) and JSON documentation from source code withnim doc file.nim
.
Nim can generate a dependency graph DOT.dot
file withnim genDepend file.nim
.
You can run documentation as Unittests withrunnableExamples
.
"""Documentation of module"""classKitten(object):"""Documentation of class"""defpurr(self):"""Documentation of method"""print("Purr Purr")
⬆️ Python ⬆️ ⬇️ Nim ⬇️
## Documentation of Module *ReSTructuredText* and **MarkDown**type Kitten=object## Documentation of type age:int## Documentation of fieldproc purr(self: Kitten)=## Documentation of function echo"Purr Purr"
You can write constructs likeif..then
andtry..except..finally
on a single line without errors or warnings; indentation is optional. Obviously, this is only a good idea if the snippet is short and simple.
let a=try:1+2except:42finally: echo"Inline try"let b=iftrue:2/4eliffalse:4*2else:0for iin0..9: echo iproc foo()= echo"Function"(proc ()= echo"Anonymous function")()template bar()= echo"Template"macro baz()= echo"Macro"var i=0while i<9: i+=1whenisMainModule: echo42
- Why is Nim CamelCase instead of snake_case?.
It really isn't, Nim is style-agnostic!
let camelCase=42# Declaring as camelCaseassert camel_case==42# Using as snake_caselet snake_case=1# Declaring as snake_caseassert snakeCase==1# Using as camelCaselet `free style`=9000assert free_style==9000
This feature allows Nim to seamlessly interoperate with a lot of programming languages with different casing styles.
For more homogeneous code you can enforce a default casing style using the compiler command--styleCheck:hint
. Nim willstyle check your code before compilation, similar topycodestyle
in Python. If you want even more strict styling you can use--styleCheck:error
.
Nim comes with a builtin code auto-formatter named Nimpretty.
A lot of programming languages have some kind of case-insensitivity, such as:PowerShell, SQL, PHP, Lisp, Assembly, Batch, ABAP, Ada, Visual Basic, VB.NET, Fortran, Pascal, Forth, Cobol, Scheme, Red, Rebol.
If you are just starting from scratch, you can use Python-like names while learning. It will not produce an error unless you tell the compiler you want that.
- Why doesn't Nim use
def
instead ofproc
?.
Nim usesproc
for normal functions, which is short for "procedure".
Usefunc
for when your routine cannot and should not access global or thread-local variables (see also:pure functions).
Nim has side-effects tracking.
You can not useecho
insidefunc
, becauseecho
mutatesstdout
, which is a side-effect. UsedebugEcho
instead.
See also:
If you are just starting from scratch, you can useproc
for all the functions while learning. It will not produce an error for doing so.
Nim has had async built-in for a long time. It works as you may expect withasync
,await
,Future
, etc.
asyncdispatch is a module to write concurrent code using theasync
/await
syntax.
Future
is a type (like a Future in Python, or a Promise in JavaScript).
{.async.}
is a pragma that converts functions to async (likeasync def
in Python).
Let's convert the official Python asyncioHello World to Nim:
asyncdefmain():print("Hello ...")awaitasyncio.sleep(1)print("... World!")asyncio.run(main())
⬆️ Python ⬆️ ⬇️ Nim ⬇️
proc main() {.async.}= echo("Hello ...") await sleep_async(1) echo("... World!")wait_for main()
Internally async is implemented using metaprogramming (macros, templates, pragmas, etc).
Description | asyncCheck | waitFor | await |
---|---|---|---|
Waits for the Future to complete | ❎ | ✔️ | ✔️ |
Ignores the Future | ✔️ | ❎ | ❎ |
Returns result inside Future | ❎ | ✔️ | ✔️ |
Only available inside async | ❎ | ❎ | ✔️ |
- Why doesn't Nim use
async def
?.
Async is just amacro
in Nim, no need to change the syntax of the language. It is like a decorator in Python, only more powerful.
Also in Nim the same function can be asynchronousand synchronous at the same time, with the same code, with the same name.
In Python when you have a library"foo", you may need bothfoo
(sync) andaiofoo
(async), which are usually completely different projects, repos, developers and APIs. This is not needed in Nim, or rarely seen, thanks to said feature.
Because async is just amacro
in Nim you can create your own async your way too.
See alsoasyncfile,asyncnet,asyncstreams,asyncftpclient,asyncfutures.
You never have to actually manually edit C, the same way in Python you never manually edit the .pyc files.
In Nim you code by writing Nim, the same way in Python you code by writing Python.
A template replaces its invocation with the template body at compile-time.
Essentially,the compiler will copy and paste a chunk of code for you.
A template allows us to have a function-like constructs without any overhead, orto split huge functions into smaller parts.
Too many function and variable names may pollute the local namespace.Variables inside templates do not exist outside of their template.Templates do not exist in the namespace at run-time (if you do not export them),Templates may optimize certain values if they are known at compile-time.
Templatescannot doimport
norexport
of libraries automatically implicitly. Templates donot "auto-import" symbols used inside itself. If you use any imported library on the body of a template, you must import that library when invoking that template.
Inside templates you can not usereturn
because a template is not a function.
Templates allow you to implement very high-level, beautiful APIs for everyday usage, while keeping the low-level optimized details out of your head andDRY.
Pythonwith open("file.txt", mode = "r") as file:
implemented using a template:
The GIF is not perfect, but a lazy simplified approximation!
This is not the way to read files in Nim, just an exercise.
This template is not perfect, but a lazy approximation. It is an exercise for the reader to try to improve it ;P
template withOpen(name:string, mode:char, body:untyped)=let flag=if mode=='w': fmWriteelse: fmRead# "flag" doen't exist outside of this templatelet file {.inject.}= open(name, flag)# Create and inject `file` variable, `file` exists outside of this template because of {.inject.}try: body# `body` is the code passed as argumentfinally: file.close()# Code after the code passed as argumentwithOpen("testing.nim",'r'):# Mimic Python with `open("file", mode='r') as file` echo"Hello Templates"# Code inside the template, this 2 lines are "body" argument on the template echo file.read_all()# This line uses "file" variable
If you are just starting from scratch, do not worry, you can use functions for everything while learning.
Sharing variables between functions is similar to Python.
Global variable:
global_variable=""deffunction0():globalglobal_variableglobal_variable="cat"deffunction1():globalglobal_variableglobal_variable="dog"function0()assertglobal_variable=="cat"function1()assertglobal_variable=="dog"function0()assertglobal_variable=="cat"
⬆️ Python ⬆️ ⬇️ Nim ⬇️
var global_variable=""proc function0()= global_variable="cat"proc function1()= global_variable="dog"function0()assert global_variable=="cat"function1()assert global_variable=="dog"function0()assert global_variable=="cat"
Object Attribute:
classIceCream:def__init__(self):self.object_attribute=Nonedeffunction_a(food):food.object_attribute=9deffunction_b(food):food.object_attribute=5food=IceCream()function_a(food)assertfood.object_attribute==9function_b(food)assertfood.object_attribute==5function_a(food)assertfood.object_attribute==9
⬆️ Python ⬆️ ⬇️ Nim ⬇️
type IceCream=object object_attribute:intproc functiona(food:var IceCream)= food.object_attribute=9proc functionb(food:var IceCream)= food.object_attribute=5var food= IceCream()functiona(food)assert food.object_attribute==9functionb(food)assert food.object_attribute==5functiona(food)assert food.object_attribute==9
You can pass functions as arguments to other functions like in Python. Functions (procs) are first class objects.
If you are migrating from an interpreted language, like Python or JavaScript, you may find strange mentions of "In-Place" and "Out-Place" somewhere in Nim. If you don't know what it means then Nim looks like it has duplicated functions.
Python allocates a new string or object when something in it changes somehow. Let's say you have a huge string in a variable and you want to change a single character. Since Python strings are immutable, the only solution is to duplicate the string in memory but with the new copy having that character changed. Returning a new copy is an "Out-Place" operation. Most of Python works like this.
On the other hand, Nim's strings are mutable. In Nim you can change only the character you want to change, rather than copying the string in memory. Some functions work in-place, some functions work on a new copy. The documentation will (usually) tell which.
usingmacro
Nim can turn from an in-place function to out-place one.
Nim stdlib modules designed for the JavaScript target usually work on a new copy. This is just how the JavaScript target is; there is no in-place API nor benefits from using it.
Some Nim stdlib modules that work on a new copy may or may not be changed to work in-place in the future.
Examples:
import std/sugar# sugar.dupfunc inplace_function(s:varstring)=# Does not use "string" but "var string" s="CHANGED"# In-Place algo.var bar="in-place"inplace_function(bar)## Variable mutated in-place.assert bar=="CHANGED"# Out-Place algo.assert"out-place".dup(inplace_function)=="CHANGED"## Variable mutated on a new copy.
- A lib providing powerful Python builtins, syntax and some stdlib supported, just write Python in Nim!https://github.com/nimpylib/pylib
- https://github.com/yglukhov/nimpy/wiki#publish-to-pypi
- https://github.com/sstadick/ponim/blob/master/README.md#nim--python--poetry--
- https://github.com/sstadick/nython#nython
If you want the compilation to be completely silent (you may miss important warnings and hints),you can add to the compile command--hints:off --verbosity:0
.
The compiler help is long. To make it more user friendly only the most frequent commands are shown with--help
. If you want to see the full help you can use--fullhelp
.
When your code is ready for production you should use a Release build,you can add to the compile command-d:release
or-d:danger
(dangerous!).
Feature | Debug Build | Release Build | Danger Build |
---|---|---|---|
Speed | Slow | Fast | Faster |
File Size | Big | Small | Smaller |
Optimized | ❎ | ✔️ | ✔️ |
Tracebacks | ✔️ | ❎ | ❎ |
Run-time checks | ✔️ | ✔️ | ❎ |
Compile-time checks | ✔️ | ✔️ | ✔️ |
assert | ✔️ | ✔️ | ❎ |
doAssert | ✔️ | ✔️ | ✔️ |
Nim compiles to C, so it can run on Arduino and similar hardware.
Has several memory management strategies to fit your needs, including full manual memory management.Nim binaries are small when built for Release and it can fit the hardware tiny storage.
- https://github.com/zevv/nim-arduino
- https://github.com/elcritch/nesper#example-code
- https://gitlab.com/endes123321/nimcdl/tree/master#nimcdl-nim-circuit-design-language
- https://github.com/cfvescovo/Arduino-Nim#arduino-nim
- https://gitlab.com/nimbed/nimbed#nimbed
- https://gitlab.com/endes123321/led-controller-frontend#led-controller-frontend
- https://gitlab.com/jalexander8717/msp430f5510-nim
- https://github.com/mwbrown/nim_stm32f3
- https://github.com/gokr/ardunimo
- https://gitlab.com/NetaLabTek/Arduimesp
- https://ftp.heanet.ie/mirrors/fosdem-video/2020/AW1.125/nimoneverything.webm
SuperCollider is C++ so it can be re-utilized using Nim.
Theoretically, Nim SuperCollider plugins should be just as fast as C code.Nim's metaprogramming allows us to build LiveCoding friendly DSLs.
Some projects for Nim LiveCoding:
- https://github.com/vitreo12/omni#omni
- https://github.com/capocasa/scnim#scnim---writing-supercollider-ugens-using-nim
Seethis
The key to understanding Nim is that Nim was designed to be as fast as C, but to be much safer. Many of the design decisions are based on making it harder to shoot yourself in the foot.In Python, there are no pointers (everything is treated as a reference).While Nim does give you pointers, Nim gives you other, safer tools for your everyday needs, while pointers are mostly reserved for interfacing with C and doing low-level system programming.
Contrarily to Python, most Nim code can be executed at compile time to perform meta-programming.You can do a lot of the DSLs possible with Python decorators/metaprogramming with Nim macros and pragmas.(And some stuff that you can't!). Of course, this requires some different patterns and more type safety.
Intro
Getting Started
- Install
- Docs
- Curated Packages
- Editor Support
- Unofficial FAQ
- Nim for C programmers
- Nim for Python programmers
- Nim for TypeScript programmers
- Nim for D programmers
- Nim for Java programmers
- Nim for Haskell programmers
Developing
- Build
- Contribute
- Creating a release
- Compiler module reference
- Consts defined by the compiler
- Debugging the compiler
- GitHub Actions/Travis CI/Circle CI/Appveyor
- GitLab CI setup
- Standard library and the JavaScript backend
Misc