kubo39's blog

ただの雑記です。

LDCにおいてもはや必要なくなったcopy-relocation checkのためのワークアラウンドを消した話

LDCのバグで困っている、というので調査をしてみた。

調べてみると、どうもこれはLDCで実行バイナリを生成する場合(より正確にはmain関数を定義している場合)のみ行われるcopy-relocation checkのために必要とされるようだ。

なんでcopy-relocation checkが必要とされているかというと、モジュールの衝突検出を行う際に用いられているためである。

モジュールの衝突検知は共有オブジェクトをロード/リンクした際に別のモジュール空間のModuleInfoと同一セグメントに置かれていないかどうかをチェックする機構である。 もし同一セグメント上に重なって配置してしまうとModuleInfoは(たぶん)後からロードされた共有ライブラリのほうがセグメントをひっそりと上書きしてしまうのでユーザが意図しない挙動を引き起こしたりする。 copy-relocationのロジックはあまりちゃんと読んでいないが、どうやらdruntimeを特別扱いするために必要なようだ。

しかしモジュール衝突検知はdruntimeから削除されており、そのためcopy-relocation checkももはや DMD LDC ともに存在していない。 そのためLDCはcopy-relocation checkのためのワークアラウンドなシンボル作成を必要としなくなっている。

これを削除するプルリクエストはすでに投げており、マージはしてもらえそうだがシンボル情報を使っているライブラリがあるかもしれないということでv1.19ではひとまず入らない方向となっている。

DMDのDWARFが2019年でまだv3だった

ふと、DMDが生成するデバッグ情報を調べていたところDMDはDWARF v3でデバッグ情報を付与するようだ。

DMDのバージョンは2.089.0を使っている。

$ dmd --version
DMD64 D Compiler v2.089.0
Copyright (C) 1999-2019 by The D Language Foundation, All Rights Reserved written by Walter Bright
  • DWARF v3にあがったときのコミット (2015年)

[dwarf] Bump default debug version to DWARF3 by ibuclaw · Pull Request #4734 · dlang/dmd · GitHub

  • DWARF v4の拡張の定義(2015年)

Add DWARF 4 extensions by ibuclaw · Pull Request #4733 · dlang/dmd · GitHub

上記の変更でv4移行のバージョンが定義されている場合 DW_AT_MIPS_linkage_name ではなく DW_AT_linkage_name を用いるようになっているが、DMD内の定義で enum DWARF_VERSION = 3 と固定されているので DW_AT_MIPS_linkage_name が生成されている。

$ LANG=C objdump --dwarf=info helloworld| grep linkage_name| ddemangle
    <75>   DW_AT_MIPS_linkage_name: _Dmain
    <ce>   DW_AT_MIPS_linkage_name: main
    <77a>   DW_AT_MIPS_linkage_name: @safe void std.stdio.writeln!(immutable(char)[]).writeln(immutable(char)[])
    <89e>   DW_AT_MIPS_linkage_name: @safe void std.stdio.File.LockingTextWriter.put!(immutable(char)[]).put(scope immutable(char)[])
    <9c6>   DW_AT_MIPS_linkage_name: pure nothrow @nogc @safe const(char)[] std.stdio.File.LockingTextWriter.put!(immutable(char)[]).put(scope immutable(char)[]).__dgliteral2()
    <a7a>   DW_AT_MIPS_linkage_name: nothrow @nogc @trusted ulong std.stdio.trustedFwrite!(char).trustedFwrite(shared(core.stdc.stdio._IO_FILE)*, const(char[]))
    <b4d>   DW_AT_MIPS_linkage_name: @safe int std.exception.enforce!(std.exception.ErrnoException).enforce!(int).enforce(int, lazy const(char)[], immutable(char)[], ulong)
    <c34>   DW_AT_MIPS_linkage_name: @safe void std.exception.bailOut!(std.exception.ErrnoException).bailOut(immutable(char)[], ulong, scope const(char)[])
    <cdf>   DW_AT_MIPS_linkage_name: pure nothrow @property @safe immutable(char)[] object.idup!(const(char)).idup(const(char)[])
    <d71>   DW_AT_MIPS_linkage_name: pure nothrow @trusted immutable(char)[] object._trustedDup!(const(char), immutable(char))._trustedDup(const(char)[])
    <e2d>   DW_AT_MIPS_linkage_name: pure nothrow immutable(char)[] object._dup!(const(char), immutable(char))._dup(const(char)[])
    <f45>   DW_AT_MIPS_linkage_name: pure nothrow @nogc @safe void object._doPostblit!(immutable(char))._doPostblit(immutable(char)[])
    <100d>   DW_AT_MIPS_linkage_name: pure nothrow @nogc @trusted void delegate(ref immutable(char)) pure nothrow @nogc @safe object._getPostblit!(immutable(char))._getPostblit()
    <10a2>   DW_AT_MIPS_linkage_name: @safe void std.stdio.File.LockingTextWriter.put!(immutable(char)).put(immutable(char))
    <115a>   DW_AT_MIPS_linkage_name: nothrow @nogc @trusted int std.stdio.File.LockingTextWriter.put!(immutable(char)).put(immutable(char)).trustedFPUTC(int, core.stdc.stdio._IO_FILE*)
    <124d>   DW_AT_MIPS_linkage_name: nothrow @nogc @trusted int std.stdio.File.LockingTextWriter.put!(immutable(char)).put(immutable(char)).trustedFPUTWC(dchar, core.stdc.stdio._IO_FILE*)
    <131d>   DW_AT_MIPS_linkage_name: @safe void std.stdio.File.LockingTextWriter.put!(char).put(char)
    <13c6>   DW_AT_MIPS_linkage_name: nothrow @nogc @trusted int std.stdio.File.LockingTextWriter.put!(char).put(char).trustedFPUTC(int, core.stdc.stdio._IO_FILE*)
    <14a1>   DW_AT_MIPS_linkage_name: nothrow @nogc @trusted int std.stdio.File.LockingTextWriter.put!(char).put(char).trustedFPUTWC(dchar, core.stdc.stdio._IO_FILE*)
    <3410>   DW_AT_linkage_name: (indirect string, offset: 0x120): __register_atfork

__register_atforkDW_AT_linkage_name と出てきているが、これはDMDとは関連がない。

<1><340f>: Abbrev Number: 60 (DW_TAG_subprogram)
    <3410>   DW_AT_external    : 1
    <3410>   DW_AT_declaration : 1
    <3410>   DW_AT_linkage_name: (indirect string, offset: 0x120): __register_atfork
    <3414>   DW_AT_name        : (indirect string, offset: 0x120): __register_atfork
    <3418>   DW_AT_decl_file   : 42
    <3419>   DW_AT_decl_line   : 52

山陰のうまい飯 2019年

前回の帰省のタイミングでアップデート。松江・米子近郊。

松江

山美世(うなぎ)

  • おすすめ度: 9/10
  • 値段: やや高め

今松江近辺で一番美味しいうなぎはここ。 ちょっと立地がわるいかも、米子空港からは近い。 うなぎの名店は他に「いずも」や「おおはかや」などもある。

神代そば (そば)

  • おすすめ度: 10/10
  • 値段: お手頃

定番中の定番。割子蕎麦がおすすめ。 観光スポットにも近いので観光がてらに。 甘い出雲そばらしさを求めるなら近くにある「きがるそば」のほうがいいかも。

ろんぢん (ステーキ・しゃぶしゃぶ)

  • おすすめ度: 9/10
  • 値段: お高い

高級志向であればここ。島根は牛がうまいのだ。 ここも有名観光地付近なので周辺の散策を兼ねるのがいいかも。

Mr.ビーフ (ステーキ)

  • おすすめ度: 9/10
  • 値段: お高い

島根和牛のシャトーブリアンのステーキが比較的お手頃に食べられる。 ちなみに東京・銀座に姉妹店がある。

博多 (海鮮)

  • おすすめ度: 10/10
  • 値段: そこそこ

郷土料理の鯛まま・かにままをはじめ海鮮がおいしい。

晶福 (飲み屋・海鮮)

  • おすすめ度: 9/10
  • 値段: お手頃

普通の飲み屋っぽい佇まいだが、とれたての海鮮と多種の地酒がおいている。 わりと手頃な価格なのも嬉しいところ。

山陰地ビール館 (地ビール)

  • おすすめ度: 9/10
  • 値段: そこそこ

島根の地ビールが飲める場所。 私的にここのヴァイツェンがわりと好き。

喫茶きはる (和菓子)

  • おすすめ度: 10/10
  • 値段: そこそこ

和菓子職人が作る創作和菓子を楽しめる。松江を感じられるのはここが一番かな。 人間国宝だかなんだからしい?

カフェヴィータ (カフェ)

  • おすすめ度: 10/10
  • 値段: そこそこ

山陰が誇るバリスタ一家の息子・弟の店。 でかいバリスタマシンはみるだけで楽しめる。 なんといってもカフェオレがおすすめ。

クーラン・デ・エール (カフェ)

  • おすすめ度: 9/10
  • 値段: そこそこ

駅前のちょっと大人なカフェ。 コーヒー&アルコールな飲み物が名物。 自家製ケーキもgood。

出雲

吉岡製菓 (洋菓子・和菓子)

  • おすすめ度: 9/10
  • 値段: そこそこ

フルーツを使った創作菓子の店として界隈では有名。 ここは販売のみなので注意。通販でもいいかも。

安来

カフェロッソ (カフェ)

  • おすすめ度: 10/10
  • 値段: そこそこ

山陰が誇るバリスタ一家の息子・兄の店。 ここもカフェオレがおすすめ。 景色も楽しめる。

境港

境港

  • おすすめ度: 8/10
  • 値段: そこそこ~お高い

なんか海鮮いろいろ売ってる、食べれるとこもある。

米子・日吉津

ダックン・ダック (フランス料理)

  • おすすめ度: 10/10
  • 値段: やや高め

コース料理ではここ。素材にこだわった肉も魚も野菜もおいしい。 席は多くないので念の為予約はしたほうがいいかもしれない。 ランチが狙いどころか。

山芳亭 (海鮮)

  • おすすめ度: 8/10
  • お値段: そこそこ

地元民はここにいく。 境港とれたての海の幸を安価に食べられる。

ずっとdruntimeのベンチマークのコンパイルがリンカエラーで落ち続けてる

最近ちっともDMDのビルドがうまくいかない。 ホストのコンパイラとしてDMD 2.087.1にパスを通した状態で、全部(dmd,druntime,phobos)のレポジトリに make -f posix.mak clean をかけた状態でmasterを更新してdmdmake -f posix.mak -j4phobosでも make -f posix.mak -j4 する。

その状態でdruntime直下に移動して make -f posix.mak benchmark-compile-only をかけると以下のようなリンクエラーに遭遇する。

(dmd-2.087.1)$ LANG=C make -f posix.mak benchmark-compile-only
../dmd/generated/linux/release/64/dmd -conf= -m64 -I/home/kubo39/dev/dlang/druntime/import -I../phobos -L-L../phobos/generated/linux/release/64 -fPIC -defaultlib=libphobos2.so -L-rpath=../phobos/generated/linux/release/64 -de benchmark/runbench.d -ofgenerated/linux/release/64/benchmark
DMD=../dmd/generated/linux/release/64/dmd generated/linux/release/64/benchmark --repeat=0 --dflags="-conf= -m64 -I/home/kubo39/dev/dlang/druntime/import -I../phobos -L-L../phobos/generated/linux/release/64 -fPIC -defaultlib=libphobos2.so -L-rpath=../phobos/generated/linux/release/64 -de"
generated/linux/release/64/benchmark: symbol lookup error: generated/linux/release/64/benchmark: undefined symbol: _D4core8internal5array5utils12__ModuleInfoZ
posix.mak:346: recipe for target 'benchmark-compile-only' failed
make: *** [benchmark-compile-only] Error 127

この現象がいつまでたってもなおらない。おかげでrunbench.dでなんでパッチが落ちるのかの原因調査ができない。 もうずっとこんな調子で嫌気がさしてきた。CIは動いているので、なんか自分のところの環境が変なんだろうけどまるっきり見当がつかない。

とにかくこんな調子じゃ開発なんてできやしない。

追記

cloneしなおしてきてもできなかったが、dmd 2.086.1をホストにしたら make -f posix.mak benchmark-compile-only が成功した。dmd 2.087.0以降のregression?

いまさらマンデルブロー集合を書くので、なるべく型安全っぽくを意識した

プログラミングRustをちらちら読んでいたら並列化の例としてあったので、いまさらマンデルブロー集合を実装した。

元のコードがRustなのでなるべく型安全な感じに書いてみたつもり。

  • 他の言語でfind相当のものがcountUntilという名前で、findは別の操作を行う関数だった。紛らわしい。
  • PNGファイルの出力はimageformatsというライブラリを使った。ヘッダから自動で画像形式を推定してくれる、手軽に使えるので便利。
  • nogcというライブラリを使えば型変換をnogcでできるようだが、結局write_imageGC必要とするので用いなかった。
  • tupleの中の要素はinoutと組み合わせられないのか、 stack based variables can be inout だそう。

ソースコードは以下。

import core.stdc.stdlib : exit;
import std.complex;
import std.conv : to, ConvException;
import std.parallelism : parallel;
import std.range : chunks;
import std.stdio;
import std.typecons : Nullable, Tuple, tuple;

import imageformats : write_image;

Nullable!uint escapeTime(uint limit)(Complex!double c) @nogc nothrow pure @safe
{
    auto z = complex(0.0, 0.0);
    foreach (i; 0 .. limit)
    {
        z = z * z + c;
        if ((z.re * z.re + z.im * z.im) > 4.0)
            return typeof(return)(i);
    }
    return (typeof(return)).init;
}

alias Pair(T) = Nullable!(Tuple!(T, T));

Pair!T parsePair(T, char separator)(string s) pure @safe if (__traits(isArithmetic, T))
{
    import std.algorithm : countUntil;

    immutable index = s.countUntil(separator);
    if (index == -1)
        return (typeof(return)).init;
    try
    {
        T l = s[0 .. index].to!T;
        T r = s[index + 1 .. $].to!T;
        return typeof(return)(tuple(l, r));
    }
    catch (ConvException) return (typeof(return)).init;
}

@safe unittest
{
    assert(parsePair!(int, ',')("").isNull);
    assert(parsePair!(int, ',')("10,").isNull);
    assert(parsePair!(int, ',')("10,20").get() == tuple(10, 20));
    assert(parsePair!(int, ',')("10,20xy").isNull);
    assert(parsePair!(double, 'x')("0.5x").isNull);
    assert(parsePair!(double, 'x')("0.5x1.5").get() == tuple(0.5, 1.5));
}

Nullable!(Complex!double) parseComplex(string s) pure @safe
{
    immutable pair = parsePair!(double, ',')(s);
    if (pair.isNull) return (typeof(return)).init;
    return typeof(return)(complex(pair[0], pair[1]));
}

@safe unittest
{
    assert(parseComplex("1.25,-0.0625").get() == complex(1.25, -0.0625));
    assert(parseComplex(",-0.0625").isNull);
}

Complex!double pixelToPoint(Tuple!(size_t, size_t) bounds,
                            Tuple!(size_t, size_t) pixel,
                            Complex!double upperLeft,
                            Complex!double lowerRight) @nogc pure nothrow @safe
{
    immutable width = lowerRight.re - upperLeft.re;
    immutable height = upperLeft.im - lowerRight.im;
    return complex(upperLeft.re + pixel[0] * width / bounds[0],
                   upperLeft.im - pixel[1] * height / bounds[1]);
}

@nogc @safe unittest
{
    assert(pixelToPoint(tuple!(size_t, size_t)(100, 100),
                        tuple!(size_t, size_t)(25, 75),
                        complex(-1.0, 1.0), complex(1.0, -1.0))
           == complex(-0.5, -0.5));
}

void render(ref ubyte[] pixels, Tuple!(size_t, size_t) bounds,
            Complex!double upperLeft, Complex!double lowerRight) @nogc @safe
in { assert(pixels.length == bounds[0] * bounds[1]); }
do
{
    foreach (row; 0 .. bounds[1])
    {
        foreach (column; 0 .. bounds[0])
        {
            immutable point = pixelToPoint(bounds, tuple(column, row),
                                           upperLeft, lowerRight);
            immutable count = escapeTime!(255)(point);
            pixels[row * bounds[0] + column] =
                count.isNull ? 0 : cast(ubyte)(255 - count.get()); /* ensure count.get <= 255 */
        }
    }
}

void writeImage(string filename, const ubyte[] pixels,
                Tuple!(size_t, size_t) bounds)
{
    write_image(filename, bounds[0], bounds[1], pixels);
}

version(unittest) { void main() {} }
else
{
void main(string[] args)
{
    if (args.length != 5)
    {
        stderr.writeln("Usage: mandelbrot FILE PIXELS UPPERLEFT LOWERRIGHT");
        stderr.writefln("Example %s mandel.png 1000x750 -1.20,0.35 -1,0.20",
                        args[0]);
        exit(1);
    }
    immutable bounds = parsePair!(size_t, 'x')(args[2]).get();
    immutable upperLeft = parseComplex(args[3]).get();
    immutable lowerRight = parseComplex(args[4]).get();
    auto pixels = new ubyte[bounds[0] * bounds[1]];

    auto bands = pixels.chunks(bounds[0]);
    foreach (top, band; parallel(bands))
    {
        auto bandBounds = tuple(cast() bounds[0], 1UL);
        immutable bandUpperLeft = pixelToPoint(bounds, tuple(0UL, top),
                                               upperLeft, lowerRight);
        immutable bandLowerRight = pixelToPoint(bounds,
                                                tuple(cast() bounds[0], top + 1UL),
                                                upperLeft, lowerRight);
        render(band, bandBounds, bandUpperLeft, bandLowerRight);
    }
    writeImage(args[1], pixels, bounds);
}
}

追記

imageformatsのnogc版のimagefmtというライブラリがあった。

https://github.com/lgvz/imagefmt

これを使ってちょっとリファクタリングしたコードもはっておく。

import core.stdc.stdlib : exit;
import std.complex;
import std.conv : to, ConvException;
import std.parallelism : parallel;
import std.range : chunks;
import std.stdio;
import std.typecons : Nullable, Tuple, tuple;

import imagefmt : write_image;

Nullable!uint escapeTime(uint limit)(Complex!double c) @nogc nothrow pure @safe
{
    auto z = complex(0.0, 0.0);
    foreach (i; 0 .. limit)
    {
        z = z * z + c;
        if ((z.re * z.re + z.im * z.im) > 4.0)
            return typeof(return)(i);
    }
    return (typeof(return)).init;
}

alias Pair(T) = Nullable!(Tuple!(T, T));

Pair!T parsePair(T, char separator)(string s) pure @safe if (__traits(isArithmetic, T))
{
    import std.algorithm : countUntil;

    immutable index = s.countUntil(separator);
    if (index == -1)
        return (typeof(return)).init;
    try
    {
        T l = s[0 .. index].to!T;
        T r = s[index + 1 .. $].to!T;
        return typeof(return)(tuple(l, r));
    }
    catch (ConvException) return (typeof(return)).init;
}

@safe unittest
{
    assert(parsePair!(int, ',')("").isNull);
    assert(parsePair!(int, ',')("10,").isNull);
    assert(parsePair!(int, ',')("10,20").get() == tuple(10, 20));
    assert(parsePair!(int, ',')("10,20xy").isNull);
    assert(parsePair!(double, 'x')("0.5x").isNull);
    assert(parsePair!(double, 'x')("0.5x1.5").get() == tuple(0.5, 1.5));
}

Nullable!(Complex!double) parseComplex(string s) pure @safe
{
    immutable pair = parsePair!(double, ',')(s);
    if (pair.isNull) return (typeof(return)).init;
    return typeof(return)(complex(pair[0], pair[1]));
}

@safe unittest
{
    assert(parseComplex("1.25,-0.0625").get() == complex(1.25, -0.0625));
    assert(parseComplex(",-0.0625").isNull);
}

Complex!double pixelToPoint(T)(Tuple!(T, T) bounds, Tuple!(T, T) pixel,
                               Complex!double upperLeft,
                               Complex!double lowerRight) @nogc pure nothrow @safe
{
    immutable width = lowerRight.re - upperLeft.re;
    immutable height = upperLeft.im - lowerRight.im;
    return complex(upperLeft.re + pixel[0] * width / bounds[0],
                   upperLeft.im - pixel[1] * height / bounds[1]);
}

@nogc @safe unittest
{
    assert(pixelToPoint!int(tuple(100, 100), tuple(25, 75),
                            complex(-1.0, 1.0), complex(1.0, -1.0))
           == complex(-0.5, -0.5));
}

void render(T)(ref ubyte[] pixels, Tuple!(T, T) bounds,
               Complex!double upperLeft, Complex!double lowerRight) @nogc @safe
in { assert(pixels.length == bounds[0] * bounds[1]); }
do
{
    foreach (row; 0 .. bounds[1])
    {
        foreach (column; 0 .. bounds[0])
        {
            immutable point = pixelToPoint!T(bounds, tuple(column, row),
                                             upperLeft, lowerRight);
            immutable count = escapeTime!(255)(point);
            pixels[row * bounds[0] + column] =
                count.isNull ? 0 : cast(ubyte)(255 - count.get()); /* ensure count.get <= 255 */
        }
    }
}

int writeImage(T : int)(string filename, const ubyte[] pixels, Tuple!(T, T) bounds) nothrow @nogc
{
    return write_image(filename, bounds[0].to!int, bounds[1].to!int, pixels);
}

version(unittest) { void main() {} }
else
{
int main(string[] args)
{
    if (args.length != 5)
    {
        stderr.writeln("Usage: mandelbrot FILE PIXELS UPPERLEFT LOWERRIGHT");
        stderr.writefln("Example %s mandel.png 1000x750 -1.20,0.35 -1,0.20",
                        args[0]);
        exit(1);
    }
    immutable bounds = parsePair!(int, 'x')(args[2]).get();
    immutable upperLeft = parseComplex(args[3]).get();
    immutable lowerRight = parseComplex(args[4]).get();
    auto pixels = new ubyte[bounds[0] * bounds[1]];

    auto bands = pixels.chunks(bounds[0]);
    foreach (top, band; parallel(bands))
    {
        auto bandBounds = tuple(cast() bounds[0], 1);
        immutable bandUpperLeft = pixelToPoint(bounds, tuple(0, top.to!int),
                                               upperLeft, lowerRight);
        immutable bandLowerRight = pixelToPoint(bounds,
                                                tuple(cast() bounds[0], top.to!int + 1),
                                                upperLeft, lowerRight);
        render(band, bandBounds, bandUpperLeft, bandLowerRight);
    }
    return writeImage(args[1], pixels, bounds);
}
}

zshを自前でビルドして使う

zshにパッチあてて使ってるんだけど、ビルド方法とか一応メモしておく。

zshのビルドは楽にできる、まあ今回の作業では必要なかったのだけれど。

$ apt install -y yodl # 依存ライブラリ, あとPerlとかも必要
$ ./Util/preconfig
$ ./configure
$ make

パッチ自体は特にとりあげるほどの内容ではないので割愛。

補完にパッチをあてた場合は、fpathを上書きしてそっちを優先的にみるようにする必要がある。 設定ファイルは邪魔なので読み込まないようにした。

$ zsh -d -f # no-rcs && no-global_rcs
$ fpath=("$(pwd)"/Completion/Unix/Command "${fpath[@]}")
$ autoload -Uz compinit
$ compinit -C
$ nm --demangle= # 補完で `dlang' が出てくるパッチの確認

一応パッチ自体はPR出した。マージされるかわからないけど, わりとすんなり修正できるのでみなさんもzshにパッチ投げてみるといいかもしれません。 (そのあとちゃんとマージされたのでコントリビュッタです)

jailingをD言語にポーティング、やってみた!

kazuho/jailingD言語に置き換えてみました。

これです。

目的

実用的にはLinuxであればどの環境でもPerl5が入っていることは期待できるし外部依存もないので特に意義はないです。 あくまで学習目的というやつです。コードサイズも小さいし、いいかなって。

jailing

説明はここを読んで。

もう少しカスタムできるツールだとfirejailだとかnsjailとかあるけど、このくらいの分量だと学習しやすくてありがたいです。 というかこれだけで立派なchroot+jailになるのいいですね。

D言語

Q: なんでD言語なの?
A: 言わせんな恥ずかしい

書いてみての雑感。

  • わりと標準ライブラリでなんとかなるところがよい?でもけっきょく executeShell ばっかりになるもんなあという気もしないではない。
  • あとはなんか std.path.buildPath なんかはきっちり書きたい感出せる気がする。
  • あ、FFIextern (C) だけすればなんとかなるのでだいぶよい。

D言語で書けばバイナリを配るだけ作戦が使える、のだけど元がPerlPythonスクリプトの場合だと書き換え自体がメリットになりにくい。 jailingのようなツールだとそんなに速度とかいらなさそうだし、どうせプロセス起動が遅いのでさほど変わらんだろう。 Rubyとかだったらスクリプトと別に処理系入れるの面倒じゃない?みたいなところに対してメリットが言えそうなんだけど。 なおこの作戦はGoやRustでも別によいという話もある。

jl

だいたい似たような動機のプロジェクトで yoshitsugu/jl というRustのやつがありました。

Rustだと caps とか libmount とかライブラリがあったりして外部コマンドを使わないで実装できるのはいい感じがします。実行時のメモリフットプリントとかにうれしい。 あと clap みたいなコマンドラインパーサがあるとCLI書くとき便利。 ここでは使われていないけど他にもstructoptとかも便利系のやつです。

ところでjlはコマンド実行も外部プロセスにしてしまってるけどここは CommandExtexec 関数を使うべきでは。