Nix Language Quirks: Difference between revisions

From NixOS Wiki
Link to manual for indented strings. Also change capitalization to match manual
Tags: Mobile edit Mobile web edit
 
(10 intermediate revisions by the same user not shown)
Line 20: Line 20:
2 </syntaxHighlight>
2 </syntaxHighlight>


Nah, <code>with</code> and <code>let</code> have different priority when resolving names.
In this case, <code>with</code> and <code>let</code> have different priority when resolving names.


[https://github.com/NixOS/nix/issues/1361 Good discussion on this topic]
[https://github.com/NixOS/nix/issues/1361 Good discussion on this topic]
Generally the use of <code>with</code> is discouraged. See the [https://nix.dev/guides/best-practices#with-scopes best practices guide] for how best to use <code>inherit</code> as an alternative.


== Old <code>let</code> syntax ==
== Old <code>let</code> syntax ==


This is an old Nix syntax, that probably isn't used much
This is an [https://github.com/NixOS/nix/issues/1361#issuecomment-323050690 old] Nix syntax, that probably isn't used much
  <syntaxHighlight lang=nix>
  <syntaxHighlight lang=nix>
nix-repl> let { x = 1; y = x + 1; body = y; }
nix-repl> let { x = 1; y = x + 1; body = y; }
Line 73: Line 75:


== Imports and namespaces ==
== Imports and namespaces ==
There is a keyword <code>import</code>, but it's equivalent in other languages is <code>eval</code>. It can be used for namespacing too:
Nix includes a keyword <code>import</code>, but it's equivalent in other languages is <code>eval</code>.  
 
It is typically be used for namespacing:


  <syntaxHighlight lang=nix>let
  <syntaxHighlight lang=nix>let
Line 81: Line 85:
   pkgs.runCommand (lib.strings.removePrefix ".... </syntaxHighlight>
   pkgs.runCommand (lib.strings.removePrefix ".... </syntaxHighlight>


consider using <code>import</code> here as using <code>qualified import ...</code> in Haskell or <code>import ...</code> in Python.  
consider the use of <code>import</code> here similar to using <code>qualified import ...</code> in Haskell or <code>import ...</code> in Python. Another (discouraged and increasingly uncommon) way of importing is [https://nix.dev/manual/nix/2.24/language/syntax#with-expressions <code>with import ...;</code>], which corresponds to Python <code>from ... import *</code>. This use of <code>with</code> imports everything from the target into scope, which has numerous potential gotchas and problems, and so using [https://nix.dev/guides/best-practices#with-scopes <code>inherit</code>] instead is encouraged and preferred.
 
Another way of importing is <code>with import ...;</code>, which corresponds to Python <code>from ... import *</code>.
 
But because of not very great IDE support in Nix, <code>with import ...;</code> is discouraged. Rather use <code>inherit</code>, especially if you are targeting source code for Nix newcomers:
 
  <syntaxHighlight lang=nix>
let
  lib = import <nixpkgs/lib>;
  inherit (lib.strings)
    removePrefix removeSuffix
  ;
  inherit (lib.lists)
    isList init drop
  ;
in
  removePrefix ... </syntaxHighlight>
 
<code>inherit</code> has higher priority than <code>with</code>, and conflicts with <code>let</code>
 
  <syntaxHighlight lang=nix>
nix-repl> let pkgs = { x = 1; }; x = 2; x = 3; inherit (pkgs) x; in x
error: attribute ‘x’ at (string):1:31 already defined at (string):1:24 </syntaxHighlight>
This makes it a sane citizen of Nix lanugage... except it has a twin, called <code>{ inherit ...; }</code>. They DON'T do the same - <code>let inherit ...</code> adds let-bindings, and <code>{ inherit ...; }</code> adds attributes to a record.


== builtins.replaceStrings key match on "" ==
== builtins.replaceStrings key match on "" ==
Line 113: Line 94:
</syntaxHighlight>
</syntaxHighlight>


Function allows match for "" in string. <code>[match]</code> gets checked sequentially, and when "" is checked - it always matches. And so - when "" is checked in always inserts the replacement, then next char in sting gets passed through, and the next char after that from the string gets processed.
The [https://noogle.dev/f/builtins/replaceStrings <code>builtins.replaceStrings</code>] function allows matching <code>""</code> in <code>string</code>. <code>[match]</code> gets checked sequentially, and when <code>""</code> is checked - it ''always'' matches. And so - when <code>""</code> is checked it ''always'' inserts the corresponding replacement from <code>[replace]</code>, then the next char in <code>string</code> gets inserted, and then the next char after that from <code>string</code> gets processed.
<syntaxHighlight lang=nix>
<syntaxHighlight lang=nix>
nix-repl> builtins.replaceStrings ["" "e"] [" " "i"] "Hello world"
nix-repl> builtins.replaceStrings ["" "e"] [" " "i"] "Hello world"
Line 123: Line 104:
== Indented strings trim leading whitespace ==
== Indented strings trim leading whitespace ==


Not really surprising, but ...
Leading spaces are removed from both single-line and multi-line <strong>indented strings</strong>.
 
Leading spaces are removed also in single-line <strong>indented strings</strong>.


<syntaxHighlight lang=nix>
<syntaxHighlight lang=nix>
Line 151: Line 130:
== Integer precision ==
== Integer precision ==


Integer precision is limited to 64 Bit in the original Nix interpreter.
Integer precision is limited to [https://en.m.wikipedia.org/wiki/64-bit_computing 64-bit] in the original Nix interpreter.


So the valid integer range is from -2**63 to 2**63-1 = from -9223372036854775808 to 9223372036854775807
So the valid integer range is from -2**63 to 2**63-1 = from -9223372036854775808 to 9223372036854775807
Line 222: Line 201:
{ a = "b"; } // (if true then { foo = "bar"; } else { } )
{ a = "b"; } // (if true then { foo = "bar"; } else { } )
</syntaxHighlight>
</syntaxHighlight>
== Hexadecimal, octal, and binary ==
As of late 2024, Nix doesn't contain builtin support for parsing many number formats like hexadecimal, octal, and binary. It ''does'', however, support the [https://noogle.dev/f/builtins/fromTOML <code>builtins.fromTOML</code>] function, which [https://github.com/NixOS/nix/issues/7578#issuecomment-1955985859 can be used] to parse these number formats.
<syntaxHighlight lang=nix>
nix-repl> (builtins.fromTOML "octal = 0o11").octal
9
nix-repl> (builtins.fromTOML "binary = 0b1001").binary
9
nix-repl> (builtins.fromTOML "hex = 0x09").hex       
9
</syntaxHighlight>
== Mimicking case statements with attribute sets ==
Nix doesn't include native support for case statements, however when dealing with string types it's possible to use some string interpolation behavior to achieve something similar to case statement behavior, as described in this [https://discourse.nixos.org/t/case-statement-expr/27741/12 thread].
In the example from the thread, given some string argument <code>x</code>, the following code would place different values into a text file depending on it's value:
<syntaxHighlight lang=nix>
environment.etc."just/for/test".text = {
  "a" = "hello";
  "b" = "hi";
  "c" = "ciao";
}."${x}";
</syntaxHighlight>
So if <code>x</code> is set to the string <code>"a"</code> then the <code>just/for/test</code> file contents would be set to the string <code>"hello"</code>. The code above is the same in behavior to the following, more common, if-else-style construct:
<syntaxHighlight lang=nix>
environment.etc."just/for/test".text =
  if x == "a" then
    "hello"
  else if x == "b" then
    "hi"
  else if x == "c" then
    "ciao"
  else
    abort "x is invalid";
</syntaxHighlight>
There is an example in <code>coq</code> package code [https://github.com/NixOS/nixpkgs/blob/5185539c51ba658e70b29e01c0c320a85f4e2098/pkgs/build-support/coq/extra-lib.nix#L98 here] where someone used this behavior to build a reusable function <code>switch</code>.


== Nix Language FAQ ==
== Nix Language FAQ ==

Latest revision as of 02:02, 22 October 2024

with and let

with gets less priority than let. This can lead to confusions, especially if you like to write with pkgs;:

nix-repl> pkgs = { x = 1; }

nix-repl> with pkgs; x
1

nix-repl> with pkgs; let x = 2; in x
2

So we see, that let binding overrides with binding. But what about this?

nix-repl> let x = 2; in with pkgs; x
2

In this case, with and let have different priority when resolving names.

Good discussion on this topic

Generally the use of with is discouraged. See the best practices guide for how best to use inherit as an alternative.

Old let syntax

This is an old Nix syntax, that probably isn't used much

nix-repl> let { x = 1; y = x + 1; body = y; }
2

It is equivalent to modern syntax expression let x = 1; y = x + 1; in y. Note, that it doesn't require rec keyword.

Note, that it isn't equivalent to with rec { x = 1; y = x + 1; body = y; }; body because of mentioned with and let quirk, but is same as rec { x = 1; y = x + 1; body = y; }.body

Default values are not bound in @ syntax

Destructured arguments can have default values, but those default values are part of the full function argument.

In the following example, calling the function that binds a default value "a" to the argument's attribute a with an empty attribute set as an argument will produce an empty attribute set args instead of { a = "a"; }:

(args@{a ? "a"}: args) {}

{ }

Related: GitHub issue filed 2017

Something that looks like both record attribute and let-binding

Destructuring function argument - is a great feature of Nix.

nix-repl> f = { x ? 1, y ? 2 }: x + y

nix-repl> f { }
3

The fact that we can add @args argument assignment is also cool

nix-repl> f = { x ? 1, y ? 2, ... }@args: with args; x + y + z

nix-repl> f { z = 3; }
6

But don't be fooled, args doesn't necessarily contain x and y:

nix-repl> f = { x ? 1, y ? 2, ... }@args: args.x + args.y + args.z

nix-repl> f { z = 3;}
error: attribute x missing, at (string):1:30

These x and y are in fact let-bindings, but overridable ones.

Imports and namespaces

Nix includes a keyword import, but it's equivalent in other languages is eval.

It is typically be used for namespacing:

let
  pkgs = import <nixpkgs> {};
  lib = import <nixpkgs/lib>;
in
  pkgs.runCommand (lib.strings.removePrefix "....

consider the use of import here similar to using qualified import ... in Haskell or import ... in Python. Another (discouraged and increasingly uncommon) way of importing is with import ...;, which corresponds to Python from ... import *. This use of with imports everything from the target into scope, which has numerous potential gotchas and problems, and so using inherit instead is encouraged and preferred.

builtins.replaceStrings key match on ""

Syntax:

builtins.replaceStrings [match] [replace] string

The builtins.replaceStrings function allows matching "" in string. [match] gets checked sequentially, and when "" is checked - it always matches. And so - when "" is checked it always inserts the corresponding replacement from [replace], then the next char in string gets inserted, and then the next char after that from string gets processed.

nix-repl> builtins.replaceStrings ["" "e"] [" " "i"] "Hello world"
" H e l l o   w o r l d "
nix-repl> builtins.replaceStrings ["ll" ""] [" " "i"] "Hello world"
"iHie ioi iwioirilidi"

Indented strings trim leading whitespace

Leading spaces are removed from both single-line and multi-line indented strings.

''  s  '' == "s  "

Usually, indented strings have multiple lines:

''
  s
'' == "s\n"

Though note that tab characters are not stripped:

''
	s
'' ==  "	s\n"

This is documented in more detail in the String section of the Nix reference manual. Also see NixOS/nix#7834 and NixOS/nix#9971 for more information.

Integer precision

Integer precision is limited to 64-bit in the original Nix interpreter.

So the valid integer range is from -2**63 to 2**63-1 = from -9223372036854775808 to 9223372036854775807

Integer overflow is not an error

nix-repl> 9223372036854775807 + 1
-9223372036854775808

Invalid integer literals throw

nix-repl> 9223372036854775808  
error: invalid integer '9223372036854775808'

No negative number literals

Negative numbers are parsed as "zero minus positive"

nix-instantiate --parse --expr '(-1)'
(__sub 0 1)

So this throws, because the positive number is out of range

nix-repl> -9223372036854775808
error: invalid integer '9223372036854775808'

but this works

nix-repl> -9223372036854775807 - 1
-9223372036854775808

Attribute set entries with a name that evaluates to null will not be added to the set

From this section of the Nix Reference Manual:

In the special case where an attribute name inside of a set declaration evaluates to null (which is normally an error, as null cannot be coerced to a string), that attribute is simply not added to the set:

{ ${if foo then "bar" else null} = true; }

This will evaluate to {} if foo evaluates to false.

The relevant source can be found here.

This feature can be used to conditionally include or exclude attribute set entries, for example:

nix-repl> { ${if true then "foo" else null} = "bar"; }      
{ foo = "bar"; }

nix-repl> { ${if false then "foo" else null} = "bar"; }
{ }

This might be used as an alternative to conditionally merging attribute sets using // like the following:

{ a = "b"; } // (if true then { foo = "bar"; } else { } )

Hexadecimal, octal, and binary

As of late 2024, Nix doesn't contain builtin support for parsing many number formats like hexadecimal, octal, and binary. It does, however, support the builtins.fromTOML function, which can be used to parse these number formats.

nix-repl> (builtins.fromTOML "octal = 0o11").octal
9

nix-repl> (builtins.fromTOML "binary = 0b1001").binary
9

nix-repl> (builtins.fromTOML "hex = 0x09").hex         
9

Mimicking case statements with attribute sets

Nix doesn't include native support for case statements, however when dealing with string types it's possible to use some string interpolation behavior to achieve something similar to case statement behavior, as described in this thread.

In the example from the thread, given some string argument x, the following code would place different values into a text file depending on it's value:

environment.etc."just/for/test".text = {
  "a" = "hello";
  "b" = "hi";
  "c" = "ciao";
}."${x}";

So if x is set to the string "a" then the just/for/test file contents would be set to the string "hello". The code above is the same in behavior to the following, more common, if-else-style construct:

environment.etc."just/for/test".text =
  if x == "a" then
    "hello"
  else if x == "b" then
    "hi"
  else if x == "c" then
    "ciao"
  else
    abort "x is invalid";

There is an example in coq package code here where someone used this behavior to build a reusable function switch.

Nix Language FAQ

Q: What is the shortest id function definition?

A: x: x

Q: Why not x:x?

A:

nix-repl> builtins.typeOf (x: x)
"lambda"
nix-repl> builtins.typeOf (x:x)
"string"

! Can you figure out how can this happens before reading explanation?

Q: Can Nix code be interpolated?

No, only attribute names can.

nix-repl> let ${"x"} = 2; in x
2

nix-repl> with { ${"x"} = 2; }; x
2

nix-repl> let x = 1; y = ${x}; in y
error: syntax error, unexpected DOLLAR_CURLY, at (string):1:16

Q: Can it be eval-ed from string?

A: Yes, but it is not recommended as "eval" is generally regarded as an easy to abuse language feature. It is possible but only via the store (not as bad as "import from derivation", but still not suitable for hot code paths):

nix-repl> let code = "(x: x) ''id function was called''"; in import (builtins.toFile "eval" code)
"id function was called"