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.
./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.
./tools and "./tools" name the same folder, and Nix hands you two different
kinds of value for them.
Quote the second one, so the set reads { plain = "path"; quoted = "string"; }.
›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.
You 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.
Say settings.nix holds this, and nothing else:
{
port = 3000;
greeting = "hello";
}
Then importing it gives you that attribute set:
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.
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.
Take the search tools out of the same file too, so you get [ "vim" "ripgrep" ].
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.
Point import at a directory and it doesn’t complain. It goes looking for one
particular file inside.
Which 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.
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.
- 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.
importevaluates 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.


Share your thoughts