本文へ移動
Hikari 仕様

std:path — パス文字列操作

標準ライブラリモジュール (index.md)。本書中の裸の §N は本書の節を指す。言語の意味論は ../language-spec.md を参照。

import path := "std:path"join / dir / base / ext / stem / normalize / is_absolute / split の 8 slot を持つ namespace object を path に束縛する。パス文字列の字句操作 (連結・分解・正規化) を担う。

std:fs / std:term と違い I/O を伴わず、純粋・同期 (Future を返さない)。すべて String 上の純粋関数で、実際のファイルアクセスは呼び出し側が std:fs 等で行う。パスの存在確認やディレクトリ判定はせず、与えられた文字列だけを見て答える (字句的)。

import { join, dir, base, ext } := "std:path"

join(["data", "2026-06-19", "note.md"])   #> "data/2026-06-19/note.md"
dir("data/2026-06-19/note.md")             #> "data/2026-06-19"
base("data/2026-06-19/note.md")            #> "note.md"
ext("note.md")                             #> ".md"
名前 意味
join join(parts) parts (String の List) をパス区切りで連結し、正規化した String を返す
dir dir(path) path から末尾要素を除いた親ディレクトリ (正規化済み) の String を返す
base base(path) path の末尾要素 (basename) の String を返す
ext ext(path) path の末尾要素の拡張子 (先頭ドット込み) の String を返す。無ければ ""
stem stem(path) base(path) から ext(path) を除いた部分の String を返す
normalize normalize(path) path の冗長なパス区切り・. / .. をまとめた String を返す
is_absolute is_absolute(path) path が絶対パスなら true、相対なら false (Bool) を返す
split split(path) {dir, base} の closed・immutable なレコードを返す

本モジュールは効果を1 つも持たない../language-spec.md §17.9)。

以下の例では import path := "std:path" で束縛したものとして path.join 等で記す。全関数は値を 直接返す (Future でも Result でもない)。非 String 引数・join への非 List / 非 String 要素は回復可能エラーではなく、呼び出し位置で Error (panic 型, language-spec.md §16) になる (§9)。

1. パス区切りと正規化

パス区切りとは 1 本のパスの中で要素を分ける記号をいう。PATH 環境変数のように複数のパスを 1 つの文字列へ並べるときの境目 (デリミター) とは別のものである。

  • パス区切りは OS native — POSIX 系では /、Windows では \。どちらになるかは処理系が動いているホストで決まり、実行時に選べる口は無い。std:fs がパスの解釈をホストへ委ねるのと同じ向きである (fs.md §12)
  • 「正規化」とは冗長なパス区切りの畳み込み (a//ba/b)、. の除去、.. の親方向への適用、末尾パス区切りの除去をいう。.. は根より上へは出ない — 根を持つパスでは根で止まり (/../a/a)、根を持たないパスでは .. がそのまま残る (a/../....)
  • すべて 字句的 に行う。FS にアクセスしないので、シンボリックリンクの解決や存在確認はしない
  • 入力 String は OS native なパス区切りで書かれているものとみなす。パス区切りを含まない文字列は単一要素のパスとして扱う

1.1 Windows のパス区切りとボリューム名

Windows ホストでは次の 2 点が POSIX 系と違う。以降の各節の例は POSIX 系のホストのものである。

  • 入力は \/ の両方をパス区切りとして受け、出力は \ に揃える
  • パスの先頭に ボリューム名が付きうる。形は 2 つで、ドライブ (C: — ラテン 1 文字と :。大小どちらも受ける) と UNC 共有 (\\server\share)。正規化はボリューム名をそのまま保ち (大小もまとめない)、.. はボリューム名より上へ出ない

ボリューム名は末尾要素の取り出しにも効く。base はボリューム名を末尾要素とみなさないので、C:\C: も末尾要素はパス区切り 1 文字 (\) である。絶対かどうかもボリューム名で決まる (§8)。

次の表は Windows ホストでの答えである。綴りは実際のパスで書いてある — Hikari のソースに書くときは \\\ とエスケープする (language-spec.md §7.2)。

綴り normalize dir base is_absolute
C:\a\b C:\a\b C:\a b true
C:/a/b C:\a\b C:\a b true
C:\a\..\b C:\b C:\ b true
C:\..\a C:\a C:\ a true
C:\ C:\ C:\ \ true
C: C:. C:. \ false
C:a\b C:a\b C:a b false
C:a\..\.. C:.. C:. .. false
\\server\share\a \\server\share\a \\server\share\ a true
\\server\share \\server\share \\server\share \ true
\a\b \a\b \a b false
a\b a\b a b false

C:\..\aC:\a になるのは §1 の「.. は根より上へは出ない」がボリューム名にも効くからで、根を持たない C:a\..\.. では POSIX 系と同じく .. が残る。

2. path.join

  • 引数: parts (String の List) 1 個。非 List、または List 要素に非 String を含むと呼び出し位置で Error (§10)
  • parts の各要素をパス区切りで連結し、結果を 正規化 した String を返す
  • 空文字列の要素は無視する。すべて空または parts が空 List [] のときは "" を返す
path.join(["a", "b", "c"])      #> "a/b/c"
path.join(["a/", "/b"])         #> "a/b"      (冗長なパス区切りをまとめる)
path.join(["a", "", "b"])       #> "a/b"      (空要素を無視)
path.join(["a", "..", "b"])     #> "b"        (.. を字句的に適用)
path.join([])                   #> ""

3. path.dir

  • 引数: path (String) 1 個。非 String は呼び出し位置で Error (§10)
  • path から末尾要素を除いた親ディレクトリを 正規化して 返す
  • 末尾のパス区切りは無視して末尾要素を決める
path.dir("a/b/c")   #> "a/b"
path.dir("a/b/")    #> "a"
path.dir("a")       #> "."    (パス区切りを含まない → カレント)
path.dir("/a")      #> "/"
path.dir("/")       #> "/"
path.dir("")        #> "."

4. path.base

  • 引数: path (String) 1 個。非 String は呼び出し位置で Error (§10)
  • path の末尾要素 (basename) を返す。末尾のパス区切りは除いてから末尾要素を取る
path.base("a/b/c")   #> "c"
path.base("a/b/")    #> "b"
path.base("a")       #> "a"
path.base("/")       #> "/"
path.base("")        #> "."

5. path.ext

  • 引数: path (String) 1 個。非 String は呼び出し位置で Error (§10)
  • path末尾要素 の最後の . 以降を、先頭ドット込み で返す。. が無ければ ""
  • 拡張子の判定は末尾要素 (basename) に対してのみ行う。ディレクトリ部分の . は見ない
  • 末尾要素は §4 と同じ決め方をする。末尾のパス区切りは除いてから採る
path.ext("note.md")      #> ".md"
path.ext("a.tar.gz")     #> ".gz"   (最後のドット以降)
path.ext("note")         #> ""
path.ext("a.b/c")        #> ""      (拡張子判定は末尾要素 "c" のみ)
path.ext(".gitignore")   #> ".gitignore"   (末尾要素の最初の文字が "." でもその位置のドットを採る)
path.ext("a/b.txt/")     #> ".txt"  (末尾のパス区切りは末尾要素の決め方を変えない)
path.ext("")             #> "."    (空の綴りの末尾要素は base と同じく "." である)

6. path.stem

  • 引数: path (String) 1 個。非 String は呼び出し位置で Error (§10)
  • base(path) から末尾の ext(path) を取り除いた部分を返す。定義上 stem(p) ++ ext(p) == base(p) (++ は文字列連結を表す説明用記法)
path.stem("a/note.md")   #> "note"
path.stem("a.tar.gz")    #> "a.tar"
path.stem("note")        #> "note"
path.stem(".gitignore")  #> ""      (base == ext なので stem は空)

7. path.normalize

  • 引数: path (String) 1 個。非 String は呼び出し位置で Error (§10)
  • path§1 の規則で正規化した String を返す。FS にアクセスせず字句的にまとめるだけ
  • 空文字列は "." に正規化する
path.normalize("a//b/./c")   #> "a/b/c"
path.normalize("a/b/../c")   #> "a/c"
path.normalize("./a")        #> "a"
path.normalize("")           #> "."
path.normalize("a/b/")       #> "a/b"   (末尾のパス区切りを除く)

8. path.is_absolute

  • 引数: path (String) 1 個。非 String は呼び出し位置で Error (§10)
  • path が絶対パスなら true、相対なら false を返す (Bool)。字句的判定 で、FS にアクセスしない
  • 判定基準は OS native。POSIX 系では先頭がパス区切りであること。Windows ではドライブ付きで根を持つ (C:\a) か UNC 共有で始まる (\\server\share\a) ことで、ドライブ相対の C:a とボリューム名を持たない \a は絶対ではない (§1.1)
path.is_absolute("/a/b")   #> true     (POSIX 系)
path.is_absolute("a/b")    #> false
path.is_absolute("./a")    #> false
path.is_absolute("")       #> false

9. path.split

  • 引数: path (String) 1 個。非 String は呼び出し位置で Error (§10)
  • path を親ディレクトリと末尾要素に分け、スロット {dir, base} を持つ closed・immutable なレコード を返す:
    • dirdir(path) (§3) と同じ値 (String)
    • basebase(path) (§4) と同じ値 (String)
  • 定義上 split(p).dir == dir(p) かつ split(p).base == base(p)dirbase を 1 回の呼び出しでまとめて得る糖衣
match path.split("a/b/c") {
  r: Any => print(r.dir); print(r.base)  # "a/b" と "c"
}

s := path.split("note.md")
s.dir    #> "."
s.base   #> "note.md"

10. エラーモデル

std:path の全関数は 値を直接返し、回復可能エラー (Result / {kind, message}) を返さない。パスの字句操作は入力が String (および join では String の List) でありさえすれば失敗しないためである。

型が合わない呼び出しは回復可能エラーではなく、呼び出し位置で Error (panic 型, language-spec.md §16) になる:

  • いずれかの関数に 非 Stringpath を渡す
  • join非 List を渡す、または List 要素に 非 String を含む

これは json.md §5 の引数型エラー (非 String の text 等) と同じ規律で、std:fs の I/O 失敗 ({kind, message}Err) とは別の層である。