Home Explore Blog Models CI



nixpkgs

3rd chunk of `lib/path/README.md`
e0f4f21f7b456df5e27ccdf749aa8281eb488c3f3de7ac8e0000000100000fed
Observing: Subpaths such as `foo/bar` can be represented in various ways:
- string: `"foo/bar"`
- list with all the components: `[ "foo" "bar" ]`
- attribute set: `{ type = "relative-path"; components = [ "foo" "bar" ]; }`

Considering: Paths should be as safe to use as possible.
We should generate string outputs in the library and not encourage users to do that themselves.

Decision: Paths are represented as strings.

<details>
<summary>Arguments</summary>

- (+) It's simpler for the users of the library.
  One doesn't have to convert a path a string before it can be used.
  - (+) Naively converting the list representation to a string with `concatStringsSep "/"` would break for `[]`, requiring library users to be more careful.
- (+) It doesn't encourage people to do their own path processing and instead use the library.
  With a list representation it would seem easy to just use `lib.lists.init` to get the parent directory, but then it breaks for `.`, which would be represented as `[ ]`.
- (+) `+` is convenient and doesn't work on lists and attribute sets.
  - (-) Shouldn't use `+` anyways, we export safer functions for path manipulation.

</details>

### Parent directory

Observing: Relative paths can have `..` components, which refer to the parent directory.

Considering: Paths should be as safe and unambiguous as possible.

Decision: `..` path components in string paths are not supported, neither as inputs nor as outputs.
Hence, string paths are called subpaths, rather than relative paths.

<details>
<summary>Arguments</summary>

- (+) If we wanted relative paths to behave according to the "physical" interpretation (as a directory tree with relations between nodes), it would require resolving symlinks, since e.g. `foo/..` would not be the same as `.` if `foo` is a symlink.
  - (-) The "logical" interpretation is also valid (treating paths as a sequence of names), and is used by some software.
    It is simpler, and not using symlinks at all is safer.
  - (+) Mixing both models can lead to surprises.
  - (+) We can't resolve symlinks without filesystem access.
  - (+) Nix also doesn't support reading symlinks at evaluation time.
  - (-) We could just not handle such cases, e.g. `equals "foo" "foo/bar/.. == false`.
    The paths are different, we don't need to check whether the paths point to the same thing.
    - (+) Assume we said `relativeTo /foo /bar == "../bar"`.
      If this is used like `/bar/../foo` in the end, and `bar` turns out to be a symlink to somewhere else, this won't be accurate.
      - (-) We could decide to not support such ambiguous operations, or mark them as such, e.g. the normal `relativeTo` will error on such a case, but there could be `extendedRelativeTo` supporting that.
- (-) `..` are a part of paths, a path library should therefore support it.
  - (+) If we can convincingly argue that all such use cases are better done e.g. with runtime tools, the library not supporting it can nudge people towards using those.
- (-) We could allow "..", but only in the prefix.
  - (+) Then we'd have to throw an error for doing `append /some/path "../foo"`, making it non-composable.
  - (+) The same is for returning paths with `..`: `relativeTo /foo /bar => "../bar"` would produce a non-composable path.
- (+) We argue that `..` is not needed at the Nix evaluation level, since we'd always start evaluation from the project root and don't go up from there.
  - (+) `..` is supported in Nix paths, turning them into absolute paths.
    - (-) This is ambiguous in the presence of symlinks.
- (+) If you need `..` for building or runtime, you can use build-/run-time tooling to create those (e.g. `realpath` with `--relative-to`), or use absolute paths instead.
  This also gives you the ability to correctly handle symlinks.

</details>

### Trailing slashes

Observing: Subpaths can contain trailing slashes, like `foo/`, indicating that the path points to a directory and not a file.

Considering: Paths should be as consistent as possible, there should only be a single normalisation for the same path.

Title: Nix `lib.path` Decisions: Subpath String Representation and Exclusion of `..` Components
Summary
This document outlines further design decisions for the Nix `lib.path` library. It reiterates the decision to represent subpaths as simple strings (e.g., `"foo/bar"`) rather than lists or attribute sets. This choice prioritizes user simplicity, avoids potential conversion errors (especially with empty lists), and encourages the use of library functions over custom path manipulation. Crucially, the document also decides *not* to support `..` (parent directory) components in paths, neither as input nor output. This means paths within this context are specifically termed 'subpaths' instead of 'relative paths'. The rationale for this exclusion is to maintain safety, unambiguity, and composability, particularly given the challenges of resolving symlinks without filesystem access during Nix evaluation. It's argued that `..` is often unnecessary at the Nix evaluation level and, if required for build- or run-time scenarios, can be handled by external tooling like `realpath`.