Case statement (expr.)

Hello Nix people,

I can’t find case statement in Nix lang/libraries and google doesn’t give me anything. Is there a case statement (expression / function)?

Or do I need to write something like this:

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";

Thank you

The Nix language is very small, there is no case construct. Here is the language reference, see in particular language constructs.

If the value you’re switching over is guaranteed to be a string, you could abuse attribute sets by encoding the outcomes as attributes and use attr.${value} or default for conciseness.

2 Likes

I do what Fricklefthandwerk suggests and it works great!

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

The beauty of this is that there’s no need for an explicit abort since a wrong value causes a build failure.

5 Likes

Yes, in my case it’s just a string. I’d prefer to use something like Enum / Symbol / Keyword, but as you correctly pointed out, Nix is simple so I need to use a string.

My assumption was that somebody created a lib / function to solve the ugly if-then-if-… But attr.set is enough! Thank you @fricklerhandwerk and @emmanuelrosa !

btw, if-else chains can look a little better if you put else if on the same line. For cases where x is not a simple string, this can more resemble a case statement

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";
5 Likes

Good point. Thank you.

Can someone explain how this dark magic exactly works? I do feel a bit like entering the territory of voodoo :wink:

Also, are there any down- /upsides between this approach and @figsoda if-else chain?

I personally like the idea of being able to fail eval with a more specific error message…

Please check String interpolation - Nix Reference Manual on the details, which was added since my last post here. Feel free to ask questions if that leaves something unclear and I can improve that page.

For a more specific error message you could use throw in the fallback case:

attrs.${value} or throw "not supported: ${value}"
1 Like

.${value} or (aka attribute selection) binds stronger than throw "..." (aka function application), see Operators - Nix Reference Manual (also written by @fricklerhandwerk :100:), so this needs some parenthesis:

attrs.${value} or (throw "not supported: ${value}")
4 Likes

Thanks, very helpful reading.

In the process of coming up with a solution my mind went to ponder if string interpolation is possible with in function names?

e.g. (random possibly not working code) when trying to call a more specific function based on an argument of a more generic one.

mkGeneric = { flavor, ... }@args: mkSpecial${flavor} args

No, that’s not possible. As opposed to paths and attribute names, which are interchangeable with strings (literally: { "foo" = 1; } is legal), function names are symbols.

Someone emulated this
nixpkgs/pkgs/build-support/coq/extra-lib.nix at master · NixOS/nixpkgs (github.com)

  /* Emulate a "switch - case" construct,
   instead of relying on `if then else if ...` */
  /* Usage:
  ```nix
  switch-if [
    if-clause-1
    ..
    if-clause-k
  ] default-out

where a if-clause has the form { cond = b; out = r; }
the first branch such as b is true */

1 Like