Skip to content

Initial bindings/examples for writing Nix functions in Rust#1

Merged
edolstra merged 54 commits intomainfrom
initial
Jan 21, 2026
Merged

Initial bindings/examples for writing Nix functions in Rust#1
edolstra merged 54 commits intomainfrom
initial

Conversation

@edolstra
Copy link
Collaborator

@edolstra edolstra commented Sep 19, 2025

This adds a crate for writing WASM functions in Rust that can be executed by the Nix evaluator using builtins.wasm.

To try it out:

# nix shell .#nix # to get a version of Nix that has `builtins.wasm`

# nix build # to build the WASM functions

# ls -l ./result/
-r--r--r-- 3 root root  17396 Jan  1  1970 nix_wasm_plugin_fib.wasm
-r--r--r-- 3 root root  64292 Jan  1  1970 nix_wasm_plugin_ini.wasm
-r--r--r-- 3 root root  33277 Jan  1  1970 nix_wasm_plugin_mandelbrot.wasm
-r--r--r-- 3 root root  29635 Jan  1  1970 nix_wasm_plugin_test.wasm
-r--r--r-- 3 root root 205035 Jan  1  1970 nix_wasm_plugin_yaml.wasm

# nix eval --impure --expr 'builtins.wasm ./result/nix_wasm_plugin_fib.wasm "fib" 40'
warning: '/nix/store/1c9yg0mv…-nix-wasm-rust-0.1.0/nix_wasm_plugin_fib.wasm' function 'fib': greetings from WASM!
165580141

# nix eval --raw --impure --expr 'builtins.wasm ./result/nix_wasm_plugin_mandelbrot.wasm "mandelbrot" { width = 150; }'

Summary by CodeRabbit

  • New Features

    • Adds multiple WASM plugins (INI parser, YAML parse/serialize, Mandelbrot visualizer, Fibonacci, grep, test utilities) and a Rust runtime for host value interop.
  • Chores

    • Adds CI workflow, multi-system Nix flake build, VS Code build task, workspace configuration, and expanded .gitignore.
  • Tests

    • Adds extensive test suites and fixtures exercising plugins and end-to-end checks.
  • Documentation

    • Updates README with usage examples and plugin guidance.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 19, 2025

📝 Walkthrough

Walkthrough

Adds a Rust FFI core crate and multiple cdylib WASM plugin crates with tests and fixtures, a multi-system Nix flake and CI workflow, workspace Cargo updates, editor/task and .gitignore entries, and per-system devShells/checks to build and run plugins via builtins.wasm.

Changes

Cohort / File(s) Summary
CI & Editor
/.github/workflows/ci.yml, .gitignore, .vscode/tasks.json
Add GitHub Actions "CI" workflow (push/PR to main) that installs Nix and runs nix flake check; update .gitignore; add VSCode cargo task targeting wasm32-unknown-unknown.
Workspace Manifest
Cargo.toml
Add workspace members for multiple nix-wasm-* crates and tighten release profile (lto, opt-level='z', strip).
Nix Flake
flake.nix
New flake producing packages, devShells, and checks; builds cdylib WASM via rustPlatform.buildRustPackage, runs wasm-opt, and provides per-system check/dev shells.
FFI Core Library
nix-wasm-rust/Cargo.toml, nix-wasm-rust/src/lib.rs
New crate exposing Value wrapper, Type enum, init/panic/warn hooks, and FFI-backed constructors/accessors and call helpers used by plugins.
Plugins (cdylib WASM)
nix-wasm-plugin-*/Cargo.toml, nix-wasm-plugin-*/src/lib.rs
Add multiple plugin crates exporting C-ABI entrypoints: fib, fromINI, mandelbrot, fromYAML/toYAML, grep, and test helpers (range, strictMap, lazyMap, sum, double, counter).
Plugin Tests & Fixtures
**/tests/*.nix, **/tests/*.exp, **/tests/*.{ini,yaml}
Add per-plugin Nix test expressions, expected outputs, and input fixtures (fib, ini, yaml, mandelbrot, grep, plugin tests).
Docs
README.md
Add README with crate overview, build/eval steps, and example builtins.wasm usage.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    actor User as "User / Nix expression"
    participant Nix as "Nix evaluator"
    participant Loader as "builtins.wasm (loader)"
    participant Plugin as "WASM module (plugin)"
    participant FFI as "nix-wasm-rust FFI"

    Note over User,Nix: Nix expression invokes builtins.wasm to call a plugin
    User->>Nix: evaluate expression (calls builtins.wasm)
    Nix->>Loader: request instantiate/load `<plugin>.wasm`
    Loader->>Plugin: invoke exported function (pass opaque Value)
    Plugin->>FFI: call FFI helpers (get_*/make_*, call, warn)
    FFI-->>Plugin: return primitives / handles
    Plugin->>Plugin: perform plugin logic (compute/parse/render/convert)
    Plugin->>FFI: construct result via make_* APIs
    FFI-->>Plugin: return result handle
    Plugin-->>Loader: return Value to loader
    Loader-->>Nix: deliver result into evaluation
    Nix-->>User: evaluation returns result
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰 I munched a flake and stitched a tiny crate,

I whispered FFI tales into WASM's gate,
Fib hops, INI folds, YAML streams that gleam,
Mandelbrot blooms like a rabbit's dream,
CI hums softly — the plugins wake.

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and concisely describes the main objective: introducing Rust bindings and examples for writing Nix functions in Rust via WASM, which aligns with the PR's comprehensive addition of nix-wasm-rust library and multiple plugin examples.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Comment @coderabbitai help to get the list of available commands and usage tips.

@edolstra edolstra force-pushed the initial branch 2 times, most recently from 9ab433c to d2cd6cc Compare September 29, 2025 14:18
Flake lock file updates:

• Updated input 'nix':
    'github:DeterminateSystems/nix-priv/c1279376ce2b948c9835a1072944f29f47e6f556?narHash=sha256-%2B1E1dQsjkH6J/H5jZdChvciyHVrURb%2BsPPbQy7JLvTU%3D' (2025-09-19)
  → 'github:DeterminateSystems/nix-priv/a8a675040f8beda9cae3b03a4864bdba1d14d753?narHash=sha256-LxWPE%2BNkDBdEhx9fJA2X9vUWEgqbJvdZbuYKXJqajfY%3D' (2025-09-30)
@edolstra edolstra marked this pull request as ready for review December 25, 2025 14:19
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🧹 Nitpick comments (8)
flake.nix (1)

7-7: Verify the custom Nix fork dependency.

The flake depends on a custom Nix fork with a wasm branch, suggesting the builtins.wasm functionality used in tests is not yet in upstream Nix. Ensure this is documented in the README so users understand they need this specific Nix version.

Do you want me to help create documentation explaining this dependency?

nix-wasm-plugin-fib/src/lib.rs (1)

7-13: Exponential time complexity in recursive Fibonacci.

The naive recursive implementation has O(2^n) complexity. While this works for the test case (n=33), larger inputs will be extremely slow or may cause stack overflow. For a demo/example plugin this is likely acceptable, but consider noting this limitation or using an iterative approach if real-world usage is expected.

🔎 Optional iterative alternative
-    fn fib2(n: i64) -> i64 {
-        if n < 2 {
-            1
-        } else {
-            fib2(n - 1) + fib2(n - 2)
-        }
-    }
+    fn fib2(n: i64) -> i64 {
+        if n < 2 {
+            return 1;
+        }
+        let (mut a, mut b) = (1i64, 1i64);
+        for _ in 2..=n {
+            (a, b) = (b, a + b);
+        }
+        b
+    }
nix-wasm-plugin-ini/src/lib.rs (1)

8-8: Consider graceful error handling for malformed INI input.

Using expect() will cause a panic if the INI parsing fails, which may abort the WASM module. Since this parses user-provided data (not system initialization), consider returning an error value or propagating the error more gracefully to the caller.

Based on the retrieved learnings, unwraps are acceptable during Nix initialization for catastrophic failures, but this is user input parsing where malformed data is a recoverable scenario.

nix-wasm-plugin-yaml/src/lib.rs (4)

4-32: Consider defensive handling for Yaml::Real conversion.

Line 6: as_f64().expect() should always succeed for Yaml::Real, but the underlying yaml-rust2 implementation returns None if the string can't be parsed as f64. Consider handling this edge case gracefully rather than panicking.

The panic-based error handling for non-string keys (line 22) and unimplemented types (line 30) is acceptable for a plugin context, but be aware these will abort the WASM execution on malformed input.


34-47: Missing type validation on input argument.

The function assumes arg is a string and directly calls get_string() without verifying arg.get_type() == Type::String. If called with a non-string value, this could lead to undefined behavior depending on the host's FFI implementation.

🔎 Proposed fix
 #[no_mangle]
 pub extern "C" fn fromYAML(arg: Value) -> Value {
+    assert!(matches!(arg.get_type(), Type::String), "fromYAML expects a string argument");
     let s = arg.get_string();
 
-    let yaml = YamlLoader::load_from_str(&s).unwrap();
+    let yaml = YamlLoader::load_from_str(&s).expect("invalid YAML input");
 
     let mut docs = vec![];

49-75: Float serialization may lose precision; error message could be clearer.

  1. Line 52: Using format!("{}", v.get_float()) for Yaml::Real may not preserve the original precision or format. Consider using {:?} or a more precise formatter if round-trip fidelity matters.

  2. Lines 70-73: The panic message shows the type as a raw integer (v.get_type() as u64), which isn't user-friendly. Consider using Debug formatting if Type implements it.

🔎 Proposed improvement
-        Type::Float => Yaml::Real(format!("{}", v.get_float())),
+        Type::Float => Yaml::Real(v.get_float().to_string()),
...
-        _ => panic!(
-            "Nix type {} cannot be serialized to YAML",
-            v.get_type() as u64
-        ),
+        t => panic!(
+            "Nix type {:?} cannot be serialized to YAML",
+            t
+        ),

77-89: Missing type validation on input argument.

Similar to fromYAML, this function assumes arg is a list without validation. Add a type check for robustness.

🔎 Proposed fix
 #[no_mangle]
 pub extern "C" fn toYAML(arg: Value) -> Value {
+    assert!(matches!(arg.get_type(), Type::List), "toYAML expects a list argument");
     let mut out_str = String::new();
 
     for v in arg.get_list() {
         let yaml = to_yaml(v);
         let mut emitter = YamlEmitter::new(&mut out_str);
-        emitter.dump(&yaml).unwrap();
+        emitter.dump(&yaml).expect("failed to emit YAML");
         out_str.push('\n');
     }
nix-wasm-rust/src/lib.rs (1)

12-30: Address FIXME: externref consideration.

The FIXME at line 12 notes a potential improvement using WebAssembly's externref type. This would provide better type safety and garbage collection integration for Value references. Current approach using u32 handles works but requires manual lifecycle management on the host side.

Would you like me to open an issue to track the externref migration?

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between aee172e and 156e231.

⛔ Files ignored due to path filters (2)
  • Cargo.lock is excluded by !**/*.lock
  • flake.lock is excluded by !**/*.lock
📒 Files selected for processing (27)
  • .github/workflows/ci.yml
  • .gitignore
  • .vscode/tasks.json
  • Cargo.toml
  • flake.nix
  • nix-wasm-plugin-fib/Cargo.toml
  • nix-wasm-plugin-fib/src/lib.rs
  • nix-wasm-plugin-fib/tests/fib.exp
  • nix-wasm-plugin-fib/tests/fib.nix
  • nix-wasm-plugin-ini/Cargo.toml
  • nix-wasm-plugin-ini/src/lib.rs
  • nix-wasm-plugin-ini/tests/test1.exp
  • nix-wasm-plugin-ini/tests/test1.ini
  • nix-wasm-plugin-ini/tests/test1.nix
  • nix-wasm-plugin-mandelbrot/Cargo.toml
  • nix-wasm-plugin-mandelbrot/src/lib.rs
  • nix-wasm-plugin-yaml/Cargo.toml
  • nix-wasm-plugin-yaml/src/lib.rs
  • nix-wasm-plugin-yaml/tests/fromYAML.exp
  • nix-wasm-plugin-yaml/tests/fromYAML.nix
  • nix-wasm-plugin-yaml/tests/test1.yaml
  • nix-wasm-plugin-yaml/tests/toYAML.exp
  • nix-wasm-plugin-yaml/tests/toYAML.nix
  • nix-wasm-plugin-yaml/tests/toYAML2.exp
  • nix-wasm-plugin-yaml/tests/toYAML2.nix
  • nix-wasm-rust/Cargo.toml
  • nix-wasm-rust/src/lib.rs
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: RossComputerGuy
Repo: DeterminateSystems/secure-build PR: 32
File: src/commands/scan.rs:139-162
Timestamp: 2025-10-21T19:23:14.234Z
Learning: In the secure-build repository (Rust), unwraps during Nix initialization (NixStore::open, FlakeSettings::new, EvalStateBuilder, etc.) are acceptable because failures indicate catastrophic system conditions like out-of-memory where no recovery is possible.
📚 Learning: 2025-10-21T17:29:29.023Z
Learnt from: RossComputerGuy
Repo: DeterminateSystems/secure-build PR: 32
File: flake.nix:175-177
Timestamp: 2025-10-21T17:29:29.023Z
Learning: In Nix flake.nix files using Rust with bindgen, when `rustPlatform.bindgenHook` is included in `nativeBuildInputs`, it automatically sets up `LIBCLANG_PATH` and other bindgen-related environment variables. An explicit `LIBCLANG_PATH` setting in the package derivation is not required, even if it appears in the devShell.

Applied to files:

  • nix-wasm-plugin-ini/Cargo.toml
  • flake.nix
  • nix-wasm-rust/Cargo.toml
📚 Learning: 2025-10-21T19:23:14.234Z
Learnt from: RossComputerGuy
Repo: DeterminateSystems/secure-build PR: 32
File: src/commands/scan.rs:139-162
Timestamp: 2025-10-21T19:23:14.234Z
Learning: In the secure-build repository (Rust), unwraps during Nix initialization (NixStore::open, FlakeSettings::new, EvalStateBuilder, etc.) are acceptable because failures indicate catastrophic system conditions like out-of-memory where no recovery is possible.

Applied to files:

  • flake.nix
  • nix-wasm-rust/Cargo.toml
🧬 Code graph analysis (3)
nix-wasm-plugin-ini/src/lib.rs (1)
nix-wasm-rust/src/lib.rs (4)
  • make_string (68-73)
  • make_string (70-70)
  • make_attrset (143-148)
  • make_attrset (145-145)
nix-wasm-plugin-fib/src/lib.rs (1)
nix-wasm-rust/src/lib.rs (4)
  • warn (3-10)
  • warn (5-5)
  • make_int (40-45)
  • make_int (42-42)
nix-wasm-plugin-yaml/src/lib.rs (1)
nix-wasm-rust/src/lib.rs (18)
  • make_float (54-59)
  • make_float (56-56)
  • make_int (40-45)
  • make_int (42-42)
  • make_string (68-73)
  • make_string (70-70)
  • make_bool (94-99)
  • make_bool (96-96)
  • make_list (115-120)
  • make_list (117-117)
  • make_attrset (143-148)
  • make_attrset (145-145)
  • make_null (108-113)
  • make_null (110-110)
  • get_float (61-66)
  • get_float (63-63)
  • get_type (33-38)
  • get_type (35-35)
🪛 actionlint (1.7.9)
.github/workflows/ci.yml

19-19: the runner of "actions/checkout@v3" action is too old to run on GitHub Actions. update the action's version to fix this issue

(action)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: ci
🔇 Additional comments (33)
.gitignore (1)

1-5: Well-structured .gitignore for Nix/Rust/WASM project.

The patterns appropriately cover the key artifacts for this project type: WebAssembly binaries, Nix build outputs, Rust compiler artifacts, editor backups, and test outputs. All patterns are syntactically correct and serve clear purposes aligned with the project's technology stack.

Cargo.toml (2)

1-9: LGTM!

The workspace configuration correctly includes all plugin crates and uses resolver 2, which is the current standard for Rust workspaces.


11-13: LGTM!

The release profile optimizations are well-suited for WebAssembly targets. LTO and size optimization (opt-level = 's') will produce smaller WASM binaries, which is important for plugin distribution and loading performance.

.vscode/tasks.json (1)

1-25: LGTM!

The VSCode task configuration correctly targets WASM builds with appropriate settings. The wasm32-unknown-unknown target and release builds align well with the project's WebAssembly plugin focus.

.github/workflows/ci.yml (1)

21-29: LGTM!

The Nix setup with FlakeHub cache and nix flake check --all-systems provides comprehensive multi-system verification for the WASM plugins.

flake.nix (2)

23-57: LGTM!

The build derivation is well-structured with comprehensive testing. The checkPhase runs Nix evaluation tests against expected outputs, and the installPhase applies additional WASM optimization with wasm-opt -O3. The inclusion of the custom Nix CLI from the wasm branch ensures test compatibility.


60-73: LGTM!

The development shell and checks are properly configured. The devShell includes essential Rust development tools and WASM utilities, while checks ensure the build succeeds across all supported systems.

nix-wasm-plugin-fib/tests/fib.nix (1)

1-1: LGTM!

The test correctly invokes the fibonacci WASM plugin with input 33, demonstrating the builtins.wasm integration.

nix-wasm-plugin-fib/tests/fib.exp (1)

1-1: LGTM!

The expected value is correct for fibonacci(33).

nix-wasm-plugin-yaml/tests/fromYAML.nix (1)

1-1: LGTM!

The test correctly loads YAML data from a file and parses it via the WASM plugin, demonstrating the YAML parsing functionality.

nix-wasm-plugin-yaml/tests/toYAML2.nix (1)

1-1: LGTM!

The test exercises YAML serialization with diverse data types including integers, null values, strings, and nested attribute sets, providing good coverage of the toYAML functionality.

nix-wasm-rust/Cargo.toml (1)

1-4: LGTM!

The minimal manifest is appropriate for a core FFI library with no external dependencies.

nix-wasm-plugin-yaml/tests/toYAML2.exp (1)

1-1: LGTM!

Test expectation format is valid.

nix-wasm-plugin-ini/tests/test1.nix (1)

1-1: LGTM!

The Nix expression correctly loads the WASM plugin and invokes the fromINI function with test data.

nix-wasm-plugin-fib/Cargo.toml (1)

1-10: LGTM!

The configuration is correct for a WASM plugin with appropriate cdylib type and path dependency.

nix-wasm-plugin-yaml/tests/test1.yaml (1)

1-22: LGTM!

The multi-document YAML file is well-formed with proper syntax, diverse data types, and correct document separators.

nix-wasm-plugin-ini/tests/test1.ini (1)

1-15: LGTM!

The INI test data is valid and intentionally includes duplicate RequiresMountsFor keys (lines 3-5) to test the plugin's handling of multi-value directives common in systemd unit files.

nix-wasm-plugin-ini/tests/test1.exp (1)

1-1: No action required—behavior is expected.

The rust-ini library (version 0.21) uses a HashMap-based structure for storing INI properties, which means duplicate keys are handled by keeping only the last value. This is standard INI parser behavior. The test correctly validates that when three RequiresMountsFor entries are provided, the final output contains only the last value (/nix/var/nix/db). This is intentional and not a bug.

Likely an incorrect or invalid review comment.

nix-wasm-plugin-ini/Cargo.toml (1)

11-11: No action required. The rust-ini version 0.21 is current—the latest release is 0.21.3 (published 2025-08-30)—and no security vulnerabilities are known for this crate.

Likely an incorrect or invalid review comment.

nix-wasm-plugin-mandelbrot/Cargo.toml (1)

1-11: LGTM!

The manifest is correctly structured for a WASM plugin crate. The cdylib crate type is appropriate for producing a dynamic library compatible with the WASM target, and the dependencies align with the implementation in src/lib.rs.

nix-wasm-plugin-yaml/tests/toYAML.exp (1)

1-1: LGTM!

Test expectation file containing the expected multi-document YAML output for the toYAML round-trip test.

nix-wasm-plugin-yaml/tests/toYAML.nix (1)

1-3: LGTM!

The test correctly exercises the YAML round-trip by parsing test1.yaml with fromYAML and then serializing it back with toYAML.

nix-wasm-plugin-fib/src/lib.rs (1)

1-16: LGTM overall - clean FFI plugin structure.

The function correctly uses the nix-wasm-rust FFI conventions with #[no_mangle] and extern "C". The implementation demonstrates the Value abstraction for input/output exchange with the host.

nix-wasm-plugin-yaml/tests/fromYAML.exp (1)

1-1: LGTM!

Test expectation file containing the expected JSON representation of the parsed YAML multi-document input.

nix-wasm-plugin-yaml/Cargo.toml (1)

1-11: LGTM!

Good choice using yaml-rust2, which is the actively maintained fork of the original yaml-rust crate. The manifest structure correctly follows the established plugin pattern.

nix-wasm-plugin-ini/src/lib.rs (1)

1-24: LGTM - clean implementation of INI-to-attrset conversion.

The function correctly iterates sections and properties, building a nested attribute set structure. The handling of None section names as empty strings is appropriate for representing global (unnamed) INI sections.

nix-wasm-plugin-mandelbrot/src/lib.rs (2)

59-67: Verify the mirroring logic produces the expected output.

The mirroring skips one line (.skip(1)) before reversing, which excludes the center line from duplication. This appears intentional for symmetry, but ensure the final visualization meets expectations—particularly that the aspect ratio and symmetry look correct.


1-92: LGTM - impressive Mandelbrot visualization plugin.

The implementation correctly uses smooth coloring for anti-aliasing effects, proper complex number iteration, and ANSI escape sequences for terminal rendering. The use of num::Complex64 is appropriate for the computation.

nix-wasm-plugin-yaml/src/lib.rs (1)

1-2: LGTM!

Imports are appropriate for the YAML conversion functionality.

nix-wasm-rust/src/lib.rs (4)

3-10: LGTM!

The warn function correctly passes the string as a pointer-length pair to the FFI boundary.


32-66: LGTM!

The type query and primitive constructors/accessors follow correct FFI patterns.


87-113: LGTM!

These accessors and constructors are correctly implemented.


143-148: Verify tuple slice layout compatibility with FFI host.

The make_attrset function passes a pointer to &[(&str, Value)]. The tuple (&str, Value) is not #[repr(C)], so its memory layout is not guaranteed. The FFI host must correctly interpret Rust's tuple layout for (&str, Value), which consists of (ptr, len, ValueId).

If the host is also Rust-generated or explicitly handles this layout, this works. Otherwise, consider a #[repr(C)] struct for the attribute entry.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
.github/workflows/ci.yml (1)

18-19: Update to actions/checkout@v4.

The v3 runner is deprecated and may not work on newer GitHub Actions infrastructure.

🔎 Proposed fix
-        uses: actions/checkout@v3
+        uses: actions/checkout@v4
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 156e231 and da5e59f.

📒 Files selected for processing (1)
  • .github/workflows/ci.yml
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: RossComputerGuy
Repo: DeterminateSystems/secure-build PR: 32
File: src/commands/scan.rs:139-162
Timestamp: 2025-10-21T19:23:14.234Z
Learning: In the secure-build repository (Rust), unwraps during Nix initialization (NixStore::open, FlakeSettings::new, EvalStateBuilder, etc.) are acceptable because failures indicate catastrophic system conditions like out-of-memory where no recovery is possible.
Learnt from: RossComputerGuy
Repo: DeterminateSystems/secure-build PR: 32
File: flake.nix:175-177
Timestamp: 2025-10-21T17:29:29.023Z
Learning: In Nix flake.nix files using Rust with bindgen, when `rustPlatform.bindgenHook` is included in `nativeBuildInputs`, it automatically sets up `LIBCLANG_PATH` and other bindgen-related environment variables. An explicit `LIBCLANG_PATH` setting in the package derivation is not required, even if it appears in the devShell.
🪛 actionlint (1.7.9)
.github/workflows/ci.yml

19-19: the runner of "actions/checkout@v3" action is too old to run on GitHub Actions. update the action's version to fix this issue

(action)


21-21: step must run script with "run" section or run action with "uses" section

(syntax-check)

edolstra and others added 4 commits December 25, 2025 15:31
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Flake lock file updates:

• Updated input 'nix':
    'github:DeterminateSystems/nix-src/d6d322b' (2025-12-20)
  → 'github:DeterminateSystems/nix-src/e7bfa9b' (2025-12-25)
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (3)
.github/workflows/ci.yml (1)

21-22: Fix syntax error in Install Nix step.

Line 22 has an extra leading dash, creating two malformed steps instead of one valid step. This will cause the workflow to fail validation.

🔎 Proposed fix
       - name: Install Nix
-      - uses: DeterminateSystems/determinate-nix-action@main
+        uses: DeterminateSystems/determinate-nix-action@main
nix-wasm-rust/src/lib.rs (2)

122-134: Use mutable pointer for copy_list destination.

The buffer res is written to by the FFI function but is declared immutable and passed via as_ptr(). This should use mut and as_mut_ptr() for correctness.

🔎 Proposed fix
     pub fn get_list(&self) -> Vec<Value> {
         let len = self.get_list_length();
-        let res: Vec<Value> = vec![Value(0); len];
+        let mut res: Vec<Value> = vec![Value(0); len];
         extern "C" {
             fn copy_list(value: ValueId, ptr: u32, len: usize);
         }
         unsafe {
             if len > 0 {
-                copy_list(self.0, res.as_ptr() as u32, len);
+                copy_list(self.0, res.as_mut_ptr() as u32, len);
             }
         }
         res
     }

150-175: Use mutable pointers for copy_attrset and copy_attrname destinations.

Both attrs (line 157) and buf (line 165) are written to by FFI functions but are declared immutable. This causes undefined behavior under Rust's aliasing model.

🔎 Proposed fix
     pub fn get_attrset(&self) -> BTreeMap<String, Value> {
         extern "C" {
             fn get_attrset_length(value: ValueId) -> usize;
             fn copy_attrset(value: ValueId, ptr: u32, len: usize);
             fn copy_attrname(value: ValueId, attr_idx: usize, ptr: u32, len: usize);
         }
         let len = unsafe { get_attrset_length(self.0) };
-        let attrs: Vec<(ValueId, usize)> = vec![(0, 0); len];
+        let mut attrs: Vec<(ValueId, usize)> = vec![(0, 0); len];
         if len > 0 {
             unsafe {
-                copy_attrset(self.0, attrs.as_ptr() as u32, len);
+                copy_attrset(self.0, attrs.as_mut_ptr() as u32, len);
             }
         }
         let mut res = BTreeMap::new();
         for (attr_idx, (value, attr_len)) in attrs.iter().enumerate() {
-            let buf: Vec<u8> = vec![0; *attr_len];
+            let mut buf: Vec<u8> = vec![0; *attr_len];
             unsafe {
-                copy_attrname(self.0, attr_idx, buf.as_ptr() as u32, *attr_len);
+                copy_attrname(self.0, attr_idx, buf.as_mut_ptr() as u32, *attr_len);
             }
             res.insert(
                 String::from_utf8(buf).expect("Nix attribute name should be UTF-8."),
                 Value(*value),
             );
         }
         res
     }
🧹 Nitpick comments (2)
nix-wasm-plugin-mandelbrot/src/lib.rs (1)

13-16: Consider validating width to prevent overflow from negative values.

If the width attribute is negative, v.get_int() as usize will wrap to a very large value, potentially causing memory exhaustion or panic when allocating the output string.

🔎 Proposed fix
     let width = args
         .get("width")
-        .map(|v| v.get_int() as usize)
+        .map(|v| v.get_int().max(1) as usize)
         .unwrap_or(120);
nix-wasm-rust/src/lib.rs (1)

12-17: Consider adding Copy derive to Value.

Since Value wraps a u32 (which is Copy), deriving Copy would make the API more ergonomic and avoid unnecessary clones in plugin code.

🔎 Proposed change
 #[repr(transparent)]
-#[derive(Clone, Debug)]
+#[derive(Clone, Copy, Debug)]
 pub struct Value(ValueId);
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between da5e59f and c09190f.

📒 Files selected for processing (3)
  • .github/workflows/ci.yml
  • nix-wasm-plugin-mandelbrot/src/lib.rs
  • nix-wasm-rust/src/lib.rs
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: RossComputerGuy
Repo: DeterminateSystems/secure-build PR: 32
File: src/commands/scan.rs:139-162
Timestamp: 2025-10-21T19:23:14.234Z
Learning: In the secure-build repository (Rust), unwraps during Nix initialization (NixStore::open, FlakeSettings::new, EvalStateBuilder, etc.) are acceptable because failures indicate catastrophic system conditions like out-of-memory where no recovery is possible.
Learnt from: RossComputerGuy
Repo: DeterminateSystems/secure-build PR: 32
File: flake.nix:175-177
Timestamp: 2025-10-21T17:29:29.023Z
Learning: In Nix flake.nix files using Rust with bindgen, when `rustPlatform.bindgenHook` is included in `nativeBuildInputs`, it automatically sets up `LIBCLANG_PATH` and other bindgen-related environment variables. An explicit `LIBCLANG_PATH` setting in the package derivation is not required, even if it appears in the devShell.
📚 Learning: 2025-11-05T19:24:31.622Z
Learnt from: cole-h
Repo: DeterminateSystems/determinate-nixd PR: 427
File: src/config/mod.rs:75-77
Timestamp: 2025-11-05T19:24:31.622Z
Learning: In `src/config/mod.rs` and `src/command/builder/mod.rs`, the `cpu_count` field uses `isize` (not an unsigned integer type) because it must match the Swift FFI boundary where `swift_rs::Int` is used in the `run_builder_vm` function signature in `src/command/builder/macos.rs`.

Applied to files:

  • nix-wasm-rust/src/lib.rs
🧬 Code graph analysis (1)
nix-wasm-plugin-mandelbrot/src/lib.rs (1)
nix-wasm-rust/src/lib.rs (2)
  • make_string (68-73)
  • make_string (70-70)
🪛 actionlint (1.7.9)
.github/workflows/ci.yml

21-21: step must run script with "run" section or run action with "uses" section

(syntax-check)

🔇 Additional comments (8)
.github/workflows/ci.yml (1)

1-19: LGTM on the workflow structure.

The workflow triggers, permissions, checkout action (now v4), cache setup, and build/test steps are correctly configured. The nix flake check --all-systems command will validate all packages and checks defined in the flake.

Also applies to: 24-29

nix-wasm-plugin-mandelbrot/src/lib.rs (3)

74-84: LGTM!

The function now correctly uses the ITERATIONS constant, and the color gradient logic is well-implemented.


86-92: LGTM!

The helper functions are correct. The to_rgb function works safely because iter_to_color clamps color values to [0.0, 1.0].


22-57: LGTM on the Mandelbrot iteration and smooth coloring logic.

The smooth coloring algorithm using the continuous potential is correctly implemented, and the ANSI color output with interpolation produces good visual results.

nix-wasm-rust/src/lib.rs (4)

3-10: LGTM!

The warn function correctly passes the string slice to the FFI boundary using pointer and length.


19-30: LGTM!

The Type enum is correctly marked #[repr(C)] for stable ABI, and explicit discriminant values starting at 1 suggest 0 may be reserved for an error/unknown state on the host side.


75-85: LGTM!

The mutability issue from the previous review has been addressed. The buffer is now correctly declared as mut and as_mut_ptr() is used for the FFI call.


32-73: LGTM on the getter and constructor methods.

The FFI patterns for get_type, make_int, get_int, make_float, get_float, make_string, get_string_length, make_bool, get_bool, make_null, make_list, get_list_length, and make_attrset are correctly implemented. Read-only operations appropriately use *const pointers.

Also applies to: 87-120, 136-148

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
flake.nix (1)

10-17: LGTM on helper structure; x86_64-darwin concern already noted.

The forAllSystems helper and cargoToml parsing are correctly implemented. The missing x86_64-darwin in supportedSystems was already flagged in a previous review.

🧹 Nitpick comments (3)
flake.nix (3)

32-48: Consider adding runHook calls to custom phases.

Custom buildPhase, checkPhase, and installPhase implementations should call runHook to ensure standard phase hooks are executed. This allows other tools and overlays to inject pre/post logic.

🔎 Proposed fix
-          buildPhase = "cargo build --release";
+          buildPhase = ''
+            runHook preBuild
+            cargo build --release
+            runHook postBuild
+          '';

           checkPhase = ''
+            runHook preCheck
             for i in nix-wasm-plugin-*/tests/*.nix; do
               echo "running test $i..."
               base="$(dirname $i)/$(basename $i .nix)"
               nix eval --store dummy:// --offline --json --show-trace -I plugins=target/wasm32-unknown-unknown/release --impure --file "$i" > "$base.out"
               cmp "$base.exp" "$base.out"
             done
+            runHook postCheck
           '';

           installPhase = ''
+            runHook preInstall
             mkdir -p $out
             for i in target/wasm32-unknown-unknown/release/*.wasm; do
               wasm-opt -O3 -o "$out/$(basename "$i")" "$i"
             done
+            runHook postInstall
           '';

62-71: Unused rec keyword.

The rec on line 62 is unnecessary since default doesn't reference other attributes within the same attrset.

🔎 Proposed fix
-      devShells = forAllSystems ({ pkgs, system }: rec {
-        default = with pkgs; self.packages.${system}.default.overrideAttrs (attrs: {
+      devShells = forAllSystems ({ pkgs, system }: {
+        default = with pkgs; self.packages.${system}.default.overrideAttrs (attrs: {

73-76: Unused rec keyword; checks structure is correct.

Same as devShells - the rec is unnecessary here. The check correctly runs the package build for CI validation.

🔎 Proposed fix
-      checks = forAllSystems ({ pkgs, system }: rec {
+      checks = forAllSystems ({ pkgs, system }: {
         build = self.packages.${system}.default;
       });
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 004bf00 and 6e88a7f.

📒 Files selected for processing (2)
  • README.md
  • flake.nix
✅ Files skipped from review due to trivial changes (1)
  • README.md
🔇 Additional comments (2)
flake.nix (2)

1-8: LGTM!

The flake description and inputs are well-structured. Using the custom nix-src/wasm branch for WASM plugin support is appropriate for this use case.


20-60: Packages structure is well-designed.

The nix-wasm-plugins derivation correctly:

  • Extracts metadata from Cargo.toml
  • Targets wasm32-unknown-unknown
  • Runs integration tests comparing expected vs actual outputs
  • Optimizes WASM output with wasm-opt -O3
  • Includes all necessary native build inputs

Flake lock file updates:

• Updated input 'nix':
    'github:DeterminateSystems/nix-src/5b31e99' (2026-01-04)
  → 'github:DeterminateSystems/nix-src/d977b54' (2026-01-06)
Flake lock file updates:

• Updated input 'nix':
    'github:DeterminateSystems/nix-src/d977b54' (2026-01-06)
  → 'github:DeterminateSystems/nix-src/8bc414c' (2026-01-19)
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@flake.nix`:
- Around line 50-56: Remove the unused build deps by editing the
nativeBuildInputs list: drop wasm-bindgen-cli and wasm-pack from
nativeBuildInputs since the build only runs cargo build, the check uses nix
eval, and install uses wasm-opt from binaryen; keep other entries
(rustc.llvmPackages.lld, binaryen, inputs.nix.packages.${system}.nix-cli) intact
so the pure wasm32-unknown-unknown build remains unchanged.
♻️ Duplicate comments (1)
flake.nix (1)

10-18: The let bindings are well-structured.

Parsing cargoToml for single source of truth on package metadata and the forAllSystems helper pattern are idiomatic Nix practices.

Note: The missing x86_64-darwin in supportedSystems was flagged in a previous review.

🧹 Nitpick comments (4)
flake.nix (4)

6-6: Unused input: flake-schemas is declared but never referenced.

The flake-schemas input is defined but not used anywhere in the outputs. Consider removing it to reduce unnecessary dependencies, or add a schemas output if you intend to use it.

♻️ Suggested fix
   inputs = {
     nixpkgs.follows = "nix/nixpkgs";
-    flake-schemas.url = "https://flakehub.com/f/DeterminateSystems/flake-schemas/*.tar.gz";
     nix.url = "github:DeterminateSystems/nix-src/wasm";
   };

32-32: Missing runHook calls in custom phases.

Custom buildPhase, checkPhase, and installPhase should include runHook preBuild/runHook postBuild (and equivalents) to allow users to inject pre/post hooks when overriding the derivation.

♻️ Suggested fix for buildPhase
-          buildPhase = "cargo build --release";
+          buildPhase = ''
+            runHook preBuild
+            cargo build --release
+            runHook postBuild
+          '';

Similar changes should be applied to checkPhase (add runHook preCheck/runHook postCheck) and installPhase (add runHook preInstall/runHook postInstall).


64-73: Unnecessary rec keyword.

The rec keyword on line 65 is unused since default doesn't reference any other attributes in this set. Removing it improves clarity.

♻️ Suggested fix
-      devShells = forAllSystems ({ pkgs, system }: rec {
-        default = with pkgs; self.packages.${system}.default.overrideAttrs (attrs: {
+      devShells = forAllSystems ({ pkgs, system }: {
+        default = self.packages.${system}.default.overrideAttrs (attrs: {
           nativeBuildInputs = attrs.nativeBuildInputs ++ [
-            wabt
-            rust-analyzer
-            rustfmt
-            clippy
+            pkgs.wabt
+            pkgs.rust-analyzer
+            pkgs.rustfmt
+            pkgs.clippy
           ];
         });
       });

Note: The with pkgs; was also moved since it's only needed for the list items after removing rec.


75-77: Unnecessary rec keyword in checks.

Similar to devShells, the rec keyword is unused here since build doesn't reference itself or other attributes.

♻️ Suggested fix
-      checks = forAllSystems ({ pkgs, system }: rec {
+      checks = forAllSystems ({ pkgs, system }: {
         build = self.packages.${system}.default;
       });

Comment on lines +50 to +56
nativeBuildInputs = [
rustc.llvmPackages.lld
wasm-bindgen-cli
wasm-pack
binaryen
inputs.nix.packages.${system}.nix-cli
];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, get an overview of the repository structure
find . -type f -name "*.rs" | head -20
find . -type f -name "Cargo.toml" | head -10

Repository: DeterminateSystems/nix-wasm-rust

Length of output: 490


🏁 Script executed:

# Check if wasm-bindgen or wasm-pack are referenced anywhere in the Rust source
rg -n "wasm-bindgen|wasm-pack" --type rust
rg -n "wasm_bindgen" --type rust

Repository: DeterminateSystems/nix-wasm-rust

Length of output: 58


🏁 Script executed:

# Also check in Cargo.toml files and other configuration files
rg -n "wasm-bindgen|wasm-pack" --type toml

Repository: DeterminateSystems/nix-wasm-rust

Length of output: 58


🏁 Script executed:

# Check what's in the flake.nix file around the mentioned lines
head -70 flake.nix | tail -30

Repository: DeterminateSystems/nix-wasm-rust

Length of output: 936


🏁 Script executed:

# Check if wasm-bindgen-cli or wasm-pack are invoked anywhere in the flake.nix
rg -n "wasm-bindgen|wasm-pack" flake.nix

Repository: DeterminateSystems/nix-wasm-rust

Length of output: 132


🏁 Script executed:

# Look at the buildPhase and other phases to see what actually runs
cat flake.nix | grep -A 20 "buildPhase"

Repository: DeterminateSystems/nix-wasm-rust

Length of output: 839


🏁 Script executed:

# Check all Cargo.toml files for wasm-bindgen dependency declarations
for file in $(find . -name "Cargo.toml"); do
  if grep -q "wasm" "$file"; then
    echo "=== $file ==="
    grep "wasm" "$file"
  fi
done

Repository: DeterminateSystems/nix-wasm-rust

Length of output: 917


Remove unused build dependencies wasm-bindgen-cli and wasm-pack.

These tools are declared in nativeBuildInputs (lines 52–53) but are never invoked. The build phase runs only cargo build, the check phase uses nix eval, and the install phase uses wasm-opt from binaryen. No Cargo.toml declares these as dependencies, and no Rust source contains any reference to them. Since this is a pure wasm32-unknown-unknown build without JavaScript bindings generation, these tools can be safely removed.

🤖 Prompt for AI Agents
In `@flake.nix` around lines 50 - 56, Remove the unused build deps by editing the
nativeBuildInputs list: drop wasm-bindgen-cli and wasm-pack from
nativeBuildInputs since the build only runs cargo build, the check uses nix
eval, and install uses wasm-opt from binaryen; keep other entries
(rustc.llvmPackages.lld, binaryen, inputs.nix.packages.${system}.nix-cli) intact
so the pure wasm32-unknown-unknown build remains unchanged.

Flake lock file updates:

• Updated input 'nix':
    'github:DeterminateSystems/nix-src/8bc414c' (2026-01-19)
  → 'github:DeterminateSystems/nix-src/cd76ab4' (2026-01-19)
Flake lock file updates:

• Updated input 'nix':
    'github:DeterminateSystems/nix-src/cd76ab4' (2026-01-19)
  → 'github:DeterminateSystems/nix-src/22551fa' (2026-01-19)
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@nix-wasm-plugin-grep/src/lib.rs`:
- Around line 4-22: The plugin's exported grep may panic across the extern "C"
boundary if nix_wasm_init_v1() hasn't been run first; either clearly document
that callers must call nix_wasm_init_v1() before using grep (update README / API
docs to state initialization order) or add local defense-in-depth in grep by
wrapping its body (the code that calls recurse and builds matches) in
std::panic::catch_unwind and, on Err, convert the panic into the same
abort/FFI-safe behavior used by the global hook (e.g., invoke the same
panic-to-FFI function or abort) so no unwinding crosses the FFI boundary; refer
to functions grep, recurse and the global initializer nix_wasm_init_v1 when
making the change.
♻️ Duplicate comments (3)
nix-wasm-rust/src/lib.rs (2)

195-200: make_attrset uses ABI‑unstable (&str, Value) in FFI.

Tuples containing &str are not FFI‑safe; #[allow(improper_ctypes)] just suppresses the warning. Use a repr(C) struct with pointer+length and ValueId (and update the host side to match).

🛠️ Proposed fix
 type ValueId = u32;

+#[repr(C)]
+struct AttrEntry {
+    name_ptr: *const u8,
+    name_len: usize,
+    value_id: ValueId,
+}
+
 impl Value {
     pub fn make_attrset(attrs: &[(&str, Value)]) -> Value {
         extern "C" {
-            #[allow(improper_ctypes)]
-            fn make_attrset(ptr: *const (&str, Value), len: usize) -> Value;
+            fn make_attrset(ptr: *const AttrEntry, len: usize) -> Value;
         }
-        unsafe { make_attrset(attrs.as_ptr(), attrs.len()) }
+        let entries: Vec<AttrEntry> = attrs
+            .iter()
+            .map(|(k, v)| AttrEntry {
+                name_ptr: k.as_ptr(),
+                name_len: k.len(),
+                value_id: v.0,
+            })
+            .collect();
+        unsafe { make_attrset(entries.as_ptr(), entries.len()) }
     }

203-219: copy_attrset uses tuple layout across FFI.

*mut (ValueId, usize) has no guaranteed C ABI; replace it with a repr(C) struct and adjust the helper/buffers.

🛠️ Proposed fix
+#[repr(C)]
+struct AttrMeta {
+    value_id: ValueId,
+    name_len: usize,
+}
+
-    fn get_attrset_from_attrs(&self, attrs: &[(ValueId, usize)]) -> BTreeMap<String, Value> {
+    fn get_attrset_from_attrs(&self, attrs: &[AttrMeta]) -> BTreeMap<String, Value> {
         extern "C" {
             fn copy_attrname(value: ValueId, attr_idx: usize, ptr: *mut u8, len: usize);
         }
         let mut res = BTreeMap::new();
-        for (attr_idx, (value, attr_len)) in attrs.iter().enumerate() {
-            let mut buf = vec![0; *attr_len];
+        for (attr_idx, entry) in attrs.iter().enumerate() {
+            let mut buf = vec![0; entry.name_len];
             unsafe {
-                copy_attrname(self.0, attr_idx, buf.as_mut_ptr(), *attr_len);
+                copy_attrname(self.0, attr_idx, buf.as_mut_ptr(), entry.name_len);
             }
             res.insert(
                 String::from_utf8(buf).expect("Nix attribute name should be UTF-8."),
-                Value(*value),
+                Value(entry.value_id),
             );
         }
         res
     }

     pub fn get_attrset(&self) -> BTreeMap<String, Value> {
         extern "C" {
-            #[allow(improper_ctypes)]
-            fn copy_attrset(value: ValueId, ptr: *mut (ValueId, usize), max_len: usize) -> usize;
+            fn copy_attrset(value: ValueId, ptr: *mut AttrMeta, max_len: usize) -> usize;
         }
         unsafe {
             // Optimistically call with a small buffer on the stack.
-            let mut buf = [(0, 0); 32];
+            let mut buf = [AttrMeta { value_id: 0, name_len: 0 }; 32];
             let len = copy_attrset(self.0, buf.as_mut_ptr(), buf.len());
             if len > buf.len() {
                 // If it didn't fit, allocate a buffer of the right size.
-                let mut buf = vec![(0, 0); len];
+                let mut buf = vec![AttrMeta { value_id: 0, name_len: 0 }; len];
                 let len2 = copy_attrset(self.0, buf.as_mut_ptr(), buf.len());
                 assert!(len2 == len);
                 self.get_attrset_from_attrs(&buf)
             } else {
                 self.get_attrset_from_attrs(&buf[0..len])
             }
         }
     }

Also applies to: 221-238

nix-wasm-plugin-test/src/lib.rs (1)

4-75: Prevent panics from crossing the FFI boundary.

All exported extern "C" functions can panic via expect/get_*; unwinding across FFI is UB. Wrap each export with catch_unwind (or use extern "C-unwind" if supported) and return a sentinel value.

🛠️ Proposed fix (apply pattern to all exports)
 use nix_wasm_rust::Value;
+use std::panic;

+fn with_unwind_guard<F: FnOnce() -> Value>(f: F) -> Value {
+    panic::catch_unwind(panic::AssertUnwindSafe(f))
+        .unwrap_or_else(|_| Value::make_null())
+}
+
 #[no_mangle]
 pub extern "C" fn range(arg: Value) -> Value {
-    let args = arg.get_attrset();
-
-    let start = args.get("start").map(|v| v.get_int()).unwrap_or(0);
-    let end = args.get("end").map(|v| v.get_int()).unwrap_or(10);
-
-    let mut list = vec![];
-
-    for i in start..=end {
-        list.push(Value::make_int(i));
-    }
-
-    Value::make_list(&list)
+    with_unwind_guard(|| {
+        let args = arg.get_attrset();
+
+        let start = args.get("start").map(|v| v.get_int()).unwrap_or(0);
+        let end = args.get("end").map(|v| v.get_int()).unwrap_or(10);
+
+        let mut list = vec![];
+
+        for i in start..=end {
+            list.push(Value::make_int(i));
+        }
+
+        Value::make_list(&list)
+    })
 }

Flake lock file updates:

• Updated input 'nix':
    'github:DeterminateSystems/nix-src/22551fa' (2026-01-19)
  → 'github:DeterminateSystems/nix-src/b878824' (2026-01-20)
@@ -0,0 +1 @@
[1,1,1]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I kinda expected what the LLM commented on... Why isn't it [1, 2, 3]? It's a static, and it's called from the same evaluation, so I'd expect that it would be properly incrementing each time, instead of reinitializing each time...

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because every Wasm invocation starts from a fresh instance. So the global variable in the Wasm memory does nothing. This test is to verify that Wasm calls are indeed pure and don't retain state across calls.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahhhhh gotcha, that makes sense. Thanks.

Copy link
Member

@cole-h cole-h left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Otherwise LGTM

edolstra and others added 3 commits January 21, 2026 17:17
Flake lock file updates:

• Updated input 'nix':
    'github:DeterminateSystems/nix-src/b878824' (2026-01-20)
  → 'github:DeterminateSystems/nix-src/57ac520' (2026-01-21)
@edolstra edolstra merged commit 01e4160 into main Jan 21, 2026
3 checks passed
@edolstra edolstra deleted the initial branch January 21, 2026 20:14
This was referenced Feb 20, 2026
This was referenced Feb 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants