-
-
Notifications
You must be signed in to change notification settings - Fork 159
[RFC 0190] Ban with expression in nixpkgs
#190
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,123 @@ | ||
| --- | ||
| feature: ban-with-expressions | ||
| start-date: 2025-10-07 | ||
| author: 6543 | ||
| co-authors: | ||
| shepherd-team: | ||
| shepherd-leader: | ||
| related-issues: | ||
| - https://github.com/NixOS/rfcs/pull/120 | ||
| - https://github.com/NixOS/nixpkgs/pull/413393 | ||
| --- | ||
|
|
||
| # Summary | ||
| [summary]: #summary | ||
|
|
||
| Completely prohibit the usage of `with` expressions in nixpkgs to improve code clarity, maintainability, and enable better static analysis. Instead using explicit `inherit` statements should be used if bringing attributes into scope is desired. | ||
|
|
||
| # Motivation | ||
| [motivation]: #motivation | ||
|
|
||
| The `with` expression in Nix creates several problems that impact code quality and tooling: | ||
|
|
||
| - **Unclear variable origins**: When reading code with `with`, it's unclear where variables come from without evaluating the expression | ||
| - **Difficult static analysis**: Tools cannot determine variables without running full nix evaluation | ||
| - **Debugging difficulties**: Error messages become less helpful when variable sources are ambiguous | ||
| - **Code review challenges**: Reviewers can not simply relay on a diff view and must read the full code and not miss any scope | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. technically, the proposed
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. true ... will remove that line |
||
|
|
||
| The `inherit` statement provides a better alternative that is: | ||
|
|
||
| - **Explicit**: Clearly shows which attributes are being brought into scope | ||
| - **Traceable**: Easy to see where each variable comes from | ||
| - **Analyzable**: Static analysis tools can easily understand the code structure | ||
| - **Debuggable**: Error messages can point to specific inherit statements | ||
|
|
||
| # Detailed design | ||
| [design]: #detailed-design | ||
|
|
||
| 1. **Immediate prohibition**: New code in Nixpkgs must not use `with` expressions | ||
| 2. **Gradual migration**: Existing `with` expressions should be migrated to `inherit` over time | ||
| 3. **CI enforcement**: Add automated checks to prevent new `with` expressions from being merged | ||
| 4. **Documentation updates**: Update Nixpkgs contributor guidelines to reflect this policy | ||
|
|
||
| # Examples and Interactions | ||
| [examples-and-interactions]: #examples-and-interactions | ||
|
|
||
| ## Simple attribute access | ||
|
|
||
| ```nix | ||
| # Before | ||
| with pkgs; [ | ||
| git | ||
| vim | ||
| curl | ||
| ] | ||
|
|
||
| # After | ||
| let | ||
| inherit (pkgs) git vim curl; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don't forget to show an example with > 4 items, where nixfmt will set one item per line, e.g. let
inherit (pkgs)
git
vim
curl
htop
;
in [
git
vim
curl
htop
]
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. haha yes 🫠 in that case [
pkgs.git
pkgs.vim
pkgs.curl
pkgs.htop
]is still more text but not as bad as your ting 😅
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, I prefer this. But this is also rare in nixpkgs anyways due to the way
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. about lists: #190 (comment) |
||
| in [ | ||
| git | ||
| vim | ||
| curl | ||
| ] | ||
| ``` | ||
|
|
||
| ## Function arguments | ||
|
|
||
| ```nix | ||
| # Before | ||
| { pkgs, ... }: | ||
| with pkgs; { | ||
| buildInputs = [ git vim ]; | ||
| } | ||
|
|
||
| # After | ||
| { pkgs, ... }: | ||
| let | ||
| inherit (pkgs) git vim; | ||
| in { | ||
| buildInputs = [ git vim ]; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This will be super annoying to type 🫠
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In cases like this I would much rather: If I am going to repeat something I may as well repeat the path and get the benefit of being able to see a more qualified source locally. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the examples could be improved. Because I'd use more inline usages > jumping to an inherit variable unless the usage becomes great enough to look cleaner from it.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. well if it's a package that should be in the inputs ... so you have them standing twice anyway else just use |
||
| } | ||
| ``` | ||
|
|
||
| ## Nested scopes | ||
|
|
||
| ```nix | ||
| # Before | ||
| with pkgs; { | ||
| meta = with lib; { | ||
| license = with licenses; [ mit ]; | ||
| }; | ||
| } | ||
|
|
||
| # After | ||
| let | ||
| inherit (pkgs.lib.licenses) mit; | ||
| in { | ||
| meta = { | ||
| license = [ mit ]; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. until now,
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nothing jet, just proposing to not use and this are examples, i would jsut use the full prefix
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
What I want to get at is, the benefits/downsides you mentioned apply to
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This would get really annoying when a project has multiple licenses. What do you propose for those instances? I think the following looks really ugly. license = [
lib.licenses.mit
lib.licenses.some-custom-license
];As compared to: license = with lib.licenses; [ mit some-custom-license ];All in all, I don't see an issue with using There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. perhaps a better approach would be some sort of linting action that shows a warn or even errors on newly changed paths if it detects
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
i change the rfc to propose an helper to not get ugly code ;) but as of right now with: license = with lib.licenses; [ mit some-custom-license ];you expect all items to come from lib.licenses right? but technikally that has to be not the case! some-custom-license can be defined elsewhere
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Again, then please be selective about it. Unless there is a second, non-top-level |
||
| }; | ||
| } | ||
| ``` | ||
|
|
||
| # Drawbacks | ||
| [drawbacks]: #drawbacks | ||
|
|
||
| - **Increased verbosity**: Code may become slightly longer due to explicit prefixes or inherit statements | ||
| - **Migration effort**: Existing codebase requires changes | ||
| - **Learning curve**: Contributors familiar with `with` need to adapt to new patterns | ||
| - **Backward incompatible**: requires changing Nix code used "in the wild" | ||
|
|
||
| However, these drawbacks are outweighed by the benefits of clearer, more maintainable code. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is true for a top-level
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, I think a carve-out for small local scopes even as small as I am generally a fan of being explicit but in these case it just seems worth it and the risk of confusion is low. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agreed, I have tried to migrate everything I touch to an inherit > with style. But, have no problems with last level
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. well tjats the problem you expect implizite that the elements in the list come from within the scope colapsed by with but it does not have to!
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. but i get why its very handy and i think if it's your own code and you dont colab its fine as you know what you do? but for nixpkgs we should be explizite to get better code quality. why not have a simple helper to still get handy simple lists? lib.elements = stringList: map:
builtins.map (key: map.${key}) stringList;this func will fail if you specify an item that is not comming from the map -> explizite & matches the expectations we use the with to list e.g. packages ... let
myMap = { a = 1; b = 2; c = 3; d = 4; };
keys = [ "a" "c" "d" ]; # all keys exist
in
lib.elements keys myMap
# Result: [ 1 3 4 ] |
||
|
|
||
| # Alternatives | ||
| [alternatives]: #alternatives | ||
|
|
||
| Just discourage usage of `with`, but in this case most things still can not be statically checked. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it is worthy to mention https://gerrit.lix.systems/c/lix/+/1987/11 (and the following commits as well) as an attempt to provide a safer alternative to the most prominent use cases of |
||
|
|
||
| # Unresolved questions | ||
| [unresolved]: #unresolved-questions | ||
|
|
||
| # Future work | ||
| [future]: #future-work | ||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is true but barely. There is only ambiguity when there are nested
withexpressions. For example:In this case
xis always the 1 from the above binding.In this case
yis alwaysscope.y.The only trouble is:
Now you don't know if this is
scope-a.xorscope-b.x.For this point we could just ban nested
with.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
my main problem is with reviewing stuff that come out of nowhere ... yes nix repl will tell me but ... :/
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't understand what you mean by "come out of nowhere".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The two "problems" mention in the text is inaccurate and only happen on nested
with. It seems the author is criticizing JavaScript'swithwhile willing to ban Nix'swith. There is a comparison, and the most notable difference is that single-levelwithin Nix can always be syntactically (statically) desugared without the need to evaluate what's inside the attrset ofwith.If you are using nil language server, variables from and not from

withare also highlighted differently.I have many similar lists with tens of elements. Manually prefixing
pkgs.or usinginheritwould at least double the code size and hurt readability more thanwithby introducing 50%+ syntax noise, not mentioning keystrokes and writability.It is already a anti-pattern to use nested
withor top-level omnivorouswiththat covers too many code. Only banning these complex cases makes sense to me.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@oxalica I agree! There should be some exceptions, like in the example you pointed out. I use
withfor declaring lists of packages pretty frequently and couldn't imagine having to prefixpkgsto every single package in the list, especially messy when you're dealing with lists that can have over 30 packages 👀