Skip to content

Lists and Attrsets

Overview

Lists and attribute sets are the two composite value types in the Nix language. Lists hold ordered sequences of values; attribute sets hold named fields. Both appear throughout Nixpkgs and NixOS configuration, and most functions take or return one of these shapes.

Details

Lists

A list is written with square brackets and whitespace-separated elements (no commas):

[ 123 ./foo.nix "abc" (f { x = y; }) ]

Function calls inside a list must be parenthesized. Without parentheses, [ f { x = y; } ] is a five-element list (function, then set), not a call result.

Lists are lazy in element values (elements are not evaluated until needed) but strict in length (the list structure is known at construction). Index with builtins.elemAt; concatenate with ++ (see operators):

builtins.elemAt [ "a" "b" "c" ] 1  # => "b"
[ 1 2 ] ++ [ 3 4 ]                 # => [ 1 2 3 4 ]

Attribute sets

An attribute set is { name = expr; ... }. Names must be unique within a set; order is irrelevant. Values are arbitrary expressions, each terminated by ;.

Nested structure can be written inline with attribute paths:

{ a.b.c = 1; a.b.d = 2; }

is equivalent to { a = { b = { c = 1; d = 2; }; }; }.

Selection and membership

  • Select: set.attr — missing attributes are an error unless a default is given: set.attr or default.
  • Has attribute: set ? attrpath — tests whether the path exists (see operators).

Attribute names can be string literals or identifiers. String interpolation works in both selection and definition:

let bar = "foo"; in { ${bar} = 123; }.foo  # => 123

If an interpolated name evaluates to null, that attribute is omitted rather than raising an error:

{ ${if false then "bar" else null} = true; }  # => {}

Update (//)

left // right merges two attribute sets. The result has every attribute from either side; on duplicate names, the right operand wins. The merge is shallow: nested sets under the same key are replaced wholesale, not deep-merged.

{ a = 1; b = 2; } // { b = 3; c = 4; }  # => { a = 1; b = 3; c = 4; }

Both operands are forced to WHNF before the merge; attribute values inside are not forced by // itself. Details and precedence: operators.

rec, inherit, and callable sets

Recursive sets (rec { ... }) put each attribute in scope for the others, like a mutual let-in binding. Without rec, x = y; refers to y in the surrounding scope, not another field in the same set. Unbounded mutual references cause infinite recursion at evaluation time.

inherit desugars to ordinary bindings:

inherit x;              # => x = x;
inherit (src) a b;      # => a = src.a; b = src.b;

A set with a __functor attribute whose value is callable can be applied like a function; the set itself is passed as the first argument:

let add = { __functor = self: x: x + self.x; };
    inc = add // { x = 1; };
in inc 1   # => 2  (like add.__functor add 1)

This pattern attaches metadata to callable values without special-casing callers.

Examples

Minimal list and set:

{
  items = [ "nginx" "postgres" ];
  cfg = {
    host = "localhost";
    port = 5432;
  };
}

rec so attributes refer to each other (see rec and fixed points):

rec { x = y; y = 123; }.x  # => 123

inherit from the surrounding scope and from another set:

let x = 123; src = { a = 1; b = 2; };
in { inherit x; inherit (src) a b; }  # => { x = 123; a = 1; b = 2; }

Dynamic attribute name with conditional omission:

let enableFeature = true;
    name = if enableFeature then "feature" else null;
in { ${name} = true; }  # => { feature = true; }

Shallow merge (nested keys under a are replaced, not combined):

{ a = { b = 1; }; } // { a = { c = 3; }; }  # => { a = { c = 3; }; }

See also

References