Skip to content

Composed Types

In this lesson, we will cover some of the composed types similar to what we did in the Basic Types lesson. There are certainly many more composed types, but for this lesson, we will focus on just a few.

Trivial Example

Similar to the Basic Types lesson, this first example is simple and almost too trivial. Again, the point create simple declared options, a configuration that is correct, and check that our output is exactly as we expect.

In the options-trivial.nix file, we have declared multiple composed options.

options-trivial.nix
{lib, ...}: let
  inherit (lib) types;
in {
  options = {
    exampleListOf = lib.mkOption {
      type = types.listOf types.int;
      description = "My example list of integers.";
    };
    exampleAttrsOf = lib.mkOption {
      type = types.attrsOf types.str;
      description = "My example attrs of strings.";
    };
    exampleNullOr = lib.mkOption {
      type = types.nullOr types.bool;
      description = "My example null or boolean.";
    };
    exampleEither = lib.mkOption {
      type = types.either types.str types.int;
      description = "My example either string or integer.";
    };
    exampleOneOf = lib.mkOption {
      type = types.oneOf [types.str types.int types.bool];
      description = "My example one of string, integer, or bool.";
    };
  };
}

In the config-trivial.nix file, we have declared values for all these options.

config-trivial.nix
{...}: {
  config = {
    exampleListOf = [
      1
      10
      100
      1000
    ];
    exampleAttrsOf = {
      foo = "bar";
      baz = "quz";
      fizz = "buzz";
    };
    exampleNullOr = true;
    exampleEither = "I could have been an integer.";
    exampleOneOf = "I could have been an integer or boolean.";
  };
}

In the eval-trivial.nix file, we evaluate our options and config and have it return the config values.

eval-trivial.nix
{pkgs}:
(
  pkgs.lib.evalModules {
    modules = [
      ./options-trivial.nix
      ./config-trivial.nix
    ];
  }
)
.config

In the run-trivial.sh file, we evaluate the eval file and have it print out a nicely formatted version of the configuration.

run-trivial.sh
nix eval -f eval-trivial.nix \
    --apply 'x: x {pkgs = import <nixpkgs> {};}' \
    --json | nix run nixpkgs#jq -- .

If you execute the run file (./run-trivial.sh), you should see an output that matches what we have configured.

{
  "exampleAttrsOf": {
    "baz": "quz",
    "fizz": "buzz",
    "foo": "bar"
  },
  "exampleEither": "I could have been an integer.",
  "exampleListOf": [
    1,
    10,
    100,
    1000
  ],
  "exampleNullOr": true,
  "exampleOneOf": "I could have been an integer or boolean."
}

Nested Example

Here we have a few examples with more heavily composed types. In the options-nested.nix file, we have declared options with types as follows:

  1. A list of any mix of strings, integers, and booleans.
  2. An attribute set where the values can be either null or strings.
  3. An attribute set where the values are lists of a mix of integers and strings.
  4. An attribute set where the values are either:
    • A list of a mix of null values and integers.
    • An attribute set where the values are strings.
options-nested.nix
{lib, ...}: let
  inherit (lib) types;
in {
  options = {
    exampleNested1 = lib.mkOption {
      type = types.listOf (types.oneOf [types.str types.int types.bool]);
      description = "My example composed types.";
    };
    exampleNested2 = lib.mkOption {
      type = types.attrsOf (types.nullOr types.str);
      description = "My example composed types.";
    };
    exampleNested3 = lib.mkOption {
      type = types.attrsOf (types.listOf (types.either types.int types.str));
      description = "My example composed types.";
    };
    exampleNested4 = lib.mkOption {
      type = types.attrsOf (
        types.either
        (types.listOf (types.nullOr types.int))
        (types.attrsOf types.str)
      );
      description = "My example composed types.";
    };
  };
}

In the config-nested.nix file, we have declared values for all these options.

config-nested.nix
{...}: {
  config = {
    exampleNested1 = [
      "I can use strings, integers, or booleans."
      42
      true
    ];
    exampleNested2 = {
      foo = "bar";
      baz = "qux";
      dontuse = null;
      fizz = null;
      buzz = null;
    };
    exampleNested3 = {
      singlePrimes = [2 3 5 7];
      fibonacci = [1 1 2 3 5 8 13];
      mixed = [(-1) 0 "one"];
    };
    exampleNested4 = {
      foo = [null 1 2 3];
      bar = {
        fizz = "buzz";
        baz = "qux";
      };
    };
  };
}

In the eval-nested.nix file, we evaluate our options and config and have it return the config values.

eval-nested.nix
{pkgs}:
(
  pkgs.lib.evalModules {
    modules = [
      ./options-nested.nix
      ./config-nested.nix
    ];
  }
)
.config

In the run-nested.sh file, we evaluate the eval file and have it print out a nicely formatted version of the configuration.

run-nested.sh
nix eval -f eval-nested.nix \
    --apply 'x: x {pkgs = import <nixpkgs> {};}' \
    --json | nix run nixpkgs#jq -- .

If you execute the run file (./run-nested.sh), you should see an output that matches what we have configured.

{
  "exampleNested1": [
    "I can use strings, integers, or booleans.",
    42,
    true
  ],
  "exampleNested2": {
    "baz": "qux",
    "buzz": null,
    "dontuse": null,
    "fizz": null,
    "foo": "bar"
  },
  "exampleNested3": {
    "fibonacci": [
      1,
      1,
      2,
      3,
      5,
      8,
      13
    ],
    "mixed": [
      -1,
      0,
      "one"
    ],
    "singlePrimes": [
      2,
      3,
      5,
      7
    ]
  },
  "exampleNested4": {
    "bar": {
      "baz": "qux",
      "fizz": "buzz"
    },
    "foo": [
      null,
      1,
      2,
      3
    ]
  }
}