Open13

めも:Rust の練習帳

nukopynukopy

リポジトリ

https://github.com/nukopy/rust-renshu-cho

環境

macOS 組み込みの Bash(/bin/bash)、ではなくミラーリポジトリのマイナーバージョンまで合致している Bash をインストールした。

https://github.com/bminor/bash/releases/tag/bash-5.2

$  /bin/bash --version
GNU bash, version 3.2.57(1)-release (arm64-apple-darwin23)
Copyright (C) 2007 Free Software Foundation, Inc.

$ /opt/homebrew/bin/bash --version
GNU bash, version 5.2.26(1)-release (aarch64-apple-darwin23.2.0)
Copyright (C) 2022 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>

This is free software; you are free to change and redistribute it.

参考

nukopynukopy

後で調べる

  • #[derive(Debug)] などの属性ってどういう仕組み?
  • Rust のテストランナーの挙動
    • #[test] ってどういう仕組みだっけ?テスト対象の関数をどうやって見つけて(テストディスカバリ)どうやって実行してるんだっけ?
    • テストの並列化はどうやってやるの?
  • which コマンドで、ビルトインコマンドと外部コマンドを引数に与えた時の挙動の違いはどうな
nukopynukopy

ビルトインコマンドと外部コマンド

by ChatGPT

ビルトインコマンド以外の、brew(macOSのHomebrew)などの外部コマンドのことは一般的に「外部コマンド」または「ユーティリティ」と呼びます。

ビルトインコマンドと外部コマンドの違い

  • ビルトインコマンド:
    • シェル(例:Bash、Zsh、Fishなど)自体に組み込まれているコマンドです。
    • 例:cd, echo, alias, export, history など。
  • 外部コマンド:
    • シェルに組み込まれていない、外部のプログラムやスクリプトとして実行されるコマンドです。
    • シェルが実行ファイルを検索して見つけ、子プロセスとして実行します。
    • 例:brew, git, curl, ls, grep など。

  • brew は Homebrew のパッケージマネージャとして外部コマンドの例です。Homebrew自体はシェルの一部ではなく、独立したプログラムです。
  • ls はファイルリストを表示するUnix系のコマンドで、これも外部コマンドです。

使用例

シェルを使用する際には、ビルトインコマンドと外部コマンドの区別が重要です。例えば、ビルトインのechoコマンドはシェルが直接処理しますが、brewコマンドはシェルがbrewという実行ファイルを検索し、それを実行します。

まとめ

Homebrewのbrewなど、シェルに組み込まれていないコマンドを指す場合は「外部コマンド」と呼びます。外部コマンドはシェルがファイルシステム上で実行ファイルを検索し、それを子プロセスとして実行するものです。

which コマンドの挙動

  • ビルトインコマンド
    • シェルに組み込まれているため、特定のディレクトリには配置されない。シェルの内部で直接利用される。
  • 外部コマンド
    • システムの基本的なユーティリティとして提供されるため、通常は /bin, /usr/bin, /usr/local/bin などのディレクトリに配置される。
bash-5.2$ which echo
/bin/echo
bash-5.2$ which ls
/bin/ls
bash-5.2$ which brew
/opt/homebrew/bin/brew
bash-5.2$ type echo
echo is a shell builtin
bash-5.2$ type ls
ls is /bin/ls
bash-5.2$ type brew
brew is /opt/homebrew/bin/brew

type コマンドとは

type コマンドは、指定したコマンドがビルトインコマンド、外部コマンド、エイリアス、シェル関数など、引数で与えられたコマンドがどのような種類のコマンドであるかを表示するために使用されるコマンド**。type コマンドは、シェルが指定されたコマンドをどのように解釈するかを確認するのに役立つ。

  • ビルトインコマンドの場合
bash-5.2$ type echo
echo is a shell builtin
  • 外部コマンドの場合
bash-5.2$ type ls
ls is /bin/ls
bash-5.2$ type brew
brew is /opt/homebrew/bin/brew

-a オプションを使用すると、システム内の同じ名前のコマンドを全て表示する。このとき、ビルトインと外部コマンドの両方を含む。

例えば、echo コマンドは、Bash などのシェルではビルトインコマンドとしても存在するが、システムの基本的なユーティリティとして外部コマンド(例えば /bin/echo)としても存在するため、2 つ表示される。一方、ls コマンドは、システムの基本的なユーティリティとしての外部コマンドのみ存在し、Bash には組み込まれていないため、外部コマンドの表示のみになる。

bash-5.2$ type -a echo
echo is a shell builtin
echo is /bin/echo

bash-5.2$ type -a ls
ls is /bin/ls

ちなみにシステム内にコマンドが見つけられないと not found を返す。

bash-5.2$ type lss
bash: type: lss: not found

echo コマンドについて、which echo の結果と type echo の結果が矛盾してない?which コマンドの結果は外部コマンドだけど、type コマンドの結果はビルトインコマンドだと示しているよ。

なるほど、自分の理解が足りなかった。which コマンドはそもそも PATH を見て外部コマンドを検索するためのコマンドだったのか。PATH がそもそも外部コマンドを検索するための環境変数だったのか。ただ「コマンド」としてしか理解できてなかった。

  • コマンド
    • ビルトインコマンド
    • 外部コマンド
    • シェル関数
    • alias

ご指摘の通り、echo コマンドについて which echotype echo の結果が異なることがあります。これは which コマンドと type コマンドがコマンドの検索方法や表示方法が異なるためです。

which コマンド

which コマンドは、指定されたコマンドがシステムのどこにあるかを表示します。具体的には、環境変数 PATH に設定されたディレクトリを順に検索し、最初に見つかった実行可能ファイルのパスを表示します。which コマンドは外部コマンドの検索に特化しています。

$ which echo
/bin/echo

この結果は、/bin/echo という外部コマンドが存在することを示しています。

type コマンド

type コマンドは、指定されたコマンドがビルトインコマンド、外部コマンド、エイリアス、シェル関数のいずれであるかを表示します。type コマンドは、シェルが実際にどのようにそのコマンドを解釈するかを示します。

$ type echo
echo is a shell builtin

この結果は、シェルが echo をビルトインコマンドとして扱っていることを示しています。

なぜ結果が異なるのか

  • which コマンド:

    • 外部コマンドを探してそのパスを表示します。
    • echo のようなコマンドは、外部コマンドとして存在する場合、そのパスを表示します。
  • type コマンド:

    • コマンドがビルトインコマンド、エイリアス、シェル関数、または外部コマンドのどれであるかを表示します。
    • ビルトインコマンドが存在する場合、シェルはそれを優先的に使用するため、type echoecho is a shell builtin と表示します。

具体的な例

$ which echo
/bin/echo

$ type echo
echo is a shell builtin

この例では、シェルの echo コマンドはビルトインコマンドとして定義されていますが、/bin/echo という外部コマンドも存在します。シェルはビルトインコマンドを優先するため、実際にはビルトインの echo が使用されます。

まとめ

whichtype の結果が異なる理由は、which が外部コマンドのみを検索するのに対し、type がシェルの内部でどのようにコマンドが定義されているかを示すためです。シェルがビルトインコマンドを持つ場合、type はそれを示しますが、which は外部コマンドのパスを表示します。この違いを理解することで、シェルの動作を正確に把握することができます。

では、名前解決はどのように行われる?

名前解決の順序としては、ビルトインコマンドが優先される。

  1. ビルトインコマンドを探す。見つかれば実行する。
  2. ビルトインコマンドが存在しなければ、環境変数 PATH を参照し、一番はじめに見つかった外部コマンドを実行する。
nukopynukopy

せっかくなので Bash のソースコードからコマンドの名前解決するところを見てみる

おそらく以下のような感じになってるはず。

  1. 入力を受け取る
  2. 入力をパースする
  3. コマンドを実行する
    1. ビルトインコマンドを探す。見つかれば実行する。
    2. ビルトインコマンドが存在しなければ、環境変数 PATH を参照し、一番はじめに見つかった外部コマンドを実行する。

ソースコードのビルド

https://github.com/bminor/bash/blob/master/NOTES

gh repo clone ...
cd ./bash

./configure
#...
# configure: creating ./config.status
# config.status: creating Makefile
# config.status: creating builtins/Makefile
# config.status: creating lib/readline/Makefile
# config.status: creating lib/glob/Makefile
# config.status: creating lib/intl/Makefile
# config.status: creating lib/malloc/Makefile
# config.status: creating lib/sh/Makefile
# config.status: creating lib/termcap/Makefile
# config.status: creating lib/tilde/Makefile
# config.status: creating doc/Makefile
# config.status: creating support/Makefile
# config.status: creating po/Makefile.in
# config.status: creating examples/loadables/Makefile
# config.status: creating examples/loadables/Makefile.inc
# config.status: creating examples/loadables/Makefile.sample
# config.status: creating examples/loadables/perl/Makefile
# config.status: creating support/bash.pc
# config.status: creating support/bashbug.sh
# config.status: creating config.h
# config.status: executing po-directories commands
# config.status: creating po/POTFILES
# config.status: creating po/Makefile
# config.status: executing stamp-h commands

とりあえず Makefile が生成された。あとは make するだけ。その前に Makefile の構造をざっと眺める。

  • エントリーポイント:all: .made
# Make sure the first target in the makefile is the right one
all: .made
  • .made
.made: $(Program) bashbug $(SDIR)/man2html$(EXEEXT)
	@echo "$(Program) last made for a $(Machine) running $(OS)" >.made

$(Program): $(OBJECTS) $(BUILTINS_DEP) $(LIBDEP) .build
	$(RM) $@
	$(PURIFY) $(CC) $(BUILTINS_LDFLAGS) $(LIBRARY_LDFLAGS) $(LDFLAGS) -o $(Program) $(OBJECTS) $(LIBS)
	ls -l $(Program)
	-$(SIZE) $(Program)

make コマンドを実行すると、all ターゲットから .made ターゲットが呼び出され、$(Program) ターゲットがビルドされる。$(Program) ターゲットのビルドが完了すると、.made ファイルが生成される。

make を実行してみる。

make &> log.txt

ログを見ると、最後の方に bash をビルドし、man ページを生成しているのがわかる。

gcc -L./builtins -L./lib/readline -L./lib/readline -L./lib/glob -L./lib/tilde  -L./lib/sh   -g -O2   -o bash shell.o eval.o y.tab.o general.o make_cmd.o print_cmd.o  dispose_cmd.o execute_cmd.o variables.o copy_cmd.o error.o expr.o flags.o jobs.o subst.o hashcmd.o hashlib.o mailcheck.o trap.o input.o unwind_prot.o pathexp.o sig.o test.o version.o alias.o array.o arrayfunc.o assoc.o braces.o bracecomp.o bashhist.o bashline.o  list.o stringlib.o locale.o findcmd.o redir.o pcomplete.o pcomplib.o syntax.o xmalloc.o  -lbuiltins -lglob -lsh -lreadline -lhistory -ltermcap -ltilde  lib/intl/libintl.a -liconv  -Wl,-framework -Wl,CoreFoundation -liconv  -ldl 
# ld: warning: ignoring duplicate libraries: '-liconv'
ls -l bash
# -rwxr-xr-x  1 nukopy  staff  1345288 May 29 05:37 bash
size bash
# __TEXT	__DATA	__OBJC	others	dec	hex
# 901120	81920	0	4295393280	4296376320	100158000
rm -f man2html.o
gcc -c  -DHAVE_CONFIG_H -DSHELL  -I/Users/nukopy/Projects/OSS/bash -I.. -Wno-parentheses -Wno-format-security -DMACOSX -I/opt/homebrew/opt/openjdk@17/include -g -O2 man2html.c
gcc   -g -O2 man2html.o -o man2html -ldl 		

ビルドした bash のバージョンを確認し、起動した。できた。

$ ./bash --version
GNU bash, version 5.2.26(1)-release (aarch64-apple-darwin23.4.0)
Copyright (C) 2022 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>

This is free software; you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

$ ./bash
bash-5.2$
nukopynukopy

echo コマンドの名前解決の仕組みをソースコードで見てみる

おそらく以下のような感じになってるはず。echo ならビルトインコマンドが実行され、ls なら外部コマンドが実行されることを確認したい。

  1. Bash の起動
  2. 対話モード(REPL)の起動 → 3 ~ 5 のループ
  3. 入力を受け取る
  4. 入力をパースする
  5. コマンドを実行する
    1. ビルトインコマンドを探す。見つかれば実行する。
    2. ビルトインコマンドが存在しなければ、環境変数 PATH を参照し、一番はじめに見つかった外部コマンドを実行する。

エントリーポイント:shell.c

shell.cmain 関数が Bash のエントリーポイント。

  • やってること

    • コマンドライン引数の解析
    • 起動ファイルの読み込み
    • 対話モード初期化
    • 対話モード開始
  • shell.c: main

https://github.com/bminor/bash/blob/f3b6bd19457e260b65d11f2712ec3da56cef463f/shell.c#L362-L835

main 関数内の reader_loop 関数を実行しているところ(L833)が対話モードを開始しているところ。

https://github.com/bminor/bash/blob/f3b6bd19457e260b65d11f2712ec3da56cef463f/shell.c#L832-L835

nukopynukopy

対話モードにおけるコマンドの解釈: eval.creader_loop() 関数

見たいのはコマンドの名前解決部分なので、reader_loop() 関数の中身を見ていく。reader_loop 関数は eval.c に定義されている。eval.c は、ソースコード 1 行目のコメントにもあるように、コマンドを読み、評価するモジュールである。

https://github.com/bminor/bash/blob/f3b6bd19457e260b65d11f2712ec3da56cef463f/eval.c#L1

  • eval.c: reader_loop

https://github.com/bminor/bash/blob/f3b6bd19457e260b65d11f2712ec3da56cef463f/eval.c#L54-L194

reader_loop関数は、シェルの対話モードにおける主要な動作を制御している。この関数は、ユーザーからのコマンド入力を受け付け、それをパース(解析)し、実行し、そして再び入力を受け付ける、という一連の流れを while ループで繰り返す。

以下に、その主要なステップを抽出する:

  1. コマンド入力の受け付け

    • このステップは、reader_loop 関数の while (EOF_Reached == 0) ループが始まるところから始まる。このループは、EOF(End Of File、つまり入力の終わり)が検出されるまで繰り返される。つまり、ユーザからの新しいコマンド入力を待ち続ける。
  2. コマンドのパース(解析)

    • このステップは、read_command() 関数の呼び出しによって行われる。この関数は、ユーザーからの入力を読み込み、それをパース(解析)してコマンドとしての形式に変換する。パースされたコマンドは、current_command = global_command 行によって current_command 変数に格納される。
  3. コマンドの実行

    • このステップは、execute_command(current_command) 関数の呼び出しによって行われる。この関数は、パースされたコマンド(current_command 変数に格納されている)を実行する。
  4. コマンドの実行結果の処理 & 次の入力の準備

    • execute_command 関数の実行結果(つまり、実行されたコマンドの終了ステータス)は、last_command_exit_valueというグローバル変数に格納される。
    • そして再度入力受け付けモードに移る前に、QUIT マクロが呼び出される。このマクロは、シェルが終了するべきかどうかをチェックする。具体的には、last_command_exit_value が非ゼロ(つまり、直前のコマンドがエラーで終了した)場合、シェルは終了する。
    • その後、dispose_command関数が呼び出されてcurrent_commandが解放され、再び入力を受け付ける状態に戻る。
    execute_command (current_command);
    
    exec_done:
    QUIT;
    
    if (current_command)
    {
      dispose_command (current_command);
      current_command = (COMMAND *)NULL;
    }
    ...
    
    • このコードは、execute_commandの実行結果を処理し、次のコマンド入力を待つための準備を行っている。
  5. 再び入力受付モードへ

    • コマンドの実行が終わると、reader_loop関数は再びwhile (EOF_Reached == 0)ループの先頭に戻り、新たなコマンド入力の受け付けを開始する。

以上が、reader_loop関数における主要なステップである。この関数は、シェルの対話モードにおける基本的な動作を制御する。

nukopynukopy

Q. あれ、でも終了ステータスがエラーでもシェルの対話モードって終わらなくない?ここでの last_command_exit_value っていうのは終了ステータス(POSIX なら 0 が成功、1 ~ 255 がエラー)とは違うの?

last_command_exit_valueは、最後に実行されたコマンドの終了ステータスを保持します。これはPOSIXの規定に従い、0が成功を示し、1から255までの値がエラーを示します。

シェルの対話モードでは、コマンドがエラーで終了したとしてもシェル自体は終了しません。しかし、シェルスクリプトの中でset -eオプションが設定されている場合(これはerrexitオプションとも呼ばれます)、エラーで終了したコマンドが実行されるとシェルスクリプト全体が終了します。

したがって、last_command_exit_valueが非ゼロ(エラー)である場合でも、シェルの対話モードは終了しません。ただし、シェルスクリプト内でエラーが発生した場合、その挙動はスクリプトがset -eを設定しているかどうかによります。

たしかに以下のシェルスクリプトを実行したらシェルが終了した。

tmp.bash
set -e
lss # 存在しないコマンド

Q. そっか対話モードだけでなく、単純にシェルスクリプトのインタプリタとして動作するモードもあるのか。

はい、その通りです。シェルは対話モードだけでなく、シェルスクリプトのインタプリタとしても動作します。対話モードでは、ユーザーからの入力を受け付けてコマンドを実行します。一方、シェルスクリプトのインタプリタとして動作する場合、シェルスクリプトファイルを読み込み、その中に書かれたコマンドを順に実行します。

具体的には、シェルを起動する際にシェルスクリプトファイルを引数として渡すと、シェルはそのファイルを読み込み、ファイル内のコマンドを順に実行します。このとき、シェルは対話モードではなく、非対話モードで動作します。

例えば、次のようにシェルを起動すると、script.shというシェルスクリプトを実行します。

bash script.sh

このscript.shファイル内には、実行したいコマンドが順番に書かれています。シェルはこれらのコマンドを上から順に実行し、スクリプトの最後まで到達したら終了します。

コードよくみると、reader_loop 内でインタラクティブシェルかどうかのフラグで処理を分岐している部分があるね。

nukopynukopy

std::process::Command

https://doc.rust-lang.org/std/process/struct.Command.html

Command.new("ls") という初期かメソッドを実行したときにはコマンドは実行されず、Command.output()Command.status()Command.spawn() が実行されたときにコマンドが実行される。

std::process::Command.output() の中身

※UNIX 系 OS、特に macOS 前提で進める

  • std::process::Command.output()(共通の実装 process.rs
    • std::process::Command.inner.output()(共通の実装 process.rs
      • std::process_unix::Command.output()(ここで各 OS の実装に飛ぶ。process_unix.rs
        • std::process_unix::Command.spawn()

std::process_unix::Command.spawn() の中で何が行われているか?

  1. 親プロセス(std::process::Command::new("some cmd") を呼んだプロセス)、子プロセス("some cmd" を実行するために fork で生成されるプロセス)の標準入出力のオブジェクトを取得する:std::process_common::Command.setup_io()
    • 子プロセスのコマンドを実行するとき、コマンドの結果を取得するためにここで取得した子プロセスの標準入出力を使用する
  2. プロセスのフォーク:std::process_unix::Command.do_fork()(中で libc::fork() を呼んでいる)
    impl Command {
        unsafe fn do_fork(&mut self) -> Result<pid_t, io::Error> {
           cvt(libc::fork())
        }
    
  3. jjj
  4. j
nukopynukopy

std::process::ExitCode と std::process::ExitStatus の違い

どちらもプロセスの終了状態を扱うための API。以下、公式ドキュメントより引用:

Differences from ExitStatus

ExitCode is intended for terminating the currently running process, via the Termination trait, in contrast to ExitStatus, which represents the termination of a child process. These APIs are separate due to platform compatibility differences and their expected usage; it is not generally possible to exactly reproduce an ExitStatus from a child for the current process after the fact.

引用元:https://doc.rust-lang.org/std/process/struct.ExitCode.html#differences-from-exitstatus

ExitStatusとの違い

ExitCodeは、Termination トレイトを介して現在実行中のプロセスを終了させることを目的としています。これに対して、ExitStatus は子プロセスの終了状態を表します。これらの API はプラットフォーム互換性の違いや使用目的の違いから分けられています。現在のプロセスの終了状態として子プロセスの ExitStatusを正確に再現することは一般的にできません。

日本語訳 by ChatGPT 4o

現在のプロセスの終了状態として子プロセスの ExitStatusを正確に再現することは一般的にできません。

ここがまだ理解できない。

std::process::ExitCode

https://doc.rust-lang.org/std/process/struct.ExitCode.html

ExitCode は、現在実行中のプロセスの終了コードを表す。これは、Rust プログラムが終了するときに特定の終了コードを返すために使用される。

  • 目的:現在実行中のプロセスを終了させるためのコードを表す
  • 使用例std::process::exit 関数や Termination トレイトを実装する際に使用される

例:

use std::process::ExitCode;

fn main() -> ExitCode {
    // プロセスの終了コードとして 1 を返す
    ExitCode::from(1)
}

std::process::ExitStatus

https://doc.rust-lang.org/std/process/struct.ExitStatus.html

ExitStatus は、(現在実行中の親プロセスの)子プロセスの終了状態を表す。これは、Rust プログラムが子プロセスを生成し、そのプロセスが終了したときの状態を取得するために使用される。

  • 目的:子プロセスの終了状態を表す
  • 使用例std::process::Command を使って子プロセスを生成し、そのプロセスが終了した後の状態を取得するために使用される

例:

use std::process::Command;

fn main() {
    let status = Command::new("ls")
                         .status()
                         .expect("failed to execute process");

    if status.success() {
        println!("Process exited successfully");
    } else {
        println!("Process exited with error code: {:?}", status.code());
    }
}

違いの詳細

公式ドキュメントの記述にある通り、ExitCodeExitStatus には以下のような違いがある。

  1. 使用目的の違い
    • ExitCode は、現在実行中のプロセスを終了させるために使用される。主に、Rust プログラム自体が終了コードを返す場合に使用される。
    • ExitStatusは、子プロセスの終了状態を表す。これは、親プロセスが子プロセスを生成し、その終了状態を取得して処理するために使用される。
  2. API の分離
    • これらの API は、プラットフォーム互換性の違いや使用方法の違いにより分離されている。現在のプロセスの終了コードを子プロセスの終了コードとして再現することは一般的にできない。例えば、システムによっては、現在のプロセスの終了コードを直接取得する方法が異なるためである。
  3. 実装の違い
    • ExitCode は、現在のプロセスの終了に特化しており、Rust のTerminationトレイトを介して使用される
    • ExitStatus は、子プロセスの終了状態を詳細に扱うために設計されており、標準ライブラリの Command 構造体を使用して生成されたプロセスの終了状態を取得する

まとめ

  • ExitCode:現在のプロセスの終了コードを表し、プログラムの終了時に使用される
  • ExitStatus:子プロセスの終了状態を表し、親プロセスが子プロセスの終了状態を取得して処理するために使用される

これらの API は異なる目的で使用され、プラットフォーム互換性や使用方法の違いから明確に分けられている。

nukopynukopy

Q

この文章もうちょっとわかりやすく解説して。

現在のプロセスの終了状態として子プロセスのExitStatusを正確に再現することは一般的にできません。

A

以下 ChatGPT の解答


子プロセスの ExitStatus を、現在のプロセスの終了状態として正確に再現することができない理由は、いくつかの技術的な制約とプラットフォーム依存の違いに起因します。以下にその主要な理由を挙げます。

プラットフォーム依存の違い

  • 終了状態の情報の違い:
    • 子プロセスが終了すると、その終了状態は親プロセスに通知されますが、終了状態にはプラットフォームごとに異なる情報が含まれることがあります。例えば、Unix系システムではプロセスの終了コードに加えて、シグナルによる終了情報も含まれますが、Windowsでは異なるメカニズムが使われます。
    • これにより、子プロセスの ExitStatus を現在のプロセスの終了状態として正確に再現することが難しくなります。

現在のプロセスの終了状態の制御

  • 直接的な終了制御:

    • ExitCode は現在のプロセスが終了する際に設定するものです。現在のプロセスの終了状態を設定するには、通常は std::process::exit を使います。この関数はプロセスの終了コードを直接設定しますが、子プロセスの ExitStatus に含まれる詳細な情報(例えば、シグナル情報など)をそのまま反映することはできません。
  • プロセス管理の違い:

    • Rustでは、現在のプロセスと子プロセスの終了管理には異なるAPIが提供されています。ExitCode は現在のプロセス専用であり、ExitStatus は子プロセス専用です。これらのAPIは、プラットフォームの互換性や使用目的に応じて分けられており、現在のプロセスが終了する際に子プロセスの終了情報を正確に模倣することは設計上想定されていません。

実装の例

現在のプロセスの終了(ExitCode

use std::process::ExitCode;

fn main() -> ExitCode {
    // 現在のプロセスを終了コード1で終了
    ExitCode::from(1)
}

子プロセスの終了(ExitStatus

use std::process::Command;

fn main() {
    let status = Command::new("ls")
                         .status()
                         .expect("failed to execute process");

    if status.success() {
        println!("Process exited successfully");
    } else {
        println!("Process exited with error code: {:?}", status.code());
    }
}

まとめ

  • プラットフォーム依存の違い: プロセスの終了状態にはプラットフォームごとに異なる情報が含まれるため、子プロセスの ExitStatus を現在のプロセスの終了状態として正確に再現することは難しいです。
  • 現在のプロセスと子プロセスの終了制御の違い: ExitCodeExitStatus はそれぞれ異なる目的と制御メカニズムを持つため、設計上これらを混同して使用することはできません。

これらの理由から、子プロセスの ExitStatus をそのまま現在のプロセスの終了状態として再現することは一般的にできないのです。

nukopynukopy

なるほど、ExitCode では ExitStatus は再現できないよってことか。


はい、その通りです。ExitCodeではExitStatusを再現できない理由は以下の通りです。

詳細な説明

  • ExitCode: 現在のプロセスを終了させるためのコードです。これは、Rustプログラムが終了するときに特定の終了コードを返すために使用されます。シンプルに終了コードを設定するためのAPIです。

  • ExitStatus: 子プロセスが終了したときの詳細な終了状態を表します。これは、終了コードに加えて、例えばシグナルによる終了などの詳細な情報も含むことができます。ExitStatusは、子プロセスの終了状態を詳細に知るためのAPIです。

再現の難しさ

  • プラットフォーム固有の情報: ExitStatusにはプラットフォーム固有の詳細情報が含まれています。例えば、Unix系システムではシグナルによる終了情報などが含まれますが、これを現在のプロセスの終了状態として再現することは難しいです。

  • APIの目的の違い: ExitCodeは現在のプロセスの終了を簡潔に表すためのものであり、ExitStatusのような詳細な情報を再現することを目的としていません。これらのAPIは異なる目的で設計されているため、ExitCodeExitStatusの詳細な情報を再現することはできません。

公式ドキュメントの要約

公式ドキュメントの記述を要約すると、以下のようになります。

  • 違い: ExitCodeは現在のプロセスの終了を表すためのものであり、ExitStatusは子プロセスの終了状態を表すものです。
  • 分離の理由: これらのAPIは、プラットフォームの互換性の違いや使用目的の違いにより分けられています。
  • 再現の困難さ: 一般的に、子プロセスのExitStatusを現在のプロセスの終了状態として後から再現することはできません。

具体的な例

以下は具体的な例です。

use std::process::{Command, ExitCode};

// 現在のプロセスの終了コードを設定
fn main() -> ExitCode {
    // ここでは終了コード1を設定
    ExitCode::from(1)
}

// 子プロセスの終了状態を取得
fn main() {
    let status = Command::new("ls")
                         .status()
                         .expect("failed to execute process");

    if status.success() {
        println!("Process exited successfully");
    } else {
        println!("Process exited with error code: {:?}", status.code());
    }
}

このように、ExitCodeExitStatusは異なる目的で使用され、それぞれが異なる役割を果たしています。