Nix Language Quirks: Difference between revisions
→Mimicking case statements with attribute sets: remove typo from example |
Some formatting changes and link to a separate quirk list |
||
(One intermediate revision by the same user not shown) | |||
Line 36: | Line 36: | ||
Note, that it isn't equivalent to <code>with rec { x = 1; y = x + 1; body = y; }; body</code> because of mentioned <code>with</code> and <code>let</code> quirk, but is same as <code>rec { x = 1; y = x + 1; body = y; }.body</code> | Note, that it isn't equivalent to <code>with rec { x = 1; y = x + 1; body = y; }; body</code> because of mentioned <code>with</code> and <code>let</code> quirk, but is same as <code>rec { x = 1; y = x + 1; body = y; }.body</code> | ||
== Default values are not bound in @ syntax == | == Default values are not bound in <code>@</code> syntax == | ||
Destructured arguments can have default values, but those default values are part of the full function argument. | Destructured arguments can have default values, but those default values are part of the full function argument. | ||
Line 87: | Line 87: | ||
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. | 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. | ||
== builtins.replaceStrings key match on "" == | == <code>builtins.replaceStrings</code> key match on "" == | ||
Syntax: | Syntax: | ||
Line 246: | Line 246: | ||
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>. | 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>. | ||
== <code>builtins.toString</code> handling of <code>true</code> and <code>false</code> is inconsistent == | |||
<syntaxHighlight lang=nix> | |||
nix-repl> builtins.toString true | |||
"1" | |||
nix-repl> builtins.toString false | |||
"" | |||
</syntaxHighlight> | |||
== Nix Language FAQ == | == Nix Language FAQ == | ||
Line 280: | Line 290: | ||
nix-repl> let code = "(x: x) ''id function was called''"; in import (builtins.toFile "eval" code) | nix-repl> let code = "(x: x) ''id function was called''"; in import (builtins.toFile "eval" code) | ||
"id function was called"</syntaxHighlight> | "id function was called"</syntaxHighlight> | ||
= Resources = | |||
* [https://md.darmstadt.ccc.de/xtNP7JuIQ5iNW1FjuhUccw# A separately maintained list of Nix language quirks] | |||
[[Category:Nix Language]] | [[Category:Nix Language]] |
Latest revision as of 04:37, 15 December 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.
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
.
builtins.toString
handling of true
and false
is inconsistent
nix-repl> builtins.toString true
"1"
nix-repl> builtins.toString false
""
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"