👑

Nim 1.6 で追加されたモジュールのプライベートアクセスについて

2021/12/12に公開

プライベートアクセスが追加された

こんにちは、前回に引き続きNim 1.6 Major Updateで追加された機能について紹介します。
この記事は、Nim Advent Calendar 2021の12日目です。

実験的機能で試されていたモジュール上でプライベートシンボル・メンバーへアクセスを行う{.all.}プラグマが追加されました。
本機能はtimotheecour氏によって2020年12月9日にRFCで提案されたものであり、氏とコアメンバーが実装に取り組んできました。
次のコード[1]で示されているように、import文で読み込むモジュールオブジェクトに対してプラグマを付与することで、公開されたシンボルだけでなく非公開のシンボルも含めて全てのシンボルにアクセス可能になります。

sample.nim
from system {.all.} as system2 import nil
echo system2.ThisIsSystem # ThisIsSystem is private in `system`
import os {.all.} # weirdTarget is private in `os`
echo weirdTarget # or `os.weirdTarget`

例えばThisIsSystemsystemモジュールで定義される非公開の定数[2]ですし、weirdTargetosモジュールで定義される非公開の定数[3]です。

Nimが他のファイルで定義されたシンボルにアクセスする方法は2つあります。
多くのケースではimportを選択しますが、includeを用いてファイルを直接読み込むことでもアクセス可能です[4]
しかし、これはモジュールシステムの管理下にない操作です。例えば、読み込み先のファイルに読み込み元と同名のシンボル名が定義されていた場合、モジュールシステムの管理下にあるimportではモジュールの名前空間を利用して両立できます。しかし、includeで読み込む場合は単にソースコードがすべて展開されるためコンパイルエラーが発生します。

そもそもプライベートなシンボルにアクセスするプログラムは直感的に危険に感じますが、例えばユニットテストではなるべくすべてのシンボルに対してテストしたいという需要があります。
現在はincludeが用いて展開していますが、今後は{.all.}を用いてモジュールシステムの管理下でプライベートなシンボルにアクセスすることになります。

includeより本機能が優れている点として部分的にプライベートシンボルにアクセス可能なことが挙げられます[5]


Nim 1.6で追加されたimportutilsモジュールでprivateAccessプロシージャが提供されています。
これは、型の非公開メンバにアクセスするためのプロシージャです。

std/importutils.nim
proc privateAccess(t: typedesc) {.magic: "PrivateAccess", raises: [], tags: [].}

例えば、次のような型を定義したfoo.nimを示します。
member1は非公開メンバですから他のモジュールからそのままではアクセスすることはできません。

foo.nim
type
  Foo = object
    member1: int

proc initFoo* (): Foo = Foo(member1: 1)

しかし、bar.nimからでもprivateAccessを適用することで非公開メンバにアクセス可能です。

bar.nim
import foo
import std/importutils

var foo = initFoo()
privateAccess(foo.type)
foo.member1 = 10 # ok

感想

プライベートアクセスの紹介でした。
じわじわと少しずつではありますが、NimはNimの路線でコードを安全に保つ方法が模索されて進化しています。特にユニットテストなどにおいて、ぜひincludeから脱却してモジュールシステム上でプライベートシンボルにアクセスしましょう。

Advent Calender 2021

脚注
  1. Nim - changelog_1_6_0より引用。 ↩︎

  2. デバッグ用の定数。https://github.com/nim-lang/Nim/blob/c239db4817586f769327a6d27c244682111f903a/lib/system.nim#L261 ↩︎

  3. 奇怪なターゲット。NimScriptか、JavaScriptターゲットである場合にtrueとなります。https://github.com/nim-lang/Nim/blob/62701cd3b92677c2014cf70b1cf5a5ba6e0468bf/lib/pure/os.nim#L37 ↩︎

  4. karaxはExampleでinclude karax / preludeを採用しています。karaxの実装を深く理解しているわけではないため意図は不明ですが、importexportを兼ねた動作が期待できます。 ↩︎

  5. fromexceptを用いてモジュールからシンボルを選択的に読み込みます。 ↩︎

Discussion