Let-In Blocks and Local Bindings
Let-In Blocks and Local Bindings
There is no x = 1 that you later change. The boring default is: let names, in uses them, bindings may mention each other in any order, and unused names (even throw) stay cheap.
Mental model
let
b = a + 10;
a = 5;
in
b # 15Order in the let is not execution order. Nix builds a graph. b needs a; a is 5.
inherit x; in an attrset is x = x; — copy the name from scope.
Nested let shadows. The outer name is still there for the outer in.
Worked examples
Case 1: Desk URL from parts
Save as desk_service.nix:
# desk_service.nix
let
domain = "internal.desk";
port = 443;
protocol = "https";
serviceUrl = "${protocol}://${domain}:${toString port}";
in
{
inherit domain port;
url = serviceUrl;
}nix eval --file desk_service.nix --jsonOutput:
{"domain":"internal.desk","port":443,"url":"https://internal.desk:443"}
Case 2: Order does not matter
Save as order.nix:
# order.nix
let
c = b + 1;
b = a + 1;
a = 1;
in
cnix eval --file order.nixOutput:
3
Write names in dependency order anyway. Humans read top-down even when Nix does not.
Case 3: Shadowing
Save as scope.nix:
# scope.nix
let
tier = "standard";
in
{
outerTier = tier;
overridden =
let
tier = "premium";
in
tier;
}nix eval --file scope.nix --jsonOutput:
{"outerTier":"standard","overridden":"premium"}
Case 4: Unused throw is fine
Save as unused.nix:
# unused.nix
let
domain = "internal.desk";
unused = throw "never evaluated";
in
domainnix eval --file unused.nixOutput:
"internal.desk"
A 200-line let of unused helpers is still a readability problem. Laziness does not make a mess cheap for the next reader.
Case 5: inherit from a set
Save as inherit_from.nix:
# inherit_from.nix
let
cfg = { host = "desk"; port = 8080; extra = true; };
in
{
inherit (cfg) host port;
}nix eval --file inherit_from.nix --jsonOutput:
{"host":"desk","port":8080}
extra stayed in cfg. inherit (cfg) host port is the boring way to pick keys.
The trap
The trap is a 200-line let of unrelated helpers. Split files (import, next chapters) when a binding cluster has a name (db, tls, desk-api).
The other trap is circular let: a = b; b = a;. Eval loops. rec attrsets have the same foot-gun (next chapter).
The boring rule
letfor names.infor the value you mean.- Bindings can be in any order; write them in human order.
inherit/inherit (set)instead ofhost = host.- Unused bindings can
throw; still delete them if they confuse. - Split large
lets into files.
Try this
- Add
unused = throw "x";todesk_service.nix; eval still works. - Reverse three bindings as in Case 2; eval.
inherit (cfg) missing;and read the error.- Nested
letthat shadowsport; print both outer and inner.