Deprecated Features - Lix Reference Manual

## Keyboard shortcuts

Press `←` or `→` to navigate between chapters

Press `S` or `/` to search in the book

Press `?` to show this help

Press `Esc` to hide this help

This section describes the notion of deprecated features, and how it fits into the big picture of the development of Lix.

# What are deprecated features?

Deprecated features are disabled by default, with the intent to eventually remove them. Users must explicitly enable them to keep using them, by toggling the associated deprecated feature flags. This allows backwards compatibility and a graceful transition away from undesired features.

# Which features can be deprecated?

Undesired features should be soft-deprecated by yielding a warning when used for a significant amount of time before the can be deprecated. Legacy obsolete feature with little to no usage may go through this process faster. Deprecated features should have a migration path to a preferred alternative.

# Lifecycle of a deprecated feature

This description is not normative, but a feature removal may roughly happen like this:

1. Add a warning when the feature is being used.
2. Disable the feature by default, putting it behind a deprecated feature flag.

- If disabling the feature started out as an opt-in experimental feature, turn that experimental flag into a no-op or remove it entirely. For example, `--extra-experimental-features no-url-literals` becomes `--extra-deprecated-features url-literals`.

3. Decide on a time frame for how long that feature will still be supported for backwards compatibility, and clearly communicate that in the error messages.

- Sometimes, automatic migration to alternatives is possible, and such should be provided if possible
- At least one NixOS release cycle should be the minimum

4. Finally remove the feature entirely, only keeping the error message for those still using it.

# Relation to language versioning

Obviously, removing anything breaks backwards compatibility. In an ideal world, we'd have SemVer controls over the language and its features, cleanly allowing us to make breaking changes. See https://wiki.lix.systems/books/lix-contributors/page/language-versioning and RFC 137 for efforts on that. However, we do not live in such an ideal world, and currently this goal is so far away, that "just disable it with some back-compat for a couple of years" is the most realistic solution, especially for comparatively minor changes.

# Currently available deprecated features

## `ancient-let`

The ancient `let { body = …; … }` syntax is deprecated.

Use the `let … in` syntax instead.

### Timeline

- 2024-09-18, 2.92.0: Introduced as soft deprecation with a warning. [CL 1787]
- 2026-01-29, 2.95.0: Upgraded the warning to a parse error. [CL 5039]

## `broken-string-escape`

In Nix, string literals define syntax for escaping special characters like `\n`. Only a limited set of escape rules are defined. All characters without defined escape sequence escape "as themselves", e.g. `"\f"` becomes simply `f` instead of a form feed character. Using these fallback escape sequences is now deprecated, because all usage sites in the wild found so far have been proven to be erroneous, where the string did not end up the way the author likely intended. For example when writing a regex in Nix, `"\."` will evaluate to the pattern `.` instead of the probably intended `\.` for matching a literal dot character, for which `"\\."` would have been correct instead.

To fix this, carefully evaluate each usage site for its intended usage and either remove the backslash or add a second backslash depending on the context.

### Timeline

- 2024-12-12, 2.95.0: Introduced as a warning. [CL 2310]

## `broken-string-indentation`

Allow indented strings (those starting with `''`) even when the indentation stripping will produce incorrect and probably unintended results. Affected strings are:

- Single line indented strings that start with whitespace: `'' foo''` will be stripped of its leading space to `foo`. To fix this, convert the string to `"` or manually concatenate in the leading whitespace.
- Multi line indented strings with text on the first line:

      ''foo
        bar
      ''

  Having text on the first line here will completely disable the indentation stripping, which is unlikely desired. To fix this, move the contents of the first line down by one line.

### Timeline

- 2026-01-30, 2.95.0: Introduced as a warning. [CL 2092]

## `cr-line-endings`

CR (`\r`) and CRLF (`\r\n`) are deprecated as line delimiters in Nix code. The reason for the CR line ending deprecation is that no OS still uses them anymore. The reason for the CRLF line ending deprecation is that there is a fatal bug in the indentation-stripping logic of indented strings, breaking all indented strings when CRLF indentation is used. In a future language revision, the CRLF line endings will be allowed again but with fixed semantics.

To fix this, convert all files to have regular `\n` endings and disable all software which performs platform-specific normalizations.

### Timeline

- 2025-02-05, 2.93.0: Introduced as a parser error. [CL 2475]

## `floating-without-zero`

The short-hand notation for floating point numbers `.123` instead of `0.123` is deprecated. Floating point literals are seldomly used, and saving one character adds little benefit. This deprecation will free the syntax for possible future new language feature (See NixOS RFC 181).

To fix this, add a zero before the dot.

### Timeline

- 2026-01-30, 2.95.0: Introduced as warning. [CL 2311]

## `nix-path-shadow`

Shadowing `<nix/fetchurl.nix>` by configuring the nix path to a value containing `nix=/some/path` is deprecated. The namespace `nix` in the Nix path is reserved for usage in internal code, and overriding it may result in nontrivial breakage.

### Timeline

- 2025-11-23, 2.95.0: Introduced as an evaluation-time warning. [CL 4632]

## `nul-bytes`

Raw NUL bytes (`\0`) in Nix string literals are deprecated.

### Timeline

- 2025-02-05., 2.93.0: Introduced as a parser error. [CL 2476]

## `or-as-identifier`

Back when the `or` operator was introduced, instead of making it a proper keyword, the syntax was adapted in attempt of making it a context-sensitive keyword without disrupting existing code. This attempt has backfired, because it causes glitches in the operator precedence when a variable is called `or`: `let or = 1; in [ (x: x) or ]` evaluates to a list with one element, but `let nor = 1; in [ (x: x) nor ]` evaluates to a list with two elements. These old backwards compatibility hacks are deprecated in favor of treating `or` as a full language keyword.

To fix this, rename affected attributes or put the attribute name in quotes (`"or"`).

### Timeline

- 2026-01-30, 2.95.0: Introduced as a warning. [CL 4764]

## `rec-set-dynamic-attrs`

Dynamic attrs (attrs with interpolation in the key) are deprecated in `rec` attrsets. This is because the recursive dynamics fundamentally do not work with dynamic attributes, which is why dynamic attributes are currently evaluated after the recursive attributes, and without the recursive semantics.

### Timeline

- 2026-01-30, 2.95.0: Introduced as a parser warning. [CL 4652, CL 5067]

## `rec-set-merges`

Parsetime attribute set merging, as done e.g. by `{ foo.bar = 1; foo.baz = 2; }`, has confusing semantics when only one of the attribute sets is marked as recursive: Whether the merged attrs is marked as recursive or not is dependent on the declaration order because of implementation quirks. Because the merging behavior is relevant for the check for missing or duplicate attributes, this can have confusing second-order effects. See https://github.com/NixOS/nix/issues/9020 for more examples. Because the parser cannot be fixed without introducing breaking changes to the language, a check has been introduced to forbid all code that would trigger the confusing behavior: Two attributes can be merged only if both or neither of them are marked as recursive.

To fix this, manually perform the merge by moving the affected attribute declarations.

### Timeline

- 2026-01-30, 2.95.0: Introduced as parser error. [CL 4638]

## `rec-set-overrides`

The magic symbol `__overrides` in recursive attribute sets is deprecated. It was introduced in the early days of the language before the widespread use of overlays and is not needed anymore.

To fix this, use fix point functions (e.g. `lib.fix` in Nixpkgs) instead.

### Timeline

- 2024-09-18, 2.92.0: Introduced as soft deprecation with a warning. [CL 1744]
- 2026-01-29, 2.95.0: Upgraded the warning to a parse error. [CL 5040]

## `shadow-internal-symbols`

Shadowing symbol names used internally by the parser is deprecated. As an example, `5 - 3` internally expands to `__sub 5 3` in the parser. `__sub` is a symbol name in global scope, which could be shadowed by let bindings like any other. Shadowing internal symbols is deprecated because it can lead to confusing unintended semantics in code. Using this to override operators with custom implementations is not supported and will lead to unintended semantics. (For example, overriding `__lessThan` will be ignored within builtin functions that perform comparison operations like sorting.)

Note that the check happens at usage site, so `let __sub = null; in body` remains allowed for as long as `body` does not contain a subtraction operation. Similarly, `let __sub = null; in __sub` remains allowed here because the explicit `__sub` usage is obvious and there is less potential for confusion.

Affected symbol names:

- `__sub`
- `__mul`
- `__div`
- `__lessThan`
- `true`
- `false`
- `null`

### Timeline

- 2024-11-18, 2.92.0: Introduced as a parser error for the symbols `__sub`, `__mul`, `__div`, `__lessThan`. [CL 2206, CL 2295]
- 2026-01-30, 2.95.0: Expanded the deprecation to also forbid shadowing the builtin symbols `null`, `true` and `false`. [CL 4788]

## `tokens-no-whitespace`

The grammar has several bugs around token boundaries, where two adjacent literals are not always required to have whitespace between them: `0a`, `0.00.0` or `foo"1"2`. This results in the following unexpected behaviors in the language:

- In lists, adjacent tokens will be parsed as several distinct elements, but outside of lists they will be parsed as a function application.
- Because leading zeroes in floating point literals are not allowed, `00012.3` unexpectedly parses as `12 0.3`.
- Nix does not have hexadecimal number literal notation, but a user naively typing `0x10` will not receive a parser error (because it validly parses as `0 x10` instead).

Because the parser cannot be fixed without introducing breaking changes to the language, all token sequences with known confusing semantics are deprecated with a parse error. To fix this, insert whitespace between tokens to properly separate them.

### Timeline

- 2026-01-30, 2.95.0: Introduced as parser error. [CL 4782]

## `url-literals`

URL literals are unquoted string literals containing URLs directly as part of the Nix language syntax. This was deprecated because it needlessly complicates the syntax for the little benefit of merely saving two characters. Additionally, it is a cause of inconsistencies in the language: `x:x` is a (URL) string but `x: x` and `_:x` are functions.

To fix this, put the URL in string quotation marks instead: `{ url = https://github.com/NixOS/nixpkgs; }` → `{ url = "https://github.com/NixOS/nixpkgs"; }`

### Timeline

- 2024-08-17, 2.92.0: Removed the old `no-url-literals` experimental feature and turned it into a parse error by default. [CL 1785]