├── .github └── workflows │ ├── ci.yml │ └── gh-pages.yml ├── .gitignore ├── README.md ├── book.toml ├── interop └── async │ ├── .gitignore │ ├── Makefile │ ├── README.md │ ├── asynclib.nim │ ├── main.c │ ├── main.go │ ├── main.rs │ └── nim.cfg ├── open-in.css └── src ├── 00_introduction.md ├── SUMMARY.md ├── errors.callbacks.md ├── errors.exceptions.md ├── errors.md ├── errors.result.md ├── errors.status.md ├── formatting.md ├── formatting.naming.md ├── formatting.style.md ├── interop.c.md ├── interop.cpp.md ├── interop.go.md ├── interop.md ├── interop.rust.md ├── language.binary.md ├── language.converters.md ├── language.finalizers.md ├── language.import.md ├── language.inline.md ├── language.integers.md ├── language.macros.md ├── language.md ├── language.memory.md ├── language.methods.md ├── language.objconstr.md ├── language.proc.md ├── language.proctypes.md ├── language.range.md ├── language.refobject.md ├── language.result.md ├── language.string.md ├── language.vardecl.md ├── language.varinit.md ├── libraries.hex.md ├── libraries.md ├── libraries.results.md ├── libraries.std.md ├── libraries.stew.md ├── libraries.wrappers.md ├── tooling.build.md ├── tooling.debugging.md ├── tooling.deps.md ├── tooling.editors.md ├── tooling.md ├── tooling.nim.md ├── tooling.profiling.md └── tooling.tricks.md /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | push: 4 | branches: 5 | - master 6 | pull_request: 7 | workflow_dispatch: 8 | 9 | concurrency: # Cancel stale PR builds (but not push builds) 10 | group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }} 11 | cancel-in-progress: true 12 | 13 | jobs: 14 | build: 15 | strategy: 16 | fail-fast: false 17 | matrix: 18 | target: 19 | - os: linux 20 | cpu: amd64 21 | rust: stable 22 | - os: macos 23 | cpu: amd64 24 | rust: stable 25 | - os: windows 26 | cpu: amd64 27 | rust: stable-gnu 28 | branch: [version-1-6, version-2-0, devel] 29 | include: 30 | - target: 31 | os: linux 32 | builder: ubuntu-latest 33 | shell: bash 34 | - target: 35 | os: macos 36 | builder: macos-latest 37 | shell: bash 38 | - target: 39 | os: windows 40 | builder: windows-latest 41 | shell: msys2 {0} 42 | 43 | defaults: 44 | run: 45 | shell: ${{ matrix.shell }} 46 | 47 | name: '${{ matrix.target.os }}-${{ matrix.target.cpu }} (Nim ${{ matrix.branch }})' 48 | runs-on: ${{ matrix.builder }} 49 | continue-on-error: ${{ matrix.branch == 'devel' }} 50 | steps: 51 | - uses: actions-rust-lang/setup-rust-toolchain@v1 52 | with: 53 | toolchain: '${{matrix.target.rust}}' 54 | - name: Checkout 55 | uses: actions/checkout@v4 56 | 57 | - name: Install build dependencies (Linux i386) 58 | if: runner.os == 'Linux' && matrix.target.cpu == 'i386' 59 | run: | 60 | sudo dpkg --add-architecture i386 61 | sudo apt-fast update -qq 62 | sudo DEBIAN_FRONTEND='noninteractive' apt-fast install \ 63 | --no-install-recommends -yq gcc-multilib g++-multilib \ 64 | libssl-dev:i386 65 | mkdir -p external/bin 66 | cat << EOF > external/bin/gcc 67 | #!/bin/bash 68 | exec $(which gcc) -m32 "\$@" 69 | EOF 70 | cat << EOF > external/bin/g++ 71 | #!/bin/bash 72 | exec $(which g++) -m32 "\$@" 73 | EOF 74 | chmod 755 external/bin/gcc external/bin/g++ 75 | echo '${{ github.workspace }}/external/bin' >> $GITHUB_PATH 76 | 77 | - name: MSYS2 (Windows amd64) 78 | if: runner.os == 'Windows' && matrix.target.cpu == 'amd64' 79 | uses: msys2/setup-msys2@v2 80 | with: 81 | path-type: inherit 82 | install: >- 83 | base-devel 84 | git 85 | mingw-w64-x86_64-toolchain 86 | 87 | - name: Restore Nim DLLs dependencies (Windows) from cache 88 | if: runner.os == 'Windows' 89 | id: windows-dlls-cache 90 | uses: actions/cache@v3 91 | with: 92 | path: external/dlls-${{ matrix.target.cpu }} 93 | key: 'dlls-${{ matrix.target.cpu }}' 94 | 95 | - name: Install DLLs dependencies (Windows) 96 | if: > 97 | steps.windows-dlls-cache.outputs.cache-hit != 'true' && 98 | runner.os == 'Windows' 99 | run: | 100 | mkdir -p external 101 | curl -L "https://nim-lang.org/download/windeps.zip" -o external/windeps.zip 102 | 7z x -y external/windeps.zip -oexternal/dlls-${{ matrix.target.cpu }} 103 | 104 | - name: Path to cached dependencies (Windows) 105 | if: > 106 | runner.os == 'Windows' 107 | run: | 108 | echo "${{ github.workspace }}/external/dlls-${{ matrix.target.cpu }}" >> $GITHUB_PATH 109 | 110 | - name: Derive environment variables 111 | run: | 112 | if [[ '${{ matrix.target.cpu }}' == 'amd64' ]]; then 113 | PLATFORM=x64 114 | else 115 | PLATFORM=x86 116 | fi 117 | echo "PLATFORM=$PLATFORM" >> $GITHUB_ENV 118 | 119 | ncpu= 120 | MAKE_CMD="make" 121 | case '${{ runner.os }}' in 122 | 'Linux') 123 | ncpu=$(nproc) 124 | ;; 125 | 'macOS') 126 | ncpu=$(sysctl -n hw.ncpu) 127 | ;; 128 | 'Windows') 129 | ncpu=$NUMBER_OF_PROCESSORS 130 | MAKE_CMD="mingw32-make" 131 | ;; 132 | esac 133 | [[ -z "$ncpu" || $ncpu -le 0 ]] && ncpu=1 134 | echo "ncpu=$ncpu" >> $GITHUB_ENV 135 | echo "MAKE_CMD=${MAKE_CMD}" >> $GITHUB_ENV 136 | 137 | - name: Build Nim and Nimble 138 | run: | 139 | curl -O -L -s -S https://raw.githubusercontent.com/status-im/nimbus-build-system/master/scripts/build_nim.sh 140 | env MAKE="${MAKE_CMD} -j${ncpu}" ARCH_OVERRIDE=${PLATFORM} NIM_COMMIT=${{ matrix.branch }} \ 141 | QUICK_AND_DIRTY_COMPILER=1 QUICK_AND_DIRTY_NIMBLE=1 CC=gcc \ 142 | bash build_nim.sh nim csources dist/nimble NimBinaries 143 | echo '${{ github.workspace }}/nim/bin' >> $GITHUB_PATH 144 | 145 | - name: Compile examples 146 | run: | 147 | cd interop/async 148 | nim --version 149 | nimble --version 150 | make prepare 151 | make main-c main-rs 152 | -------------------------------------------------------------------------------- /.github/workflows/gh-pages.yml: -------------------------------------------------------------------------------- 1 | name: github pages 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | deploy: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v3 13 | - uses: actions-rs/install@v0.1 14 | with: 15 | crate: mdbook 16 | use-tool-cache: true 17 | version: "0.4.36" 18 | - uses: actions-rs/install@v0.1 19 | with: 20 | crate: mdbook-toc 21 | use-tool-cache: true 22 | version: "0.14.1" 23 | - uses: actions-rs/install@v0.1 24 | with: 25 | crate: mdbook-open-on-gh 26 | use-tool-cache: true 27 | version: "2.4.1" 28 | - uses: actions-rs/install@v0.1 29 | with: 30 | crate: mdbook-admonish 31 | use-tool-cache: true 32 | version: "1.14.0" 33 | - name: Build book 34 | run: | 35 | PATH=.cargo/bin:$PATH mdbook build 36 | 37 | - name: Deploy 38 | uses: peaceiris/actions-gh-pages@v3 39 | with: 40 | github_token: ${{ secrets.GITHUB_TOKEN }} 41 | publish_dir: ./book 42 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | book 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | [Online version](https://status-im.github.io/nim-style-guide/) 4 | 5 | An ever evolving collection of conventions, idioms and tricks that reflects the experience of developing a production-grade application in [Nim](https://nim-lang.org) with a small team of developers. 6 | 7 | ## Build and publish 8 | 9 | The style guide is built using [mdBook](https://github.com/rust-lang/mdBook), and published to gh-pages using a github action. 10 | 11 | ```bash 12 | # Install or update tooling (make sure you add "~/.cargo/bin" to PATH): 13 | cargo install mdbook --version 0.4.36 14 | cargo install mdbook-toc --version 0.14.1 15 | cargo install mdbook-open-on-gh --version 2.4.1 16 | cargo install mdbook-admonish --version 1.14.0 17 | 18 | # Edit book and view through local browser 19 | mdbook serve 20 | ``` 21 | 22 | ## Contributing 23 | 24 | We welcome contributions to the style guide as long as they match the strict security requirements Status places on Nim code. As with any style guide, some of it comes down to taste and we might reject them based on consistency or whim. 25 | -------------------------------------------------------------------------------- /book.toml: -------------------------------------------------------------------------------- 1 | [book] 2 | authors = ["Jacek Sieka"] 3 | language = "en" 4 | multilingual = false 5 | src = "src" 6 | title = "The Status Nim style guide" 7 | 8 | [preprocessor.toc] 9 | command = "mdbook-toc" 10 | renderer = ["html"] 11 | max-level = 2 12 | 13 | [preprocessor.open-on-gh] 14 | command = "mdbook-open-on-gh" 15 | renderer = ["html"] 16 | 17 | [output.html] 18 | git-repository-url = "https://github.com/status-im/nim-style-guide/" 19 | git-branch = "main" 20 | additional-css = ["open-in.css"] 21 | 22 | -------------------------------------------------------------------------------- /interop/async/.gitignore: -------------------------------------------------------------------------------- 1 | main-c 2 | main-rs 3 | main-go 4 | libasynclib.a 5 | -------------------------------------------------------------------------------- /interop/async/Makefile: -------------------------------------------------------------------------------- 1 | main-c: asynclib.nim main.c 2 | nim c --debuginfo --app:staticlib --noMain asynclib 3 | gcc -pthread -g main.c -L. -lasynclib -o main-c 4 | 5 | main-rs: asynclib.nim main.rs 6 | nim c --debuginfo --app:staticlib --noMain asynclib 7 | rustc main.rs -L. -lasynclib -o main-rs 8 | 9 | main-go: asynclib.nim main.go 10 | nim c --debuginfo --app:staticlib --noMain asynclib 11 | go build -o main-go main.go 12 | 13 | prepare: 14 | nimble install -y chronos 15 | -------------------------------------------------------------------------------- /interop/async/README.md: -------------------------------------------------------------------------------- 1 | # Chronos HTTP server FFI 2 | 3 | This folder contains a simple `chronos`-based http server serving requests on a given port. 4 | 5 | The server runs on a separate thread and makes a callback into the host language for every request it responds to. 6 | 7 | Notable features: 8 | 9 | * The server runs in a separate thread created by Nim as part of the `startNode` function 10 | * A `Context` object is passed to the host language and used in interactions with the node 11 | * Callbacks are executed from the Nim thread into the host language 12 | * The library initializes itself the first time a node is started 13 | * For cross-thread communication, manual memory management is used 14 | -------------------------------------------------------------------------------- /interop/async/asynclib.nim: -------------------------------------------------------------------------------- 1 | {.push raises: [].} 2 | 3 | {.pragma: exported, exportc, cdecl, raises: [].} 4 | {.pragma: callback, cdecl, raises: [], gcsafe.} 5 | {.passc: "-fPIC".} 6 | 7 | import std/atomics 8 | import chronos, chronos/apps/http/httpserver 9 | 10 | # Every Nim library must have this function called - the name is derived from 11 | # the `--nimMainPrefix` command line option 12 | proc asynclibNimMain() {.importc.} 13 | 14 | var initialized: Atomic[bool] 15 | 16 | proc initLib() {.gcsafe.} = 17 | if not initialized.exchange(true): 18 | asynclibNimMain() # Every Nim library needs to call `NimMain` once exactly 19 | when declared(setupForeignThreadGc): setupForeignThreadGc() 20 | when declared(nimGC_setStackBottom): 21 | var locals {.volatile, noinit.}: pointer 22 | locals = addr(locals) 23 | nimGC_setStackBottom(locals) 24 | 25 | type 26 | OnHeaders = proc(user: pointer, data: pointer, len: csize_t) {.callback.} 27 | 28 | Context = object 29 | thread: Thread[(ptr Context, cstring)] 30 | onHeaders: OnHeaders 31 | user: pointer 32 | stop: Atomic[bool] 33 | 34 | Node = object 35 | server: HttpServerRef 36 | 37 | proc runContext(args: tuple[ctx: ptr Context, address: cstring]) {.thread.} = 38 | let 39 | node = (ref Node)() 40 | ctx = args.ctx 41 | address = $args.address 42 | 43 | deallocShared(args.address) # Don't forget to release memory manually! 44 | 45 | proc process(r: RequestFence): Future[HttpResponseRef] {.async.} = return 46 | if r.isOk(): 47 | let 48 | req = r.get() 49 | await req.consumeBody() 50 | let headers = $req.headers 51 | if headers.len > 0: 52 | ctx[].onHeaders(ctx[].user, unsafeAddr headers[0], csize_t headers.len) 53 | await req.respond(Http200, "Hello from Nim") 54 | else: 55 | dumbResponse() 56 | 57 | try: 58 | let 59 | socketFlags = {ServerFlags.TcpNoDelay, ServerFlags.ReuseAddr} 60 | node.server = HttpServerRef.new( 61 | initTAddress(address), process, socketFlags = socketFlags).expect("working server") 62 | 63 | node.server.start() 64 | defer: 65 | waitFor node.server.closeWait() 66 | 67 | while not args.ctx[].stop.load(): 68 | # Keep running until we're asked not to, by polling `stop` 69 | # TODO A replacement for the polling mechanism is being developed here: 70 | # https://github.com/status-im/nim-chronos/pull/406 71 | # Once it has been completed, it should be used instead. 72 | waitFor sleepAsync(100.millis) 73 | 74 | except CatchableError as exc: 75 | echo "Shutting down because of error", exc.msg 76 | 77 | proc startNode*( 78 | address: cstring, onHeaders: OnHeaders, user: pointer): ptr Context {.exported.} = 79 | initLib() 80 | 81 | let 82 | # createShared for allocating plain Nim types 83 | ctx = createShared(Context, 1) 84 | # allocShared0 for allocating zeroed bytes - note +1 for cstring NULL terminator! 85 | addressCopy = cast[cstring](allocShared(len(address) + 1)) 86 | 87 | copyMem(addressCopy, address, len(address) + 1) 88 | 89 | # We can pass simple data to the thread using the context 90 | ctx.onHeaders = onHeaders 91 | ctx.user = user 92 | 93 | try: 94 | createThread(ctx.thread, runContext, (ctx, addressCopy)) 95 | ctx 96 | except ResourceExhaustedError: 97 | # deallocShared for byte allocations 98 | deallocShared(addressCopy) 99 | # and freeShared for typed allocations! 100 | freeShared(ctx) 101 | nil 102 | 103 | proc stopNode*(ctx: ptr ptr Context) {.exported.} = 104 | if ctx == nil or ctx[] == nil: return 105 | 106 | ctx[][].stop.store(true) 107 | ctx[][].thread.joinThread() 108 | freeShared(ctx[]) 109 | ctx[] = nil 110 | -------------------------------------------------------------------------------- /interop/async/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | /* Import functions from Nim */ 5 | void* startNode(const char* url, void (*onHeader)(void*, const char*, size_t), void* user); 6 | void stopNode(void** ctx); 7 | 8 | void onHeader(void* user, const char* headers, size_t len) { 9 | printf("Received headers! %lu\n", len); 10 | printf("%.*s\n\n", (int)len, headers); 11 | } 12 | 13 | int main(int argc, char** argv) { 14 | printf("Starting node\n"); 15 | void* ctx = startNode("127.0.0.1:60000", onHeader, 0); 16 | printf("Node is listening on http://127.0.0.1:60000\nType `q` and press enter to stop\n"); 17 | 18 | int stop = 0; 19 | while (getchar() != 'q'); 20 | 21 | printf("Stopping node\n"); 22 | 23 | stopNode(&ctx); 24 | } 25 | -------------------------------------------------------------------------------- /interop/async/main.go: -------------------------------------------------------------------------------- 1 | 2 | package main 3 | 4 | /* 5 | #cgo LDFLAGS: -L./ -lasynclib 6 | 7 | #include 8 | 9 | // Import functions from Nim 10 | void* startNode(const char* url, void* onHeader, void* user); 11 | void stopNode(void** ctx); 12 | 13 | typedef const char cchar_t; 14 | extern void callback(void* user, cchar_t* headers, size_t len); 15 | */ 16 | import "C" 17 | 18 | import ( 19 | "fmt" 20 | "runtime" 21 | "unsafe" 22 | ) 23 | 24 | //export callback 25 | func callback(user *C.void, headers *C.cchar_t, len C.size_t) { 26 | fmt.Println("Callback! ", uint64(len)) 27 | fmt.Println(C.GoStringN(headers, C.int(len))) 28 | } 29 | 30 | func main() { 31 | runtime.LockOSThread() 32 | 33 | fmt.Println("Starting node") 34 | 35 | user := 23 36 | 37 | ctx := C.startNode(C.CString("127.0.0.1:60000"), 38 | unsafe.Pointer(C.callback), 39 | unsafe.Pointer(&user)) 40 | fmt.Println(` 41 | Node is listening on http://127.0.0.1:60000 42 | Type 'q' and press enter to stop 43 | `) 44 | 45 | for { 46 | if C.getchar() == 'q' { 47 | break 48 | } 49 | } 50 | 51 | fmt.Println("Stopping node") 52 | 53 | C.stopNode(&ctx) 54 | } 55 | -------------------------------------------------------------------------------- /interop/async/main.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::{c_char, c_void, CString}; 2 | use std::{io, ptr, slice}; 3 | 4 | extern "C" { 5 | // Functions exported by `asynclib` 6 | pub fn startNode( 7 | address: *const c_char, 8 | on_headers: extern "C" fn(*mut c_void, *const c_char, usize), 9 | user: *mut c_void, 10 | ) -> *mut c_void; 11 | pub fn stopNode(ctx: *mut *mut c_void); 12 | } 13 | 14 | extern "C" fn on_headers(_user: *mut c_void, data: *const c_char, len: usize) { 15 | println!("Received headers! {len}"); 16 | let data = String::from_utf8(unsafe { slice::from_raw_parts(data as *const u8, len) }.to_vec()) 17 | .expect("valid utf8"); 18 | println!("{data}\n\n"); 19 | } 20 | 21 | fn main() { 22 | print!("Starting node\n"); 23 | 24 | let address = CString::new("127.0.0.1:60000").expect("CString::new failed"); 25 | 26 | let mut ctx = unsafe { startNode(address.into_raw(), on_headers, ptr::null_mut()) }; 27 | print!("Node is listening on http://127.0.0.1:60000\nType `q` and press enter to stop\n"); 28 | 29 | let mut input = String::new(); 30 | loop { 31 | match io::stdin().read_line(&mut input) { 32 | Ok(n) if n > 0 => { 33 | break; 34 | } 35 | _ => {} 36 | } 37 | } 38 | 39 | print!("Stopping node\n"); 40 | 41 | unsafe { stopNode(&mut ctx) }; 42 | } 43 | -------------------------------------------------------------------------------- /interop/async/nim.cfg: -------------------------------------------------------------------------------- 1 | --nimMainPrefix:asynclib 2 | --threads:on 3 | --tlsEmulation:off -------------------------------------------------------------------------------- /open-in.css: -------------------------------------------------------------------------------- 1 | footer { 2 | font-size: 0.8em; 3 | text-align: center; 4 | border-top: 1px solid black; 5 | padding: 5px 0; 6 | } 7 | 8 | -------------------------------------------------------------------------------- /src/00_introduction.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | > "With great power comes great responsibility" - Spiderman (or Voltaire, for the so culturally inclined) 4 | 5 | This text is an ever evolving collection of conventions, idioms and tricks that reflects the experience of developing a production-grade application in Nim with a small team of developers. 6 | 7 | The guide is a living document to help manage the complexities of using an off-the-beaten-track language and environment to produce a stable product ready for an adverserial internet. 8 | 9 | Each guideline starts with a general recommendation to use or not use a particular feature, this recommendation represents a safe "default" choice. It is followed by a rationale to help you decide when and how to apply the guideline with nuance - it will not be right for every situation out there but all other things being equal, following the guideline will make life easier for others, your future self included. 10 | 11 | Following the principles and defaults set out here helps newcomers to familiarise themselves with the codebase more quickly, while experienced developers will appreciate the consistency when deciphering the intent behind a specific passage of code -- above all when trying to debug production issues under pressure. 12 | 13 | The `pros` and `cons` sections are based on bugs, confusions and security issues that have been found in real-life code and that could easily have been avoided with.. a bit of style. The objective of this section is to pass the experience on to you, dear reader! 14 | 15 | In particular when coming from a different language, experience with features like exception handling, generics and compile-time guarantees may not carry over due to subtle, and sometimes surprising, differences in semantics. 16 | 17 | Much Nim code "out there" hails from past times when certain language features were not yet developed and best practices not yet established - this also applies to this guide, which will change over time as the practice and language evolves. 18 | 19 | When in doubt: 20 | 21 | * Read your code 22 | * Deal with errors 23 | * Favour simplicity 24 | * Default to safety 25 | * Consider the adversary 26 | * Pay back your debt regularly 27 | * Correct, readable, elegant, efficient, in that order 28 | 29 | The latest version of this book can be found [online](https://status-im.github.io/nim-style-guide/) or on [GitHub](https://github.com/status-im/nim-style-guide/). 30 | 31 | This guide currently targets Nim v2.0. 32 | 33 | At the time of writing, v2.0 has been released but its new garbage collector is not yet stable enough for production use. It is advisable to test new code with both `--mm:refc` and `--mm:orc` (the default) in the transition period. 34 | 35 | 36 | 37 | ## Practical notes 38 | 39 | * When deviating from the guide, document the rationale in the module, allowing the next developer to understand the motivation behind the deviation 40 | * When encountering code that does not follow this guide, follow its local conventions or refactor it 41 | * When refactoring code, ensure good test coverage first to avoid regressions 42 | * Strive towards the guidelines where practical 43 | * Consider backwards compatibility when changing code 44 | * Good code usually happens after several rewrites: on the first pass, the focus is on the problem, not the code - when the problem is well understood, the code can be rewritten 45 | 46 | ## Updates to this guide 47 | 48 | Updates to this guide go through review as usual for code - ultimately, some choices in style guides come down to personal preference and contributions of that nature may end up being rejected. 49 | 50 | In general, the guide will aim to prioritise: 51 | 52 | * safe defaults - avoid footguns and code that is easily abused 53 | * secure practices - assume code is run in an untrusted environment 54 | * compile-time strictness - get the most out of the compiler and language before it hits the user 55 | * readers over writers - only others can judge the quality of your code 56 | 57 | ## Useful resources 58 | 59 | While this book covers Nim at Status in general, there are other resources available that partially may overlap with this guide: 60 | 61 | * [Nim language manual](https://nim-lang.org/docs/manual.html) - the authorative source for understanding the features of the language 62 | * [Nim documentation](https://nim-lang.org/documentation.html) - other official Nim documentation, including its standard library and toolchain 63 | * [Nim by Example](https://nim-by-example.github.io/getting_started/) - Nim tutorials to start with 64 | * [Chronos guides](https://github.com/status-im/nim-chronos/blob/master/docs/src/SUMMARY.md) 65 | * [nim-libp2p docs](https://vacp2p.github.io/nim-libp2p/docs/) 66 | 67 | ## Workflow 68 | 69 | ### Pull requests 70 | 71 | * One PR, one feature or fix 72 | * Avoid mixing refactoring with features and bugfixes 73 | * Post refactoring PR:s early, while working on feature that benefits from them 74 | * Rebase on top of target branch 75 | * Squash-merge the PR branch for easy rollback 76 | * Since branches contain only _one_ logical change, there's usually no need for more than one target branch commit 77 | * Revert work that causes breakage and investigate in new PR 78 | 79 | ### Contributing 80 | 81 | We welcome code contributions and welcome our code being used in other projects. 82 | 83 | Generally, all significant code changes are reviewed by at least one team member and must pass CI. 84 | 85 | * For style and other trivial fixes, no review is needed (passing CI is sufficent) 86 | * For small ideas, use a PR 87 | * For big ideas, use an RFC issue 88 | -------------------------------------------------------------------------------- /src/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | [Introduction](00_introduction.md) 4 | 5 | - [Formatting](formatting.md) 6 | - [Style](formatting.style.md) 7 | - [Naming](formatting.naming.md) 8 | - [Language](language.md) 9 | - [Import/Export](language.import.md) 10 | - [Macros](language.macros.md) 11 | - [Object construction](language.objconstr.md) 12 | - [`ref object`](language.refobject.md) 13 | - [Memory](language.memory.md) 14 | - [Variable declaration](language.vardecl.md) 15 | - [Variable initialization](language.varinit.md) 16 | - [Procedures](language.proc.md) 17 | - [Methods](language.methods.md) 18 | - [`proc` types](language.proctypes.md) 19 | - [`result` return](language.result.md) 20 | - [`inline`](language.inline.md) 21 | - [Converters](language.converters.md) 22 | - [Finalizers](language.finalizers.md) 23 | - [Binary data](language.binary.md) 24 | - [Integers](language.integers.md) 25 | - [`range` types](language.range.md) 26 | - [`string`](language.string.md) 27 | - [Error handling](errors.md) 28 | - [`Result`](errors.result.md) 29 | - [Exceptions](errors.exceptions.md) 30 | - [Status codes](errors.status.md) 31 | - [Callbacks](errors.callbacks.md) 32 | - [Libraries](libraries.md) 33 | - [`std` usage](libraries.std.md) 34 | - [Results](libraries.results.md) 35 | - [Hex output](libraries.hex.md) 36 | - [Wrappers](libraries.wrappers.md) 37 | - [`stew`](libraries.stew.md) 38 | - [Tooling](tooling.md) 39 | - [Nim version](tooling.nim.md) 40 | - [Build](tooling.build.md) 41 | - [Dependencies](tooling.deps.md) 42 | - [Editors](tooling.editors.md) 43 | - [Debugging](tooling.debugging.md) 44 | - [Profiling](tooling.profiling.md) 45 | - [Tricks](tooling.tricks.md) 46 | - [Interop with other languages](interop.md) 47 | - [C / general wrapping](interop.c.md) 48 | - [go](interop.go.md) 49 | - [rust](interop.rust.md) 50 | -------------------------------------------------------------------------------- /src/errors.callbacks.md: -------------------------------------------------------------------------------- 1 | ## Callbacks 2 | 3 | See [language section on callbacks](language.proctypes.md). 4 | -------------------------------------------------------------------------------- /src/errors.exceptions.md: -------------------------------------------------------------------------------- 1 | ## Exceptions `[errors.exceptions]` 2 | 3 | In general, prefer [explicit error handling mechanisms](errors.result.md). 4 | 5 | Annotate each module at top-level (before imports): 6 | 7 | ```nim 8 | {.push raises: [].} 9 | ``` 10 | 11 | Use explicit `{.raises.}` annotation for each public (`*`) function. 12 | 13 | Raise `Defect` to signal panics and undefined behavior that the code is not prepared to handle. 14 | 15 | ```nim 16 | # Enable exception tracking for all functions in this module 17 | `{.push raises: [].}` # Always at start of module 18 | 19 | # Inherit from CatchableError and name XxxError 20 | type MyLibraryError = object of CatchableError 21 | 22 | # Raise Defect when panicking - this crashes the application (in different ways 23 | # depending on Nim version and compiler flags) - name `XxxDefect` 24 | type SomeDefect = object of Defect 25 | 26 | # Use hierarchy for more specific errors 27 | type MySpecificError = object of MyLibraryError 28 | 29 | # Explicitly annotate functions with raises - this replaces the more strict 30 | # module-level push declaration on top 31 | func f() {.raises: [MySpecificError]} = discard 32 | 33 | # Isolate code that may generate exceptions using expression-based try: 34 | let x = 35 | try: ... 36 | except MyError as exc: ... # use the most specific error kind possible 37 | 38 | # Be careful to catch excpetions inside loops, to avoid partial loop evaluations: 39 | for x in y: 40 | try: .. 41 | except MyError: .. 42 | 43 | # Provide contextual data when raising specific errors 44 | raise (ref MyError)(msg: "description", data: value) 45 | ``` 46 | 47 | ### Pros 48 | 49 | * Used by `Nim` standard library 50 | * Good for quick prototyping without error handling 51 | * Good performance on happy path without `try` 52 | * Compatible with RVO 53 | 54 | ### Cons 55 | 56 | * Poor readability - exceptions not part of API / signatures by default 57 | * Have to assume every line may fail 58 | * Poor maintenance / refactoring support - compiler can't help detect affected code because they're not part of API 59 | * Nim exception hierarchy unclear and changes between versions 60 | * The distinction between `Exception`, `CatchableError` and `Defect` is inconsistently implemented 61 | * [Exception hierarchy RFC not being implemented](https://github.com/nim-lang/Nim/issues/11776) 62 | * `Defect` is [not tracked](https://github.com/nim-lang/Nim/pull/13626) 63 | * Without translation, exceptions leak information between abstraction layers 64 | * Writing exception-safe code in Nim impractical due to missing critical features present in C++ 65 | * No RAII - resources often leak in the presence of exceptions 66 | * Destructors incomplete / unstable and thus not usable for safe EH 67 | * No constructors, thus no way to force particular object states at construction 68 | * `ref` types incompatible with destructors, even if they worked 69 | * Poor performance of error path 70 | * Several heap allocations for each `Exception`` (exception, stack trace, message) 71 | * Expensive stack trace 72 | * Poor performance on happy path 73 | * Every `try` and `defer` has significant performance overhead due to `setjmp` exception handling implementation 74 | 75 | ### Practical notes 76 | 77 | The use of exceptions in Nim has significantly contributed to resource leaks, deadlocks and other difficult bugs. The various exception handling proposals aim to alleviate some of the issues but have not found sufficient grounding in the Nim community to warrant the language changes necessary to proceed. 78 | 79 | ### `Defect` 80 | 81 | `Defect` does [not cause](https://github.com/nim-lang/Nim/issues/12862) a `raises` effect - code must be manually verified - common sources of `Defect` include: 82 | 83 | * Over/underflows in signed arithmetic 84 | * `[]` operator for indexing arrays/seqs/etc (but not tables!) 85 | * accidental/implicit conversions to `range` types 86 | 87 | ### `CatchableError` 88 | 89 | Catching `CatchableError` implies that all errors are funnelled through the same exception handler. When called code starts raising new exceptions, it becomes difficult to find affected code - catching more specific errors avoids this maintenance problem. 90 | 91 | Frameworks may catch `CatchableError` to forward exceptions through layers. Doing so leads to type erasure of the actual raised exception type in `raises` tracking. 92 | 93 | ### Open questions 94 | 95 | * Should a hierarchy be used? 96 | * Why? It's rare that calling code differentiates between errors 97 | * What to start the hierarchy with? Unclear whether it should be a global type (like `CatchableError` or `ValueError`, or a module-local type 98 | * Should exceptions be translated? 99 | * Leaking exception types between layers means no isolation, joining all modules in one big spaghetti bowl 100 | * Translating exceptions has high visual overhead, specially when hierachy is used - not practical, all advantages lost 101 | * Should `raises` be used? 102 | * Equivalent to `Result[T, SomeError]` but lacks generics 103 | * Additive - asymptotically tends towards `raises: [CatchableError]`, losing value unless exceptions are translated locally 104 | * No way to transport accurate raises type information across Future/async/generic code boundaries - no `raisesof` equivalent of `typeof` 105 | 106 | ### Background 107 | 108 | * [Stew EH helpers](https://github.com/status-im/nim-stew/pull/26) - Helpers that make working with checked exceptions easier 109 | * [Nim Exception RFC](https://github.com/nim-lang/Nim/issues/8363) - seeks to differentiate between recoverable and unrecoverable errors 110 | * [Zahary's handling proposal](https://gist.github.com/zah/d2d729b39d95a1dfedf8183ca35043b3) - seeks to handle any kind of error-generating API 111 | * [C++ proposal](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0709r0.pdf) - After 25 years of encouragement, half the polled C++ developers continue avoiding exceptions and Herb Sutter argues about the consequences of doing so 112 | * [Google](https://google.github.io/styleguide/cppguide.html#Exceptions) and [llvm](https://llvm.org/docs/CodingStandards.html#id22) style guides on exceptions 113 | -------------------------------------------------------------------------------- /src/errors.md: -------------------------------------------------------------------------------- 1 | # Error handling 2 | 3 | Error handling in Nim is a subject under constant re-evaluation - similar to C++, several paradigms are supported leading to confusion as to which one to choose. 4 | 5 | In part, the confusion stems from the various contexts in which Nim can be used: when executed as small, one-off scripts that can easily be restarted, exceptions allow low visual overhead and ease of use. 6 | 7 | When faced with more complex and long-running programs where errors must be dealt with as part of control flow, the use of exceptions can directly be linked to issues like resource leaks, security bugs and crashes. 8 | 9 | Likewise, when preparing code for refactoring, the compiler offers little help in exception-based code: although raising a new exception breaks ABI, there is no corresponding change in the API: this means that changes deep inside dependencies silently break dependent code until the issue becomes apparent at runtime (often under exceptional circumstances). 10 | 11 | A final note is that although exceptions may have been used successfully in some languages, these languages typically offer complementary features that help manage the complexities introduced by exceptions - RAII, mandatory checking of exceptions, static analysis etc - these have yet to be developed for Nim. 12 | 13 | Because of the controversies and changing landscape, the preference for Status projects is to avoid the use of exceptions unless specially motivated, if only to maintain consistency and simplicity. 14 | 15 | ## Porting legacy code 16 | 17 | When dealing with legacy code, there are several common issues, most often linked to abstraction and effect leaks. In Nim, exception effects are part of the function signature but deduced based on code. Sometimes the deduction must make a conservative estimate, and these estimates infect the entire call tree until neutralised with a `try/except`. 18 | 19 | When porting code, there are two approaches: 20 | 21 | * Bottom up - fix the underlying library / code 22 | * Top down - isolate the legacy code with `try/except` 23 | * In this case, we note where the `Exception` effect is coming from, should it be fixed in the future 24 | -------------------------------------------------------------------------------- /src/errors.result.md: -------------------------------------------------------------------------------- 1 | ## Result `[errors.result]` 2 | 3 | Prefer `bool`, `Opt` or `Result` to signal failure outcomes explicitly. Avoid using the [`result` identifier](language.result.md). 4 | 5 | Prefer the use of `Result` when multiple failure paths exist and the calling code might need to differentiate between them. 6 | 7 | Raise `Defect` to signal panics such as logic errors or preconditions being violated. 8 | 9 | Make error handling explicit and visible at call site using explicit control flow (`if`, `try`, `results.?`). 10 | 11 | Handle errors locally at each abstraction level, avoiding spurious abstraction leakage. 12 | 13 | Isolate legacy code with explicit exception handling, converting the errors to `Result` or handling them locally, as appropriate. 14 | 15 | ```nim 16 | # Enable exception tracking for all functions in this module 17 | {.push raises: [].} # Always at start of module 18 | 19 | import results 20 | export results # Re-export modules used in public symbols 21 | 22 | # Use `Result` to propagate additional information expected errors 23 | # See `Result` section for specific guidlines for errror type 24 | func f*(): Result[void, cstring] 25 | 26 | # In special cases that warrant the use of exceptions, list these explicitly using the `raises` pragma. 27 | func parse(): Type {.raises: [ParseError]} 28 | ``` 29 | 30 | See also [Result](libraries.results.md) for more recommendations about `Result`. 31 | 32 | See also [Error handling helpers](https://github.com/status-im/nim-stew/pull/26) in stew that may change some of these guidelines. 33 | -------------------------------------------------------------------------------- /src/errors.status.md: -------------------------------------------------------------------------------- 1 | ## Status codes `[errors.status]` 2 | 3 | Avoid status codes. 4 | 5 | ```nim 6 | 7 | type StatusCode = enum 8 | Success 9 | Error1 10 | ... 11 | 12 | func f(output: var Type): StatusCode 13 | ``` 14 | 15 | ### Pros 16 | 17 | * Interop with `C` 18 | 19 | ### Cons 20 | 21 | * `output` undefined in case of error 22 | * Verbose to use, must first declare mutable variable then call function and check result - mutable variable remains in scope even in "error" branch leading to bugs 23 | 24 | ### Practical notes 25 | 26 | Unlike "Error Enums" used with `Result`, status codes mix "success" and "error" returns in a single enum, making it hard to detect "successful" completion of a function in a generic way. 27 | -------------------------------------------------------------------------------- /src/formatting.md: -------------------------------------------------------------------------------- 1 | # Formatting 2 | -------------------------------------------------------------------------------- /src/formatting.naming.md: -------------------------------------------------------------------------------- 1 | ## Naming conventions `[formatting.naming]` 2 | 3 | Always use the same identifier style (case, underscores) as the declaration. 4 | 5 | Enable `--styleCheck:usages`, and, where feasible, `--styleCheck:error`. 6 | 7 | * `Ref` for `ref object` types, which have surprising semantics 8 | * `type XxxRef = ref Xxx` 9 | * `type XxxRef = ref object ...` 10 | * `func init(T: type Xxx, params...): T` for "constructors" 11 | * `func init(T: type ref Xxx, params...): T` when `T` is a `ref` 12 | * `func new(T: type Xxx, params...): ref T` for "constructors" that return a `ref T` 13 | * `new` introduces `ref` to a non-`ref` type 14 | * `XxxError` for exceptions inheriting from `CatchableError` 15 | * `XxxDefect` for exceptions inheriting from `Defect` 16 | -------------------------------------------------------------------------------- /src/formatting.style.md: -------------------------------------------------------------------------------- 1 | ## Style `[formatting.style]` 2 | 3 | We strive to follow [NEP-1](https://nim-lang.org/docs/nep1.html) for style matters - naming, capitalization, 80-character limit etc. Common places where deviations happen include: 4 | 5 | * Code based on external projects 6 | * Wrappers / FFI 7 | * Implementations of specs that have their own naming convention 8 | * Ports from other languages 9 | * Small differences due to manual formatting 10 | * Aligned indents - we prefer python-style hanging indent for in multiline code 11 | * This is to avoid realignments when changes occur on the first line. The extra level of indentation is there to clearly distinguish itself as a continuation line. 12 | 13 | ``` 14 | func someLongFunctinName( 15 | alsoLongVariableName: int) = # Double-indent 16 | discard # back to normal indent 17 | 18 | if someLongCondition and 19 | moreLongConditions: # Double-indent 20 | discard # back to normal indent 21 | ``` 22 | 23 | ### Practical notes 24 | 25 | * We do not use `nimpretty` - as of writing (Nim 2.0), it is not stable enough for daily use: 26 | * Can break working code 27 | * Naive formatting algorithm 28 | * We do not make use of Nim's "flexible" identifier names - all uses of an identifier should match the declaration in capitalization and underscores 29 | * Enable `--styleCheck:usages` and, where feasible, `--styleCheck:error` 30 | -------------------------------------------------------------------------------- /src/interop.c.md: -------------------------------------------------------------------------------- 1 | # C / General wrapping 2 | 3 | ABI wrapping is the process describing the low-level interface of a library in an interop-friendly way using the lowest common denominator between the languages. For interop, we typically separate the "raw" ABI wrapper from higher-level code that adds native-language conveniece. 4 | 5 | When importing foreign libraries in Nim, the ABI wrapper can be thought of as a C "header" file: it describes to the compiler what code and data types are available in the library and how to encode them. 6 | 7 | Conversely, exporting Nim code typically consists of creating special functions in Nim using the C-compatible subset of the langauge then creating a corrsponding ABI description in the target language. 8 | 9 | Typical of the ABI wrapper is the use of the [FFI](https://nim-lang.org/docs/manual.html#foreign-function-interface) pragmas (`importc`, `exportc` etc) and, depending on the library, C types such as `cint`, `csize_t` as well as manual memory management directives such as `pointer`, `ptr`. 10 | 11 | In some cases, it may be necessary to write an "export wrapper" in C - this happens in particular when the library was not written with ineroperability in mind, for example when there is heavy C pre-processor use or function implementations are defined in the C header file. 12 | 13 | ## Exporting 14 | 15 | Exporting Nim code is done by creating an export module that presents the Nim code as a simplified C interface: 16 | 17 | ```nim 18 | import mylibrary 19 | 20 | # either `c`-prefixed types (`cint` etc) or explicitly sized types (int64 etc) work well 21 | proc function(arg: int64): cint {.exportc: "function", cdecl, raises: [].} = 22 | # Validate incoming arguments before converting them to Nim equivalents 23 | if arg >= int64(int.high) or arg <= int64(int.low): 24 | return 0 # Expose error handling 25 | mylibrary.function(int(arg)) 26 | ``` 27 | 28 | ## Importing 29 | 30 | ### Build process 31 | 32 | To import a library into Nim, it must first be built by its native compiler - depending on the complexity of the library, this can be done in several ways. 33 | 34 | The preferred way of compiling a native library is it include it in the Nim build process via `{.compile.}` directives: 35 | 36 | ```nim 37 | {.compile: "somesource.c".} 38 | ``` 39 | 40 | This ensures that the library is built together with the Nim code using the same C compiler as the rest of the build, automatically passing compilation flags and using the expected version of the library. 41 | 42 | Alterantives include: 43 | 44 | * build the library as a static or shared library, then make it part of the Nim compilation via `{.passL.}` 45 | * difficult to ensure version compatiblity 46 | * shared library requires updating dynamic library lookup path when running the binary 47 | * build the library as a shared library, then make it part of the Nim compilation via `{.dynlib.}` 48 | * nim will load the library via `dlopen` (or similar) 49 | * easy to run into ABI / version mismatches 50 | * no record in binary about the linked library - tools like `ldd` will not display the dependencies correctly 51 | 52 | ### Naming 53 | 54 | ABI wrappers are identified by `abi` in their name, either as a suffix or as the module name itself: 55 | 56 | * [secp256k1](https://github.com/status-im/nim-secp256k1/blob/master/secp256k1/abi.nim) 57 | * [bearssl](https://github.com/status-im/nim-bearssl/blob/master/bearssl/abi/bearssl_rand.nim) 58 | * [sqlite3](https://github.com/arnetheduck/nim-sqlite3-abi/blob/master/sqlite3_abi.nim) 59 | 60 | ### Functions and types 61 | 62 | Having created a separate module for the type, create definitions for each function and type that is meant to be used from Nim: 63 | 64 | ```nim 65 | # Create a helper pragma that describes the ABI of typical C functions: 66 | # * No Nim exceptions 67 | # * No GC interation 68 | 69 | {.pragma imported, importc, cdecl, raises: [], gcsafe.} 70 | 71 | proc function(arg: int64): cint {.imported.} 72 | ``` 73 | 74 | ### Callbacks 75 | 76 | Callbacks are functions in the Nim code that are registered with the imported library and called from the library: 77 | 78 | ```nim 79 | # The "callback" helper pragma: 80 | # 81 | # * sets an explicit calling convention to match C 82 | # * ensures no exceptions leak from Nim to the caller of the callback 83 | {.pragma: callback, cdecl, raises: [], gcsafe.} 84 | 85 | import strutils 86 | proc mycallback(arg: cstring) {.callback.} = 87 | # Write nim code as usual 88 | echo "hello from nim: ", arg 89 | 90 | # Don't let exceptions escape the callback 91 | try: 92 | echo "parsed: ", parseInt($arg) 93 | except ValueError: 94 | echo "couldn't parse" 95 | 96 | proc registerCallback(callback: proc(arg: cstring) {.callback.}) {.imported.} 97 | 98 | registerCallback(mycallback) 99 | ``` 100 | 101 | Care must be taken that the callback is called from a Nim thread - if the callback is called from a thread controlled by the library, the thread might need to be [initialized](./interop.md#calling-nim-code-from-other-languages) first. 102 | 103 | ### Memory allocation 104 | 105 | Nim supports both garbage-collected, stack-based and manually managed memory allocation. 106 | 107 | When using garbage-collected types, care must be taken to extend the lifetime of objects passed to C code whose lifetime extends beyond the function call: 108 | 109 | ```nim 110 | # Register a long-lived instance with C library 111 | proc register(arg: ptr cint) {.imported.} 112 | 113 | # Unregister a previously registered instance 114 | proc unregister(arg: ptr cint) {.imported.} 115 | 116 | proc setup(): ref cint = 117 | let arg = new cint 118 | 119 | # When passing garbage-collected types whose lifetime extends beyond the 120 | # function call, we must first protect the them from collection: 121 | GC_ref(arg) 122 | register(addr arg[]) 123 | arg 124 | 125 | proc teardown(arg: ref cint) = 126 | # ... later 127 | unregister(addr arg[]) 128 | GC_unref(arg) 129 | ``` 130 | 131 | ## C wrappers 132 | 133 | Sometimes, C headers contain not only declarations but also definitions and / or macros. Such code, when exported to Nim, can cause build problems, symbol duplication and other related issues. 134 | 135 | The easiest way to expose such code to Nim is to create a plain C file that re-exports the functionality as a normal function: 136 | 137 | ```c 138 | #include 139 | 140 | /* Reexport `function` using a name less likely to conflict with other "global" symbols */ 141 | int library_function() { 142 | /* function() is either a macro or an inline funtion defined in the header */ 143 | return function(); 144 | } 145 | ``` 146 | 147 | ## Tooling 148 | 149 | * [`c2nim`](https://github.com/nim-lang/c2nim) - translate C header files to Nim, providing a starting place for wrappers 150 | 151 | ## References 152 | 153 | * [Nim manual, FFI](https://nim-lang.org/docs/manual.html#foreign-function-interface) 154 | * [Nim for C programmers](https://github.com/nim-lang/Nim/wiki/Nim-for-C-programmers) 155 | * [Nim backend reference](https://nim-lang.org/docs/backends.html) 156 | -------------------------------------------------------------------------------- /src/interop.cpp.md: -------------------------------------------------------------------------------- 1 | # C++ 2 | 3 | Nim has two modes of interoperability with C++: API and ABI. 4 | 5 | The API interoperability works by compiling Nim to C++ and using the C++ compiler to compile the result - this mode gives direct access to C++ library code without any intermediate wrappers, which is excellent for exploratory and scripting work. 6 | 7 | However, this mode also comes with several restrictions: 8 | 9 | * the feature gap between Nim and C++ is wider: C++ has many features that cannot be represented in Nim and vice versa 10 | * while "simple C++" can be wrapped this way, libraries using modern C++ features are unlikely to work or may only be wrapped partially 11 | * C++-generated Nim code is more likely to run into cross-library issues in applications using multiple wrapped libraries 12 | * this is due to C++'s increased strictness around const-correctness and other areas where Nim, C and C++ differ in capabilities 13 | * a single library that uses Nim's C++ API features forces the entire application to be compiled with the C++ backend 14 | * the C++ backend receives less testing overall and therefore is prone to stability issues 15 | 16 | Thus, for C++ the recommened way of creating wrappers is similar to other languages: write an export library in C++ that exposes the C++ library via the C ABI then import the C code in Nim - this future-proofs the wrapper against the library starting to use C++ features that cannot be wrapped. 17 | 18 | ## Qt 19 | 20 | [Qt](https://www.qt.io/) takes control of the main application thread to run the UI event loop. Blocking this thread means that the UI becomes unresponsive, thus it is recommended to run any compution in separate threads, for example using [nim-taskpools](https://github.com/status-im/nim-taskpools). 21 | 22 | For cross-thread communication, the recommended way of sending information to the Qt thread is via a [queued signal/slot connection](https://doc.qt.io/qt-6/threads-qobject.html#signals-and-slots-across-threads). 23 | 24 | For sending information from the Qt thread to other Nim threads, encode the data into a buffer allocated with `allocShared` and use a thread-safe queue such as `std/sharedlists`. 25 | 26 | ## Examples 27 | 28 | * [nimqml](https://github.com/filcuc/nimqml) - exposes the Qt C++ library via a [C wrapper](https://github.com/filcuc/dotherside) 29 | * [nlvm](https://github.com/arnetheduck/nlvm/tree/master/llvm) - imports the [LLVM C API]() which wraps the LLVM compiler written in C++ 30 | * [godot](https://github.com/pragmagic/godot-nim) - imports `godot` via its exported [C API](https://docs.godotengine.org/de/stable/tutorials/scripting/gdnative/what_is_gdnative.html) 31 | -------------------------------------------------------------------------------- /src/interop.go.md: -------------------------------------------------------------------------------- 1 | # Go interop 2 | 3 | Nim and Go are both statically typed, compiled languages capable of interop via a simplifed C ABI. 4 | 5 | On the Go side, interop is handled via [cgo](https://pkg.go.dev/cmd/cgo). 6 | 7 | ## Threads 8 | 9 | Go includes a native `M:N` scheduler for running Go tasks - because of this, care must be taken both when calling Nim code from Go: the thread from which the call will happen is controlled by Go and we must initialise the Nim garbage collector in every function exposed to Go, as documented in the [main guide](./interop.md#calling-nim-code-from-other-languages). 10 | 11 | As an alternative, we can pass the work to a dedicated thread instead - this works well for asynchronous code that reports the result via a callback mechanism: 12 | 13 | ```nim 14 | {.pragma: callback, cdecl, raises: [], gcsafe.} 15 | 16 | type 17 | MyAPI = object 18 | queue: ThreadSafeQueue[ExportedFunctionData] # TODO document where to find a thread safe queue 19 | 20 | ExportedFunctionCallback = proc(result: cint) {.callback.} 21 | ExportedFunctionData = 22 | v: cint 23 | callback: ExportedFunctionCallback 24 | 25 | proc runner(api: ptr MyAPI) = 26 | while true: 27 | processQueue(api[].queue) 28 | 29 | proc initMyAPI(): ptr MyAPI {.exportc, raises: [].}= 30 | let api = createShared(MyAPI) 31 | # Shutdown / cleanup omitted for brevity 32 | discard createThread(runner, api) 33 | api 34 | 35 | proc exportedFunction(api: ptr MyAPI, v: cint, callback: ExportedFunctionCallback) = 36 | # By not allocating any garbage-collected data, we avoid the need to initialize the garbage collector 37 | queue.add(ExportedFunctionData(v: cint, callback: callback)) 38 | ``` 39 | 40 | The `go` thread scheduler can detect blocking functions and start new threads as appropriate - thus, blocking the C API function is a good alternative to callbacks - for example, results can be posted onto a queue that is read from by a blocking call. 41 | 42 | ## Variables 43 | 44 | When calling Nim code from Go, care must be taken that instances of [garbage-collected types](./interop.md#garbage-collected-types) don't pass between threads - this means process-wide globals and other forms of shared-memory apporaches of GC types must be avoided. 45 | 46 | [`LockOSThread`](https://pkg.go.dev/runtime#LockOSThread) can be used to constrain the thread from which a particular `goroutine` calls Nim. 47 | 48 | ## `go` interop resources 49 | 50 | * [cgo wiki](https://github.com/golang/go/wiki/cgo) 51 | * [cockroachdb experience](https://www.cockroachlabs.com/blog/the-cost-and-complexity-of-cgo/) - general cgo costs 52 | -------------------------------------------------------------------------------- /src/interop.md: -------------------------------------------------------------------------------- 1 | # Interop with other languages (FFI) 2 | 3 | Nim comes with powerful interoperability options, both when integrating Nim code in other languages and vice versa. 4 | 5 | Acting as a complement to the [manual](https://nim-lang.org/docs/manual.html#foreign-function-interface), this section of the book covers interoperability / [FFI](https://en.wikipedia.org/wiki/Foreign_function_interface): how to integrate Nim into other languages and how to use libraries from other languages in Nim. 6 | 7 | While it is possible to automate many things related to FFI, this guide focuses on core functionality - while tooling, macros and helpers can simplify the process, they remain a cosmetic layer on top of the fundamentals presented here. 8 | 9 | The focus of this guide is on pragmatic solutions available for the currently supported versions of Nim - 1.6 at the time of writing - the recommendations may change as new libraries and Nim versions become available. 10 | 11 | For examples, head to the [interop](https://github.com/status-im/nim-style-guide/tree/main/interop) folder in the style guide repository. 12 | 13 | ## Basics 14 | 15 | In interop, we rely on a lowest common denominator of features between languages - for compiled languages, this is typically the mutually overlapping part of the [ABI](https://en.wikipedia.org/wiki/Application_binary_interface). 16 | 17 | Nim is unique in that it also allows interoperability at the API level with C/C++ - however, this guide focuses on interoperability via ABI since this is more general and broadly useful. 18 | 19 | Most languages define their FFI in terms of a simplified version of the C ABI - thus, the process of using code from one language in another typically consists of two steps: 20 | 21 | * exporting the source library functions and types as "simple C" 22 | * importing the "simple C" functions and types in the target language 23 | 24 | We'll refer to this part of the process as ABI wrapping. 25 | 26 | Since libraries tend to use the full feature set of their native language, we can see two additional steps: 27 | 28 | * exposing the native library code in a "simple C" variant via a wrapper 29 | * adding a wrapper around the "simple C" variant to make the foreign library feel "native" 30 | 31 | We'll call this API wrapping - the API wrapper takes care of: 32 | 33 | * conversions to/from Nim integer types 34 | * introducing Nim idioms such as generics 35 | * adapting the [error handling](./errors.md) model 36 | 37 | The C ABI serves as the "lingua franca" of interop - the [C guide](./interop.c.md) in particular can be studied for topics not covered in the other language-specific sections. 38 | 39 | ## Calling Nim code from other languages 40 | 41 | Nim code can be compiled both as shared and static libraries and thus used from other languages. 42 | 43 | ### Exporting Nim functions to other languages 44 | 45 | To export functions to other languages, the function must be marked as `exportc, dynlib` - in addition, the function should not raise exceptions and use the `cdecl` calling convention typically. 46 | 47 | We can declare a helper `pragma` to set all the options at once: 48 | 49 | ```nim 50 | {.pragma: exported, exportc, cdecl, raises: [].} 51 | ``` 52 | 53 | ### Importing other language functions to Nim 54 | 55 | Similar to when exporting functions, imported functions need to be annotated with several pragmas to ensure they are imported correctly. Since imported functions don't interact with Nim exceptions or the garbage collector, they should be marked with `raises[], gcsafe`. 56 | 57 | ```nim 58 | {.pragma: imported, importc, cdecl, raises: [], gcsafe.} 59 | ``` 60 | 61 | ### Runtime library initialization 62 | 63 | When calling Nim from other languages, the Nim runtime must first be initialized. Additionally, if using garbage collected types, the garbage collector must also be initialized once per thread. 64 | 65 | Runtime initialization is done by calling the `NimMain` function. It can be called either separately from the host language or guarded by a boolean from every exported function. 66 | 67 | Garbage collector initialization is a two-step process: 68 | 69 | * the garbage collector itself must be inititialized with a call to `setupForeignThreadGc` 70 | * `nimGC_setStackBottom` must be called to establish the starting point of the stack 71 | * this function must be called in all places where it is possible that the exported function is being called from a "shorter" stack frame 72 | 73 | Typically, this is solved with a "library initialization" call that users of the library should call near the beginning of every thread (ie in their `main` or thread entry point function): 74 | 75 | ```nim 76 | proc NimMain() {.importc.} # This function is generated by the Nim compiler 77 | 78 | var initialized: Atomic[bool] 79 | 80 | proc initializeMyLibrary() {.exported.} = 81 | if not initialized.exchange(true): 82 | NimMain() # Every Nim library needs to call `NimMain` once exactly 83 | when declared(setupForeignThreadGc): setupForeignThreadGc() 84 | when declared(nimGC_setStackBottom): 85 | var locals {.volatile, noinit.}: pointer 86 | locals = addr(locals) 87 | nimGC_setStackBottom(locals) 88 | 89 | proc exportedFunction {.exported.} = 90 | assert initialized, "You forgot to call `initializeMyLibrary" 91 | 92 | echo "Hello from Nim 93 | ``` 94 | 95 | When creating a Nim library, it should be compiled with the flag `--nimMainPrefix:your_prefix`. This changes the generated `NimMain()` function to `your_prefixNimMain()`, ensuring that its runtime initialization won’t conflict with other Nim libraries or applications. 96 | Then, proceed to use `your_prefixNimMain()` intead of `NimMain()` to initialize your library's Nim runtime. 97 | 98 | In languages such as [Go](./interop.go.md), it is hard to anticipate which thread code will be called from - in such cases, you can safely initialize the garbage collector in every exported function instead: 99 | 100 | ```nim 101 | proc exportedFunction {.exported.} = 102 | initializeMyLibrary() # Initialize the library on behalf of the user - this is usually more convenient 103 | echo "Hello from Nim 104 | ``` 105 | 106 | The garbage collector can be avoided using [manual memory management](#manual-memory-management) techniques, thus removing the requirement to initialize it in each thread - the runtime must always be initialized. 107 | 108 | See also the [Nim documentation](https://nim-lang.org/docs/backends.html#interfacing-backend-code-calling-nim) on this topic. 109 | 110 | ### Globals and top-level code 111 | 112 | Code written outside of a `proc` / `func` is executed as part of `import`:ing the module, or, in the case of the "main" module of the program, as part of executing the module itself similar to the `main` function in C. 113 | 114 | This code will be run as part of calling `NimMain` as noted above! 115 | 116 | ## Exceptions 117 | 118 | You must ensure that no exceptions pass to the foreign language - instead, catch all exceptions and covert them to a different [error handling mechanism](./errors.md), annotating the exported function with `{.raises: [].}`. 119 | 120 | ## Memory 121 | 122 | Nim is generally a GC-first language meaning that memory is typically managed via a thread-local garbage collector. 123 | 124 | Nim also supports manual memory management - this is most commonly used for threading and FFI. 125 | 126 | ### Garbage-collected types 127 | 128 | Garbage-collection applies to the following types which are allocated from a thread-local heap: 129 | 130 | * `string` and `seq` - these are value types that underneath use the GC heap for the payload 131 | * the `string` uses a dedicated length field but _also_ ensures NULL-termination which makes it easy to pass to C 132 | * `seq` uses a similar in-memory layout without the NULL termination 133 | * addresses to elements are stable as long as as elements are not added 134 | * `ref` types 135 | * types that are declared as `ref object` 136 | * non-ref types that are allocated on the heap with `new` (and thus become `ref T`) 137 | 138 | ### `ref` types and pointers 139 | 140 | The lifetime of garbage-collected types is undefined - the garbage collector generally runs during memory allocation but this should not be relied upon - instead, lifetime can be extended by calling `GC_ref` and `GC_unref`. 141 | 142 | `ref` types have a stable memory address - to pass the address of a `ref` instance via FFI, care must be taken to extend the lifetime of the instance so that it is not garbage-collected 143 | 144 | ```nim 145 | proc register(v: ptr cint) {.importc.} 146 | proc unregister(v: ptr cint) {.importc.} 147 | 148 | # Allocate a `ref cint` instance 149 | let number = new cint 150 | # Let the garbage collector know we'll be creating a long-lived pointer for FFI 151 | GC_ref(number) 152 | # Pass the address of the instance to the FFI function 153 | register(addr number[]) 154 | 155 | # ... later, in reverse order: 156 | 157 | # Stop using the instance in FFI - address is guaranteed to be stable 158 | unregister(addr number[]) 159 | # Let the garbage collector know we're done 160 | GC_unref(number) 161 | ``` 162 | 163 | ### Manual memory management 164 | 165 | Manual memory management is done with [`create`](https://nim-lang.org/docs/system.html#create%2Ctypedesc) (by type), [`alloc`](https://nim-lang.org/docs/system.html#alloc.t%2CNatural) (by size) and [`dealloc`](https://nim-lang.org/docs/system.html#dealloc%2Cpointer): 166 | 167 | ```nim 168 | proc register(v: ptr cint) {.importc.} 169 | proc unregister(v: ptr cint) {.importc.} 170 | 171 | # Allocate a `ptr cint` instance 172 | let number = create cint 173 | # Pass the address of the instance to the FFI function 174 | register(number) 175 | 176 | # ... later, in reverse order: 177 | 178 | # Stop using the instance in FFI - address is guaranteed to be stable 179 | unregister(number) 180 | # Free the instance 181 | dealloc(number) 182 | ``` 183 | 184 | To allocate memory for cross-thread usage, ie allocating in one thread and deallocating in the other, use `createShared` / `allocShared` and `deallocShared` instead. 185 | 186 | ## Threads 187 | 188 | Threads in Nim are created with [`createThread`](https://nim-lang.org/docs/threads.html) which creates the thread and prepares the garbage collector for use on that thread. 189 | 190 | See [above](#calling-nim-code-from-other-languages) for how to initialize the garbage collector when calling Nim from threads created in other languages. 191 | 192 | ### Passing data between threads 193 | 194 | The primary method of passing data between threads is to encode the data into a shared memory section then transfer ownership of the memory section to the receiving thread either via a thread-safe queue, channel, socket or pipe. 195 | 196 | The queue itself can be passed to thread either at creation or via a global variable, though we generally seek to avoid global variables. 197 | 198 | ```nim 199 | # TODO pick a queue 200 | 201 | type ReadStatus = enum 202 | Empty 203 | Ok 204 | Done 205 | 206 | proc read(queue: ptr Queue[pointer], var data: seq[byte]): ReadStatus = 207 | var p: pointer 208 | if queue.read(p): 209 | if isNil(p): 210 | ReadStatus.Done 211 | else: 212 | var len: int 213 | copyMem(addr len, p, sizeof(len)) 214 | data = newSeqUninitalized[byte](len) 215 | copyMem(addr data[0], cast[pointer](cast[uint](data) + sizeof(len)), len) 216 | ReadStatus.Ok 217 | else: 218 | ReadStatus.Empty 219 | 220 | proc write(queue: ptr Queue[pointer], data: openArray[byte]) = 221 | # Copy data to a shared length-prefixed buffer 222 | let 223 | copy = allocShared(int(len) + sizeof(len)) 224 | copyMem(copy, addr len, sizeof(len)) 225 | copyMem(cast[pointer](cast[uint](copy) + sizeof(len)), v, len) 226 | 227 | # Put the data on a thread-safe queue / list 228 | queue.add(copy) 229 | 230 | proc reader(queue: ptr Queue[pointer]): 231 | var data: seq[byte] 232 | while true: 233 | case queue.read(data) 234 | of Done: return 235 | of Ok: process(data) 236 | of Empty: 237 | # Polling should usually be replaced with an appropriate "wake-up" mechanism 238 | sleep(100) 239 | ``` 240 | 241 | ### async / await 242 | 243 | When `chronos` is used, execution is typically controlled by the `chronos` per-thread dispatcher - passing data to `chronos` is done either via a pipe / socket or by polling a thread-safe queue. 244 | 245 | See [the async example](https://github.com/status-im/nim-style-guide/tree/main/interop/async/). 246 | 247 | ## Resources 248 | 249 | * [Nim backends documentation](https://nim-lang.org/1.6.0/backends.html) 250 | -------------------------------------------------------------------------------- /src/interop.rust.md: -------------------------------------------------------------------------------- 1 | # Rust interop 2 | 3 | Nim and Rust are both statically typed, compiled languages capable of "systems programming". 4 | 5 | Because of these similarities, interop between Nim and [`rust`](https://doc.rust-lang.org/nomicon/ffi.html) is generally straightforward and handled the same way as [C interop](./interop.c.md) in both languages: Rust code is exported to C then imported in Nim as C code and vice versa. 6 | 7 | ## Memory 8 | 9 | While Nim is a GC-first language, `rust` in general uses lifetime tracking (via `Box`) and / or reference counting (via `Rc`/`Arc`) outside of "simple" memory usage. 10 | 11 | When used with Nim, care must be taken to extend the lifetimes of Nim objects via `GC_ref` / `GC_unref`. 12 | 13 | ## Tooling 14 | 15 | * [`nbindgen`](https://github.com/arnetheduck/nbindgen/) - create Nim ["ABI headers"](./interop.md#basics) from exported `rust` code 16 | -------------------------------------------------------------------------------- /src/language.binary.md: -------------------------------------------------------------------------------- 1 | ## Binary data `[language.binary]` 2 | 3 | Use `byte` to denote binary data. Use `seq[byte]` for dynamic byte arrays. 4 | 5 | Avoid `string` for binary data. If stdlib returns strings, [convert](https://github.com/status-im/nim-stew/blob/76beeb769e30adc912d648c014fd95bf748fef24/stew/byteutils.nim#L141) to `seq[byte]` as early as possible 6 | 7 | ### Pros 8 | 9 | * Explicit type for binary data helps convey intent 10 | 11 | ### Cons 12 | 13 | * `char` and `uint8` are common choices often seen in `Nim` 14 | * hidden assumption that 1 byte == 8 bits 15 | * language still being developed to handle this properly - many legacy functions return `string` for binary data 16 | * [Crypto API](https://github.com/nim-lang/Nim/issues/7337) 17 | 18 | ### Practical notes 19 | 20 | * [stew](libraries.stew.md) contains helpers for dealing with bytes and strings 21 | 22 | -------------------------------------------------------------------------------- /src/language.converters.md: -------------------------------------------------------------------------------- 1 | ## Converters `[language.converters]` 2 | 3 | [Manual](https://nim-lang.org/docs/manual.html#converters) 4 | 5 | Avoid using converters. 6 | 7 | ### Pros 8 | 9 | * Implicit conversions lead to low visual overhead of converting types 10 | 11 | ### Cons 12 | 13 | * Surprising conversions lead to ambiguous calls: 14 | ```nim 15 | converter toInt256*(a: int{lit}): Int256 = a.i256 16 | if stringValue.len > 32: 17 | ... 18 | ``` 19 | ``` 20 | Error: ambiguous call; both constants.>(a: Int256, b: int)[declared in constants.nim(76, 5)] and constants.>(a: UInt256, b: int)[declared in constants.nim(82, 5)] match for: (int, int literal(32)) 21 | ``` 22 | -------------------------------------------------------------------------------- /src/language.finalizers.md: -------------------------------------------------------------------------------- 1 | ## Finalizers `[language.finalizers]` 2 | 3 | [Manual](https://nim-lang.org/docs/system.html#new%2Cref.T%2Cproc%28ref.T%29) 4 | 5 | Don't use finalizers. 6 | 7 | ### Pros 8 | 9 | * Alleviates the need for manual cleanup 10 | 11 | ### Cons 12 | 13 | * [Buggy](https://github.com/nim-lang/Nim/issues/4851), cause random GC crashes 14 | * Calling `new` with finalizer for one instance infects all instances with same finalizer 15 | * Newer Nim versions migrating new implementation of finalizers that are sometimes deterministic (aka destructors) 16 | -------------------------------------------------------------------------------- /src/language.import.md: -------------------------------------------------------------------------------- 1 | ## Import, export `[language.import]` 2 | 3 | [Manual](https://nim-lang.org/docs/manual.html#modules-import-statement) 4 | 5 | `import` a minimal set of modules using explicit paths. 6 | 7 | `export` all modules whose types appear in public symbols of the current module. 8 | 9 | Prefer specific imports. Avoid `include`. 10 | 11 | ```nim 12 | # Group by std, external then internal imports 13 | import 14 | # Standard library imports are prefixed with `std/` 15 | std/[options, sets], 16 | # use full name for "external" dependencies (those from other packages) 17 | package/[a, b], 18 | # use relative path for "local" dependencies 19 | ./c, ../d 20 | 21 | # export modules whose types are used in public symbols in the current module 22 | export options 23 | ``` 24 | 25 | ### Practical notes 26 | 27 | Modules in Nim share a global namespace, both for the module name itself and for all symbols contained therein - because of this, your code might break because a dependency introduces a module or symbol with the same name - using prefixed imports (relative or package) helps mitigate some of these conflicts. 28 | 29 | Because of overloading and generic catch-alls, the same code can behave differently depending on which modules have been imported and in which order - reexporting modules that are used in public symbols helps avoid some of these differences. 30 | 31 | See also: [sandwich problem](https://github.com/nim-lang/Nim/issues/11225) 32 | 33 | -------------------------------------------------------------------------------- /src/language.inline.md: -------------------------------------------------------------------------------- 1 | ## Inline functions `[language.inline]` 2 | 3 | Avoid using explicit `{.inline.}` functions. 4 | 5 | ### Pros 6 | 7 | * Sometimes give performance advantages 8 | 9 | ### Cons 10 | 11 | * Adds clutter to function definitions 12 | * Larger code size, longer compile times 13 | * Prevent certain LTO optimizations 14 | 15 | ### Practical notes 16 | 17 | * `{.inline.}` does not inline code - rather it copies the function definition into every `C` module making it available for the `C` compiler to inline 18 | * Compilers can use contextual information to balance inlining 19 | * LTO achieves a similar end result without the cons 20 | -------------------------------------------------------------------------------- /src/language.integers.md: -------------------------------------------------------------------------------- 1 | ## Integers `[language.integers]` 2 | 3 | Prefer signed integers for counting, lengths, array indexing etc. 4 | 5 | Prefer unsigned integers of specified size for interfacing with binary data, bit manipulation, low-level hardware access and similar contexts. 6 | 7 | Don't cast pointers to `int`. 8 | 9 | ### Practical notes 10 | 11 | * Signed integers are overflow-checked and raise an untracked `Defect` on overflow, unsigned integers wrap 12 | * `int` and `uint` vary depending on platform pointer size - use judiciously 13 | * Perform range checks before converting to `int`, or convert to larger type 14 | * Conversion to signed integer raises untracked `Defect` on overflow 15 | * When comparing lengths to unsigned integers, convert the length to unsigned 16 | * Pointers may overflow `int` when used for arithmetic 17 | * Avoid `Natural` - implicit conversion from `int` to `Natural` can raise a `Defect` 18 | * see [`range`](./language.range.md) 19 | -------------------------------------------------------------------------------- /src/language.macros.md: -------------------------------------------------------------------------------- 1 | ## Macros `[language.macros]` 2 | 3 | [Manual](https://nim-lang.org/docs/manual.html#macros) 4 | 5 | Be judicious in macro usage - prefer more simple constructs. 6 | Avoid generating public API functions with macros. 7 | 8 | ### Pros 9 | 10 | * Concise domain-specific languages precisely convey the central idea while hiding underlying details 11 | * Suitable for cross-cutting libraries such as logging and serialization, that have a simple public API 12 | * Prevent repetition, sometimes 13 | * Encode domain-specific knowledge that otherwise would be hard to express 14 | 15 | ### Cons 16 | 17 | * Easy to write, hard to understand 18 | * Require extensive knowledge of the `Nim` AST 19 | * Code-about-code requires tooling to turn macro into final execution form, for audit and debugging 20 | * Unintended macro expansion costs can surprise even experienced developers 21 | * Unsuitable for public API 22 | * Nowhere to put per-function documentation 23 | * Tooling needed to discover API - return types, parameters, error handling 24 | * Obfuscated data and control flow 25 | * Poor debugging support 26 | * Surprising scope effects on identifier names 27 | 28 | ### Practical notes 29 | 30 | * Consider a more specific, non-macro version first 31 | * Use a difficulty multiplier to weigh introduction of macros: 32 | * Templates are 10x harder to understand than plain code 33 | * Macros are 10x harder than templates, thus 100x harder than plain code 34 | * Write as much code as possible in templates, and glue together using macros 35 | 36 | See also: [macro defense](https://github.com/status-im/nimbus-eth2/wiki/The-macro-skeptics-guide-to-the-p2pProtocol-macro) 37 | 38 | -------------------------------------------------------------------------------- /src/language.md: -------------------------------------------------------------------------------- 1 | # Language features 2 | 3 | Nim is a language that organically has grown to include many advanced features and constructs. These features allow you to express your intent with great creativity, but often come with significant stability, simplicity and correctness caveats when combined. 4 | 5 | Before stepping off the well-trodden path, consider the maintenance and compatibilty costs. 6 | -------------------------------------------------------------------------------- /src/language.memory.md: -------------------------------------------------------------------------------- 1 | ## Memory allocation `[language.memory]` 2 | 3 | Prefer to use stack-based and statically sized data types in core/low-level libraries. 4 | Use heap allocation in glue layers. 5 | 6 | Avoid `alloca`. 7 | 8 | ```nim 9 | func init(T: type Yyy, a, b: int): T = ... 10 | 11 | # Heap allocation as a local decision 12 | let x = (ref Xxx)( 13 | field: Yyy.init(a, b) # In-place initialization using RVO 14 | ) 15 | ``` 16 | 17 | ### Pros 18 | 19 | * RVO can be used for "in-place" initialization of value types 20 | * Better chance of reuse on embedded systems 21 | * https://barrgroup.com/Embedded-Systems/How-To/Malloc-Free-Dynamic-Memory-Allocation 22 | * http://www.drdobbs.com/embedded-systems/embedded-memory-allocation/240169150 23 | * https://www.quora.com/Why-is-malloc-harmful-in-embedded-systems 24 | * Allows consumer of library to decide on memory handling strategy 25 | * It's always possible to turn plain type into `ref`, but not the other way around 26 | 27 | ### Cons 28 | 29 | * Stack space limited - large types on stack cause hard-to-diagnose crashes 30 | * Hard to deal with variable-sized data correctly 31 | 32 | ### Practical notes 33 | 34 | `alloca` has confusing semantics that easily cause stack overflows - in particular, memory is released when function ends which means that in a loop, each iteration will add to the stack usage. Several `C` compilers implement `alloca` incorrectly, specially when inlining. 35 | 36 | -------------------------------------------------------------------------------- /src/language.methods.md: -------------------------------------------------------------------------------- 1 | ## Methods `[language.methods]` 2 | 3 | [Manual](https://nim-lang.org/docs/manual.html#methods) 4 | 5 | Use `method` sparingly - consider a "manual" vtable with `proc` closures instead. 6 | 7 | ### Pros 8 | 9 | * Compiler-implemented way of doing dynamic dispatch 10 | 11 | ### Cons 12 | 13 | * Poor implementation 14 | * Implemented using `if` tree 15 | * Require full program view to "find" all implementations 16 | * Poor discoverability - hard to tell which `method`'s belong together and form a virtual interface for a type 17 | * All implementations must be public (`*`)! 18 | 19 | ### Practical notes 20 | 21 | * Does not work with generics 22 | * No longer does multi-dispatch 23 | 24 | -------------------------------------------------------------------------------- /src/language.objconstr.md: -------------------------------------------------------------------------------- 1 | ## Object construction `[language.objconstr]` 2 | 3 | Use `Xxx(x: 42, y: Yyy(z: 54))` style, or if type has an `init` function, `Type.init(a, b, c)`. 4 | 5 | Prefer that the default 0-initialization is a valid state for the type. 6 | 7 | ```nim 8 | # `init` functions are a convention for constructors - they are not enforced by the language 9 | func init(T: type Xxx, a, b: int): T = T( 10 | x: a, 11 | y: OtherType(s: b) # Prefer Type(field: value)-style initialization 12 | ) 13 | 14 | let m = Xxx.init(1, 2) 15 | 16 | # `new` returns a reference to the given type: 17 | func new(T: type Xxx, a, b: int ): ref T = ... 18 | 19 | # ... or `init` when used with a `ref Xxx`: 20 | func init(T: type (ref Xxx), a, b: int ): T = ... 21 | ``` 22 | 23 | ### Pros 24 | 25 | * Correct order of initialization enforced by compiler / code structure 26 | * Dedicated syntax constructs a clean instance resetting all fields 27 | * Possible to build static analysis tools to detect uninitialized fields 28 | * Works for both `ref` and non-`ref` types 29 | 30 | ### Cons 31 | 32 | * Sometimes inefficient compared to updating an existing `var` instance, since all fields must be re-initialized 33 | * Compared to `func newXxx()`, `func new(T: type Xxx)` will be a generic procedure, which can cause issues. See [Import, export](language.import.md) 34 | 35 | ### Practical notes 36 | 37 | * The default, 0-initialized state of the object often gets constructed in the language - avoiding a requirement that a magic `init` function be called makes the type more ergonomic to use 38 | * Avoid using `result` or `var instance: Type` which disable several compiler diagnostics 39 | * When using inheritance, `func new(T: type Xxx)` will also bind to any type inheriting from Xxx 40 | -------------------------------------------------------------------------------- /src/language.proc.md: -------------------------------------------------------------------------------- 1 | ## Functions and procedures `[language.proc]` 2 | 3 | Prefer `func` - use `proc` when side effects cannot conveniently be avoided. 4 | 5 | Avoid public functions and variables (`*`) that don't make up an intended part of public API. 6 | 7 | ### Practical notes 8 | 9 | * Public functions are not covered by dead-code warnings and contribute to overload resolution in the the global namespace 10 | * Prefer `openArray` as argument type over `seq` for traversals 11 | -------------------------------------------------------------------------------- /src/language.proctypes.md: -------------------------------------------------------------------------------- 1 | ## Callbacks, closures and forward declarations `[language.proctypes]` 2 | 3 | Annotate `proc` type definitions and forward declarations with `{.raises [], gcsafe.}` or specific exception types. 4 | 5 | ```nim 6 | # By default, Nim assumes closures may raise any exception and are not gcsafe 7 | # By annotating the callback with raises and gcsafe, the compiler ensures that 8 | # any functions assigned to the closure fit the given constraints 9 | type Callback = proc(...) {.raises: [], gcsafe.} 10 | ``` 11 | 12 | ### Practical notes 13 | 14 | * Without annotations, `{.raises [Exception].}` and no GC-safety is assumed by the compiler, infecting deduction in the whole call stack 15 | * Annotations constrain the functions being assigned to the callback to follow its declaration, simplifying calling the callback safely 16 | * In particular, callbacks are difficult to reason about when they raise exceptions - what should the caller of the callback do? 17 | -------------------------------------------------------------------------------- /src/language.range.md: -------------------------------------------------------------------------------- 1 | ## `range` `[language.range]` 2 | 3 | Avoid `range` types. 4 | 5 | ### Pros 6 | 7 | * Range-checking done by compiler 8 | * More accurate bounds than `intXX` 9 | * Communicates intent 10 | 11 | ### Cons 12 | 13 | * Implicit conversions to "smaller" ranges may raise `Defect` 14 | * Language feature has several fundamental design and implementation issues 15 | * https://github.com/nim-lang/Nim/issues/16839 16 | * https://github.com/nim-lang/Nim/issues/16744 17 | * https://github.com/nim-lang/Nim/issues/13618 18 | * https://github.com/nim-lang/Nim/issues/12780 19 | * https://github.com/nim-lang/Nim/issues/10027 20 | * https://github.com/nim-lang/Nim/issues?page=1&q=is%3Aissue+is%3Aopen+range 21 | -------------------------------------------------------------------------------- /src/language.refobject.md: -------------------------------------------------------------------------------- 1 | ## `ref object` types `[language.refobject]` 2 | 3 | Avoid `ref object` types, except: 4 | 5 | * for "handle" types that manage a resource and thus break under value semantics 6 | * where shared ownership is intended 7 | * in reference-based data structures (trees, linked lists) 8 | * where a stable pointer is needed for 3rd-party compatibility 9 | 10 | Prefer explicit `ref MyType` where reference semantics are needed, allowing the caller to choose where possible. 11 | 12 | ```nim 13 | # prefer explicit ref modifiers at usage site 14 | func f(v: ref Xxx) = discard 15 | let x: ref Xxx = new Xxx 16 | 17 | # Consider using Hungarian naming convention with `ref object` - this makes it clear at usage sites that the type follows the unusual `ref` semantics 18 | type XxxRef = ref object 19 | # ... 20 | ``` 21 | 22 | ### Pros 23 | 24 | * `ref object` types useful to prevent unintended copies 25 | * Limits risk of accidental stack allocation for large types 26 | * This commonly may lead to stack overflow, specially when RVO is missed 27 | * Garbage collector simplifies some algorithms 28 | 29 | ### Cons 30 | 31 | * `ref object` types have surprising semantics - the meaning of basic operations like `=` changes 32 | * Shared ownership leads to resource leaks and data races 33 | * `nil` references cause runtime crashes 34 | * Semantic differences not visible at usage site 35 | * Always mutable - no way to express immutability 36 | * Cannot be stack-allocated 37 | * Hard to emulate value semantics 38 | 39 | ### Notes 40 | 41 | `XxxRef = ref object` is a syntactic shortcut that hides the more explicit `ref Xxx` where the type is used - by explicitly spelling out `ref`, readers of the code become aware of the alternative reference / shared ownership semantics, which generally allows a deeper understanding of the code without having to look up the type declaration. 42 | -------------------------------------------------------------------------------- /src/language.result.md: -------------------------------------------------------------------------------- 1 | ## `result` return `[language.result]` 2 | 3 | Avoid using `result` for returning values. 4 | 5 | Use expression-based return or explicit `return` keyword with a value 6 | 7 | ### Pros 8 | 9 | * Recommended by NEP-1 10 | * Used in standard library 11 | * Saves a line of code avoiding an explicit `var` declaration 12 | * Accumulation-style functions that gradually build up a return value gain consistency 13 | 14 | ### Cons 15 | 16 | * No visual (or compiler) help when a branch is missing a value, or overwrites a previous value 17 | * Disables compiler diagnostics for code branches that forget to set result 18 | * Risk of using partially initialized instances due to `result` being default-initialized 19 | * For `ref` types, `result` starts out as `nil` which accidentally might be returned 20 | * Helpers may accidentally use `result` before it was fully initialized 21 | * Async/await using result prematurely due to out-of-order execution 22 | * Partially initialized instances lead to exception-unsafe code where resource leaks happen 23 | * RVO causes observable stores in the left-hand side of assignments when exceptions are raised after partially modifying `result` 24 | * Confusing to people coming from other languages 25 | * Confusing semantics in templates and macros 26 | 27 | ### Practical notes 28 | 29 | Nim has 3 ways to assign a return value to a function: `result`, `return` and "expressions". 30 | 31 | Of the three: 32 | 33 | * "expression" returns guarantee that all code branches produce one (and only one) value to be returned 34 | * Used mainly when exit points are balanced and not deeply nested 35 | * Explict `return` with a value make explicit what value is being returned in each branch 36 | * Used to avoid deep nesting and early exit, above all when returning early due to errors 37 | * `result` is used to accumulate / build up return value, allowing it to take on invalid values in the interim 38 | 39 | Multiple security issues, `nil` reference crashes and wrong-init-order issues have been linked to the use of `result` and lack of assignment in branches. 40 | 41 | In general, the use of accumulation-style initialization is discouraged unless made necessary by the data type - see [Variable initialization](language.varinit.md) 42 | 43 | -------------------------------------------------------------------------------- /src/language.string.md: -------------------------------------------------------------------------------- 1 | ## `string` `[language.string]` 2 | 3 | The `string` type in Nim represents text in an unspecified encoding, typically UTF-8 on modern systems. 4 | 5 | Avoid `string` for binary data (see [language.binary](./language.binary.md)) 6 | 7 | ### Practical notes 8 | 9 | * The text encoding is undefined for `string` types and is instead determined by the source of the data (usually UTF-8 for terminals and text files) 10 | * When dealing with passwords, differences in encoding between platforms may lead to key loss 11 | 12 | -------------------------------------------------------------------------------- /src/language.vardecl.md: -------------------------------------------------------------------------------- 1 | ## Variable declaration `[language.vardecl]` 2 | 3 | Use the most restrictive of `const`, `let` and `var` that the situation allows. 4 | 5 | ```nim 6 | # Group related variables 7 | const 8 | a = 10 9 | b = 20 10 | ``` 11 | 12 | ### Practical notes 13 | 14 | `const` and `let` each introduce compile-time constraints that help limit the scope of bugs that must be considered when reading and debugging code. 15 | -------------------------------------------------------------------------------- /src/language.varinit.md: -------------------------------------------------------------------------------- 1 | ## Variable initialization `[language.varinit]` 2 | 3 | Prefer expressions to initialize variables and return values 4 | 5 | ```nim 6 | let x = 7 | if a > 4: 5 8 | else: 6 9 | 10 | func f(b: bool): int = 11 | if b: 1 12 | else: 2 13 | 14 | # Avoid - `x` is not guaranteed to be initialized by all branches and in correct order (for composite types) 15 | var x: int 16 | if a > 4: x = 5 17 | else: x = 6 18 | ``` 19 | 20 | ### Pros 21 | 22 | * Stronger compile-time checks 23 | * Lower risk of uninitialized variables even after refactoring 24 | 25 | ### Cons 26 | 27 | * Becomes hard to read when deeply nested 28 | 29 | -------------------------------------------------------------------------------- /src/libraries.hex.md: -------------------------------------------------------------------------------- 1 | ## Hex output `[libraries.hex]` 2 | 3 | Print hex output in lowercase. Accept upper and lower case. 4 | 5 | ### Pros 6 | 7 | * Single case helps tooling 8 | * Arbitrary choice, aim for consistency 9 | 10 | ### Cons 11 | 12 | * No community consensus - some examples in the wild use upper case 13 | 14 | ### Practical notes 15 | 16 | [byteutils](https://github.com/status-im/nim-stew/blob/76beeb769e30adc912d648c014fd95bf748fef24/stew/byteutils.nim#L129) contains a convenient hex printer. 17 | 18 | -------------------------------------------------------------------------------- /src/libraries.md: -------------------------------------------------------------------------------- 1 | # Libraries 2 | 3 | The libraries section contains guidelines for libraries and modules frequently used in the codebase. 4 | -------------------------------------------------------------------------------- /src/libraries.results.md: -------------------------------------------------------------------------------- 1 | ## Results `[libraries.results]` 2 | 3 | [Manual](https://github.com/arnetheduck/nim-results/blob/592e0dcba157da84e2cced2309fa27dc67b667f3/results.nim#L18) 4 | 5 | Use `Result` to document all outcomes of functions. 6 | 7 | Use `cstring` errors to provide diagnostics without expectation of error differentiation. 8 | 9 | Use `enum` errors when error kind matters. 10 | 11 | Use complex types when additional error information needs to be included. 12 | 13 | Use `Opt` (`Result`-based `Option`) for simple functions that fail only in trivial ways. 14 | 15 | ``` 16 | # Stringly errors - the cstring is just for information and 17 | # should not be used for comparisons! The expectation is that 18 | # the caller doesn't have to differentiate between different 19 | # kinds of errors and uses the string as a print-only diagnostic. 20 | func f(): Result[int, cstring] = ... 21 | 22 | # Calling code acts on error specifics - use an enum 23 | func f2(): Result[int, SomeEnum] = ... 24 | if f2.isErr and f2.error == SomeEnum.value: ... 25 | 26 | # Transport exceptions - Result has special support for this case 27 | func f3(): Result[int, ref SomeError] = ... 28 | ``` 29 | 30 | ### Pros 31 | 32 | * Give equal consideration to normal and error case 33 | * Easier control flow vulnerability analysis 34 | * Good for "binary" cases that either fail or not 35 | * No heap allocations for simple errors 36 | 37 | ### Cons 38 | 39 | * Visual overhead and poor language integration in `Nim` - ugly `if` trees grow 40 | * Nim compiler generates ineffient code for complex types due to how return values are 0-intialized 41 | * Lack of pattern matching makes for inconvenient code 42 | * Standard library raises many exceptions, hard to use cleanly 43 | 44 | ### Practical notes 45 | 46 | * When converting modules, isolate errors from legacy code with `try/except` 47 | * Common helpers may be added at some point to deal with third-party dependencies that are hard to change - see `stew/shims` 48 | 49 | -------------------------------------------------------------------------------- /src/libraries.std.md: -------------------------------------------------------------------------------- 1 | ## Standard library usage `[libraries.std]` 2 | 3 | Use the Nim standard library judiciously. Prefer smaller, separate packages that implement similar functionality, where available. 4 | 5 | ### Pros 6 | 7 | * Using components from the standard library increases compatibility with other Nim projects 8 | * Fewer dependencies in general 9 | 10 | ### Cons 11 | 12 | * Large, monolithic releases make upgrading difficult - bugs, fixes and improvements are released together causing upgrade churn 13 | * Many modules in the standard library are unmaintained and don't use state-of-the-art features of Nim 14 | * Long lead times for getting fixes and improvements to market 15 | * Often not tailored for specific use cases 16 | * Stability and backwards compatibility requirements prevent fixing poor and unsafe API 17 | 18 | ### Practical notes 19 | 20 | Use the following stdlib replacements that offer safer API (allowing more issues to be detected at compile time): 21 | 22 | * async -> chronos 23 | * bitops -> stew/bitops2 24 | * endians -> stew/endians2 25 | * exceptions -> results 26 | * io -> stew/io2 27 | * sqlite -> nim-sqlite3-abi 28 | * streams -> nim-faststreams 29 | 30 | -------------------------------------------------------------------------------- /src/libraries.stew.md: -------------------------------------------------------------------------------- 1 | ## `stew` `[libraries.stew]` 2 | 3 | `stew` contains small utilities and replacements for `std` libraries. 4 | 5 | If similar libraries exist in `std` and `stew`, prefer [stew](https://github.com/status-im/nim-stew). 6 | 7 | ### Pros 8 | 9 | * `stew` solves bugs and practical API design issues in stdlib without having to wait for nim release 10 | * Fast development cycle 11 | * Allows battle-testing API before stdlib consideration (think boost) 12 | * Encourages not growing nim stdlib further, which helps upstream maintenance 13 | 14 | ### Cons 15 | 16 | * Less code reuse across community 17 | * More dependencies that are not part of nim standard distribution 18 | 19 | ### Practical notes 20 | 21 | `nim-stew` exists as a staging area for code that could be considered for future inclusion in the standard library or, preferably, a separate package, but that has not yet been fully fleshed out as a separate and complete library. 22 | -------------------------------------------------------------------------------- /src/libraries.wrappers.md: -------------------------------------------------------------------------------- 1 | ## Wrappers `[libraries.wrappers]` 2 | 3 | Prefer native `Nim` code when available. 4 | 5 | `C` libraries and libraries that expose a `C` API may be used (including `rust`, `C++`, `go`). 6 | 7 | Avoid `C++` libraries. 8 | 9 | Prefer building the library on-the-fly from source using `{.compile.}`. Pin the library code using a submodule or amalgamation. 10 | 11 | The [interop](./interop.md) guide contains more information about foreing language interoperability. 12 | 13 | ### Pros 14 | 15 | * Wrapping existing code can improve time-to-market for certain features 16 | * Maintenance is shared with upstream 17 | * Build simplicity is maintained when `{.compile.}` is used 18 | 19 | ### Cons 20 | 21 | * Often leads to unnatural API for `Nim` 22 | * Constrains platform support 23 | * Nim and `nimble` tooling poorly supports 3rd-party build systems making installation difficult 24 | * Nim `C++` support incomplete 25 | * Less test suite coverage - most of `Nim` test suite uses `C` backend 26 | * Many core `C++` features like `const`, `&` and `&&` difficult to express - in particular post-`C++11` code has a large semantic gap compared to Nim 27 | * Different semantics for exceptions and temporaries compared to `C` backend 28 | * All-or-nothing - can't use `C++` backend selectively for `C++` libraries 29 | * Using `{.compile.}` increases build times, specially for multi-binary projects - use judiciously for large dependencies 30 | 31 | ### Practical notes 32 | 33 | * Consider tooling like `c2nim` and `nimterop` to create initial wrapper 34 | * Generate a `.nim` file corresponding to the `.h` file of the C project 35 | * preferably avoid the dependency on the `.h` file (avoid `{.header.}` directives unless necessary) 36 | * Write a separate "raw" interface that only imports `C` names and types as they're declared in `C`, then do convenience accessors on the Nim side 37 | * Name it `xxx_abi.nim` 38 | * To use a `C++` library, write a `C` wrapper first 39 | * See `llvm` for example 40 | * When wrapping a `C` library, consider ABI, struct layout etc 41 | 42 | ### Examples 43 | 44 | * [nim-secp256k1](https://github.com/status-im/nim-secp256k1) 45 | * [nim-sqlite3-abi](https://github.com/arnetheduck/nim-sqlite3-abi) 46 | * [nim-bearssl](https://github.com/status-im/nim-bearssl/) 47 | * [nim-blscurve](https://github.com/status-im/nim-blscurve/) 48 | -------------------------------------------------------------------------------- /src/tooling.build.md: -------------------------------------------------------------------------------- 1 | ## Build system `[tooling.build]` 2 | 3 | We use a build system with `make` and `git` submodules. The long term plan is to move to a dedicated package and build manager once one becomes available. 4 | 5 | ### Pros 6 | 7 | * Reproducible build environment 8 | * Fewer disruptions due to mismatching versions of compiler and dependencies 9 | 10 | ### Cons 11 | 12 | * Increased build system complexity with tools that may not be familiar to `nim` developers 13 | * Build system dependencies hard to use on Windows and constrained environments 14 | 15 | ### nimble 16 | 17 | We do not use `nimble`, due to the lack of build reproducibility and other team-oriented features. We sometimes provide `.nimble` packages but these may be out of date and/or incomplete. 18 | 19 | -------------------------------------------------------------------------------- /src/tooling.debugging.md: -------------------------------------------------------------------------------- 1 | ## Debugging `[tooling.debugging]` 2 | 3 | * Debugging can be done with `gdb` just as if `C` was being debugged 4 | * Follow the [C/C++ guide](https://code.visualstudio.com/docs/cpp/cpp-debug) for setting it up in `vscode` 5 | * Pass `--opt:none --debugger:native` to disable optimizations and enable debug symbols 6 | -------------------------------------------------------------------------------- /src/tooling.deps.md: -------------------------------------------------------------------------------- 1 | ## Dependency management `[tooling.deps]` 2 | 3 | We track dependencies using `git` submodules to ensure a consistent build environment for all development. This includes the Nim compiler, which is treated like just another dependency - when checking out a top-level project, it comes with an `env.sh` file that allows you to enter the build environment, similar to python `venv`. 4 | 5 | When working with upstream projects, it's sometimes convenient to _fork_ the project and submodule the fork, in case urgent fixes / patches are needed. These patches should be passed on to the relevant upstream. 6 | 7 | ### Pros 8 | 9 | * Reproducible build environment ensures that developers and users talk about the same code 10 | * dependencies must be audited for security issues 11 | * Easier for community to understand exact set of dependencies 12 | * Fork enables escape hatch for critical issues 13 | 14 | ### Cons 15 | 16 | * Forking incurs overhead when upgrading 17 | * Transitive dependencies are difficult to coordinate 18 | * Cross-project commits hard to orchestrate 19 | 20 | ### Practical notes 21 | 22 | * All continuous integration tools build using the same Nim compiler and dependencies 23 | * When a `Nim` or other upstream issue is encountered, consider project priorities: 24 | * Use a work-around, report issue upstream and leave a note in code so that the work-around can be removed when a fix is available 25 | * Patch our branch after achieving team consensus 26 | 27 | -------------------------------------------------------------------------------- /src/tooling.editors.md: -------------------------------------------------------------------------------- 1 | ## Editors `[tooling.editors]` 2 | 3 | ### `vscode` 4 | 5 | Most `nim` developers use `vscode`. 6 | 7 | * [Nim Extension](https://marketplace.visualstudio.com/items?itemName=NimLang.nimlang) gets you syntax highlighting, goto definition and other modernities 8 | * To start `vscode` with the correct Nim compiler, run it with `./env.sh code` 9 | * Run nim files with `F6` 10 | * Suggestions, goto and similar features mostly work, but sometimes hang 11 | * You might need to `killall nimsuggest` occasionally 12 | 13 | ### Other editors with Nim integration 14 | 15 | * Sublime text 16 | * `vim` 17 | -------------------------------------------------------------------------------- /src/tooling.md: -------------------------------------------------------------------------------- 1 | # Tooling 2 | -------------------------------------------------------------------------------- /src/tooling.nim.md: -------------------------------------------------------------------------------- 1 | ## Nim version 2 | 3 | We support a single Nim version that is upgraded between release cycles of our own projects. Individual projects and libraries may choose to support multiple Nim versions, though this involves significant overhead. 4 | 5 | ### Pros 6 | 7 | * Nim `devel` branch, as well as feature and bugfix releases often break the codebase due to subtle changes in the language and code generation which are hard to diagnose - each upgrade requires extensive testing 8 | * Easier for community to understand exact set of dependencies 9 | * Balance between cutting edge and stability 10 | * Own branch enables escape hatch for critical issues 11 | 12 | ### Cons 13 | 14 | * Work-arounds in our code for `Nim` issues add technical debt 15 | * Compiler is rebuilt in every clone 16 | 17 | ### Practical notes 18 | 19 | * Following Nim `devel`, from experience, leads to frequent disruptions as "mysterious" issues appear 20 | * To support multiple Nim versions in a project, the project should be set up to run CI with all supported versions 21 | -------------------------------------------------------------------------------- /src/tooling.profiling.md: -------------------------------------------------------------------------------- 1 | ## Profiling 2 | 3 | * Linux: `perf` 4 | * Anywhere: [vtune](https://software.intel.com/content/www/us/en/develop/tools/oneapi/components/vtune-profiler.html) 5 | -------------------------------------------------------------------------------- /src/tooling.tricks.md: -------------------------------------------------------------------------------- 1 | ## Code tricks `[tooling.tricks]` 2 | 3 | * Find out where a function is used: temporarily mark it `{.deprecated.}` 4 | --------------------------------------------------------------------------------