paths-imports.md

Paths and imports

Before this, you'll want

  1. The Great Rebuild
The Nix language18 min

You’ll find ./tools, import ./tools and "${./tools}" in the same file, sometimes on neighbouring lines, looking for all the world like three spellings of one idea. They’re three different operations with three different results, and the syntax gives you almost nothing to go on.

So here’s the course’s virtual tools folder. Open each operation and watch what comes back. None of this touches your real disk.

demo
demo/
󱄅main.nix
tools/
󱄅default.nix
message.txt
./toolspath value

Points at the directory. Nothing inside it is evaluated merely because a file named default.nix exists.

import ./toolsevaluate

import sees a directory, opens tools/default.nix, evaluates its expression, and returns that value.

"${./tools}"store snapshot

String coercion copies the directory contents into the Nix store and produces a string such as /nix/store/…-tools.

A path is not a string

./tools and "./tools" name the same folder, and Nix hands you two different kinds of value for them.

Try it

Quote the second one, so the set reads { plain = "path"; quoted = "string"; }.

nix replevaluated as you typeProject filesNearby files from the example project are mounted, so relative paths and imports are real.

A relative path belongs to the file that wrote it

project/
󱄅flake.nix
packages/
󱄅default.nixthe file writing ./source
source/what ./source means there

./source inside packages/default.nix means packages/source. Not the folder you were standing in when you ran nix build, and not the top of the repository. The file holding the path is the only thing that decides.

So a Nix file carries its own neighbourhood around with it. Move the folder somewhere else and everything it points at goes along.

One place that rule can’t apply.

PickYou type `./source` straight into `nix repl`. What is it relative to?

An expression you type at a prompt was never in a file, so there’s nothing for it to be relative to. Nix falls back on your working directory, and the same goes for anything after --expr.

import runs a file and hands back what it made

Say settings.nix holds this, and nothing else:

NixA plain Nix expression. Nothing from nixpkgs or a module system is supplied.
{
  port = 3000;
  greeting = "hello";
}

Then importing it gives you that attribute set:

Project filesNearby files from the example project are mounted, so relative paths and imports are real.
import ./settings.nix

If you’re coming from C or Python, unlearn the word. Nothing gets pasted in and nothing runs top to bottom. Nix works out the one expression the file contains and hands you what it came to.

An imported file can’t see your names

A file you import is evaluated on its own, and it can’t reach a single one of your local names. The pkgs sitting in scope where you wrote import means nothing inside it. So how does it get one?

It asks. Nearly every Nix file you’ll ever open starts by naming what it wants, and tools below stands in for one that does.

Try it

Take the search tools out of the same file too, so you get [ "vim" "ripgrep" ].

nix replevaluated as you typeNixA plain Nix expression. Nothing from nixpkgs or a module system is supplied.

That’s the shape, and it’s most of what package files look like. The file is a function, you call it once with the things it asked for, and you take attributes off the result.

Importing a folder looks for default.nix

Point import at a directory and it doesn’t complain. It goes looking for one particular file inside.

PickWhich of these evaluates `tools/default.nix` and returns the value it describes?

The shortcut belongs to import, not to the path. A bare ./tools is still just a folder, which is exactly what you want when you’re handing a package its source: that copies the directory instead of running the Nix inside it.

Putting a path in a string copies it into the store

Fit a folder into a string and Nix copies the whole thing into the store first, then hands you the address it landed at.

$ nix replnix-repl> "${./tools}""/nix/store/4p8f...-tools"

Everything nested inside comes along, and the copy is read-only the moment it arrives.

What to keep

  • A path literal and a string are different types, and the type is what lets tooling follow it.
  • A relative path is relative to the file containing it, not to where you ran the command.
  • import evaluates a file’s one expression and returns the value.
  • An imported file sees none of your local names, so it asks for what it needs as a function argument.
  • Importing a directory evaluates its default.nix. A bare path does not.
  • Putting a path in a string, or handing it to src, copies it into the store.

Useful links

NORMALCOURSE IN BETA