カテゴリー:システム
対応Nimバージョン:使っている Nim 本体のバージョンに準拠
公式レポジトリ:https://github.com/nim-lang/Nim
systemとは?
概要
system は、Nim でプログラムを書くときの「最初からそこにある基本機能の集まり」である。公式ドキュメントでも、コンパイラ自体が system に依存しており、逆に system もコンパイラの特別な仕組みに依存していると説明されている。
つまりこれは単なる便利ライブラリではなく、言語の土台そのものに近い存在だ。
各モジュールは system を自動で取り込むため、普段は import system と書かないし、ユーザー側で system.nim という別モジュールを定義することもできない。
また、system は一枚岩ではない。
公式ドキュメント上でも iterators、exceptions、assertions、dollars、ctypes、repr_v2 などの関連文書に分かれており、基本演算、型、文字列、参照、例外、デバッグ表示、C連携まで広く関わっている。
初心者が Nim を学ぶとき、実際には system の関数や型を知らないうちに毎回使っている、と考えたほうがよい。
echo、len、new、high、low、inc、dec、quit などは、その代表例である。
インストール方法
system 自体を単独でインストールすることはしない。
必要なのは Nim のインストール だけである。
公式の Unix 向けインストール案内では choosenim による導入方法や、各OS向けの配布物、ソースからのビルド手順が案内されている。
Nim を導入すると標準ライブラリも一緒に入るため、その中に system も含まれる。
実行確認は、たとえば main.nim を作って nim r main.nim を実行すればよい。
公式のコンパイラガイドでも nim r は「コンパイルしてそのまま実行する」コマンドとして説明されている。
初心者はまず Nim 本体の導入を済ませ、その後は system を個別に意識するより、標準で使えるものとして触りながら覚えるのが自然である。
特徴
system の強みは、「言語機能に極めて近いAPIがまとめて置かれている」 点にある。
たとえば echo は標準出力への表示、len は文字列や seq や openArray の長さ取得、new は参照型の生成、defined はコンパイル時条件の確認、typeof は式の型の取得、addr はアドレス取得というように、日常的な処理から低レベル寄りの処理まで同じモジュール圏内に並んでいる。
しかも多くが compiler magic を使っており、普通の外部ライブラリより言語本体と密接に結びついている。
ほかのパッケージと違うのは、導入コストがほぼゼロ なことだ。
外部パッケージなら依存関係、導入方法、バージョン差、APIの流儀を毎回確認する必要がある。
しかし system は最初から使え、しかも Nim の基礎文法や標準的な書き方と一体になっている。
初心者が Nim らしいコードを書くうえで、最優先で慣れるべき対象と言ってよい。
さらに、抽象度の幅も広い。
seq[T] や set[T] のような普段使いの型から、ptr T や cstring のようなC寄りの型、typedesc や openArray のようなジェネリック設計で重要な仕組みまで、system の周辺だけでかなり多くの基礎概念を学べる。
つまり system を理解することは、単に関数を覚えることではなく、Nim の設計思想そのものを理解すること に近い。
使いどころ
もっとも基本的な使いどころは、文字列や配列や seq を扱う日常処理である。
長さを調べるなら len、先頭や末尾の添字を安全に取りたいなら low と high、要素数を増減しながら扱いたいなら setLen や add を使う。
初心者が「とりあえず配列っぽいものを扱う」場面では、かなり高い確率で system の機能を使っている。
次に重要なのは、参照型やオブジェクトを使う場面である。
Nim で ref object を生成するときは new が基本になるし、継承関係のあるオブジェクトで「この値は本当にこの派生型か」を確かめるには of が使える。
クラスベース言語に慣れている人でも、Nim ではこの辺りの基本操作が system 側に近いところに置かれているため、最初に把握しておくと混乱しにくい。
三つ目は、条件付きコンパイルや環境差の吸収である。
defined(x) はコンパイル時にシンボルが定義されているかを判定する特別な手続きであり、release ビルドかどうか、OSごとの分岐を書くかどうか、といった場面で役立つ。
これは実行時 if とは別物で、ビルド時に分岐を決める ところが要点だ。
最後に、C言語寄りの低レベル処理や最適化寄りの場面でも system は使われる。
addr でアドレスを取り、ptr T や cstring を扱い、sizeof でサイズを調べるようなコードは、通常のアプリ開発では毎回必要ではないが、FFI や高速化、組み込み寄りの処理では重要になる。
初心者はまず高レベルの使い方から入り、必要になったら低レベル側へ進むのがよい。
使い方
まず最初に覚えるべきなのは、system を 明示的に import しない ことだ。
Nim では各モジュールが system を暗黙的に取り込むため、普段のコードではそのまま echo や len を使えばよい。コンパイルと実行は nim r main.nim で行える。
# main.nim
echo "Hello, Nim"
let name = "system"
echo name.len
この例では echo が標準出力への表示、len が文字列長の取得である。
どちらも system の代表的な基本機能だ。
次は seq の基本操作である。
Nim では可変長配列として seq[T] をよく使う。
要素追加には add、長さ変更には setLen、添字範囲の確認には low と high が便利だ。
var nums: seq[int] = @[]
nums.add(10)
nums.add(20)
nums.setLen(5)
for i in low(nums)..high(nums):
echo i, ": ", nums[i]
setLen(5) の直後は、追加された要素が型のデフォルト値で埋まる。int なら 0 なので、この例では末尾に 0 が入る。
これは初学者が seq を理解するうえで大事な挙動だ
集合型 set[T] を使うときは incl がわかりやすい。要素を集合に追加する用途で使う。
var flags = {'a', 'c'}
flags.incl('b')
echo flags.len
この例では incl('b') で 'b' を集合に追加している。
集合は重複を許さないため、同じ要素を何度追加しても一回しか入らない。
これも初心者が Nim の集合型を理解するうえで重要なポイントだ。
参照型を作るときは new を使う。
Nim の ref object は最初から値が入っているわけではないので、必要なら生成してからフィールドを書き込む。
type
User = ref object
name: string
age: int
var u: User
new(u)
u.name = "Alice"
u.age = 20
echo u.name
ここでは new(u) で参照型の値を生成している。
生成された値はフィールドがデフォルト値(この場合は空文字列と 0)で初期化される。
これも初心者が Nim の参照型を理解するうえで重要なポイントだ。
型に応じた既定値を欲しいなら default(T) が使える。
これはオブジェクトのデフォルトフィールドも考慮した既定値を返す。初期値確認やテストコードで地味に役立つ。
type
Point = object
x: int
y: int
let p = default(Point)
echo p.x, ", ", p.y
この例では default(Point) で Point 型の既定値を生成している。
結果は 0, 0 になる。
これも初心者が Nim の型の既定値を理解するうえで覚えておきたい機能だ。
コンパイル時条件分岐では defined が重要だ。when と組み合わせると、実行時ではなくコンパイル時に枝が決まる。
リリースビルドだけ処理を変えたい、OS別に分岐したい、といったときに使う。
when defined(release):
echo "release build"
else:
echo "debug build"
ここでは defined(release) でリリースビルドかどうかを判定している。
ビルド時に分岐が決まるため、実行時のオーバーヘッドはない。
これも初心者が Nim のコンパイル時条件分岐を理解するうえで重要なポイントだ。
式の型を調べたいときは typeof が使える。
ジェネリックやマクロ寄りの文脈で使うことが多いが、初心者でも「この式の型は何になるのか」を掴む助けになる。
let a = 10
let b = 3.14
static:
doAssert typeof(a) is int
doAssert typeof(b) is float64
この例では typeof(a) と typeof(b) で式の型を調べている。
結果はそれぞれ int と float64 になる。
これも初心者が Nim の式の型を理解するうえで覚えておきたい機能だ。
低レベル寄りでは addr と sizeof がある。
これは C に近い操作であり、普通のアプリでは多用しないが、FFI やバイナリ処理では重要になる。
var x = 123
let px = addr x
echo px[]
echo sizeof(x)
ここでは addr x で変数 x のアドレスを取得し、px[] でその値を参照している。
また sizeof(x) で x のサイズを調べている。
これも初心者が Nim の低レベル操作を理解するうえで覚えておきたい機能だ。
注意点
最初の注意点は、system を普通の外部パッケージだと思わないことだ。
これは各モジュールで暗黙に import されるため、通常は import system と書かないし、自分で system.nim という名前のモジュールを作ることもできない。
ここを勘違いすると、名前衝突や設計の混乱が起きる。
二つ目は、Nim のインストールや標準ライブラリの探索パスが壊れると、system.nim を開けないエラーが出ることだ。
公式リポジトリの issue でも Error: cannot open '/usr/lib/nim/lib/system.nim' や Error: cannot open '/usr/lib/system.nim' といった事例が記録されている。
こういう場合はコードの問題というより、Nim 本体の導入や --lib 設定、nim.cfg、パッケージ配布側の構成を疑うべきである。
コンパイラガイドでも $lib が標準ライブラリのパスとして扱われると説明されている。
三つ目は、high(value) や low(value) のような古い書き方に注意することだ。
公式ドキュメントでは high(value) と low(value) は deprecated とされており、現在は high(type) と low(type) を使うべきだ。
昔のサンプルを読むと紛らわしいが、今から書くなら新しい形に寄せたほうがよい。
四つ目は、値域や境界に関する defect である。
たとえば chr は 0..255 の範囲しか受け取れず、256 のような値を渡すと RangeDefect になる。
inc や dec も、存在しない値まで進めたり戻したりすると OverflowDefect になる。
Nim は比較的安全寄りなので、こうした誤りをそのまま見逃さない。
初心者は「型が合えば何でも通る」と思わないほうがよい。
五つ目は、isNil の対象である。公式 docs では isNil(string) はエラーになるよう定義されている。
つまり string に対して「nil かどうか」を調べる発想は適切ではない。
空文字列かどうかを見たいなら s.len == 0 や s == "" を使うべきである。
ここは他言語の癖を持ち込むと引っかかりやすい。
型・関数・API
system は範囲が広すぎるので、ここでは初心者が最初に把握すべき主要な型と関数に絞って整理する。
まず型まわりでは、RootObj はオブジェクト継承の土台、RootRef は RootObj への参照型、seq[T] は可変長配列、set[T] は集合である。
これだけでも Nim のデータ表現のかなり重要な部分を押さえられる。
次に、ジェネリックや型操作で頻出なのが typedesc[T]、openArray[T]、varargs[...]、sink T、range[a..b]、HSlice[U, V] である。
typedesc[T] は「型そのものを引数に取る」ための仕組みで、new(T) や high(int) のような場面で出る。
openArray[T] は配列や seq をまとめて受けるための抽象的な引数型で、len などで使われる。
varargs は echo のような可変長引数、sink T はムーブ寄りの引数、range は値域制約、HSlice は範囲表現に関わる。
関数群は用途別に覚えると整理しやすい。出力系では echo が最重要で、値を標準出力に書き出してフラッシュする。
長さ取得では len が文字列、seq、set、openArray などに広く使える。
添字や範囲確認では low と high、参照型生成では new、終了処理では quit が基本になる。
更新系のAPIとしては、数値や列挙型を進める inc と dec、文字列や seq に追加する add、集合に要素を入れる incl、長さを変える setLen が代表格である。
これらは日常コードでよく出る。
特に add と setLen は seq の扱いに直結するので、配列処理を書くなら早めに慣れたほうがよい。
型判定やコンパイル時処理では、defined、typeof、of、default が重要である。
defined はビルド条件判定、typeof は式の型取得、of はオブジェクトが特定の型のインスタンスかどうかの確認、default は型の既定値取得に使う。
ジェネリックや分岐の設計で差が出る部分だ。
低レベル寄りでは addr、sizeof、isNil を覚えておくとよい。addr はアドレス取得、sizeof はサイズ取得、isNil は ptr や ref などが nil かを高速に確認する用途で使う。ただし string には使えない。
ここは便利だが、ポインタや参照の意味が曖昧なまま多用すると危ないので、最初は必要な分だけに留めるべきだ。