Skip to content

Add a NullOrUndefined<T> to Rust JSG as a clearer alternative to Option<Nullable<T>> - #8080

Merged
nathanwhit merged 1 commit into
mainfrom
nathanwhit/null-or-undefined
Oct 29, 2026
Merged

nathanwhit merged 1 commit into
mainfrom
nathanwhit/null-or-undefined

Conversation

@nathanwhit

@nathanwhit nathanwhit commented Oct 28, 2026 •

Copy link
Copy Markdown
Member

The main motivation is explicitness, particularly when handling the value, followed by reducing the syntactic noise for a common case. This came up in discussions with @mikea

Consider Option<Nullable<T>>:

fn foo(val: Option<Nullable<bool>>) {

 match val {
    Some(Nullable::Some(v)) => println!("got a value: {}", v),
    Some(Nullable::Null)) => println!("got null"),
    None => println!("got undefined")
 }
}

vs NullOrUndefined<T>:

fn foo(val: NullOrUndefined<bool>) {

 match val {
    NullOrUndefined::Some(v) => println!("got a value: {}", v),
    NullOrUndefined::Null => println!("got null"),
    NullOrUndefined::Undefined => println!("got undefined")
 }
}

The semantics and meaning of NullOrUndefined are immediately clear, whereas the meaning of None being undefined is non-obvious.

This PR adds the type and makes it the recommended way to represent T | null | undefined in Rust JSG. Option<Nullable<T>> remains but is not advertised as prominently in the docs.

Feel free to bikeshed the naming, names are hard.

@nathanwhit
nathanwhit requested review from a team as code owners October 28, 2026 22:53
@clanky-the-clanker

clanky-the-clanker Bot commented Oct 28, 2026 •

Copy link
Copy Markdown

✅ Approved — The changes since the last review (the macro's handling of optional fields and the new unit tests) are correct, and nothing in the pull request raises a correctness, security, performance or documentation concern.

The decision is advisory: this comment never approves, blocks or requests changes on the pull request.


✅ All checks passed

🔒 Security: ✅ LGTM

The change adds a plain Rust enum, its conversions and its macro handling. It adds no trust boundary and does not touch secrets, injection or authorization paths.


📖 AGENTS.md: ✅ LGTM

The src/rust/AGENTS.md update matches the change.


📋 Code Quality: ✅ LGTM

  • Field presence: the macro checks is_some() for Option and !is_undefined() for NullOrUndefined.
  • Data-property path: it calls ToJS::to_js for every field. Both types convert to undefined, so the output is unchanged.
  • Set() path: it still skips absent optional fields.
  • Empty dictionary: the Default::default() fallback works for both types. The derived Default does not require T: Default.
  • Lenient::to_parts: it now returns NullOrUndefined<T>, and its callers have been updated.
  • Conversions: the From impls keep all three states, and FromJS keeps null and undefined apart from a value.
  • GC tracing: the Traced impl visits only Some.

⚡ Performance: ✅ LGTM

The change adds a plain enum, its conversions and its macro handling. None of it adds work per request that could slow things down.


📝 Documentation: ✅ LGTM

The README, the jsg-macros README and the review checklist describe the new type accurately.


Reviewed commit: a56bdff8

@codecov-commenter

codecov-commenter commented Oct 28, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 0% with 97 lines in your changes missing coverage. Please review.
✅ Project coverage is 69.24%. Comparing base (9792d03) to head (a56bdff).
⚠️ Report is 5 commits behind head on main.

Files with missing lines Patch % Lines
src/rust/jsg/null_or_undefined.rs 0.00% 41 Missing ⚠️
src/rust/jsg/wrappable.rs 0.00% 39 Missing ⚠️
src/rust/jsg/lenient.rs 0.00% 17 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #8080      +/-   ##
==========================================
- Coverage   69.38%   69.24%   -0.14%     
==========================================
  Files         696      698       +2     
  Lines      181869   182494     +625     
  Branches    29215    29223       +8     
==========================================
+ Hits       126188   126376     +188     
- Misses      38612    39046     +434     
- Partials    17069    17072       +3     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Represent optional nullable values as Some(T), Null, or Undefined without nesting Option and Nullable. Support JavaScript conversion, omitted method arguments, struct-field omission, and GC tracing. Add state predicates, borrowing and mapping helpers, and conversions preserving all three states.

Integrate the type with Lenient, including its to_parts() return value, while retaining Option<Nullable<T>> conversions. Recommend NullOrUndefined<T> throughout the API docs, macro reference, review checklist, and agent guidance.

Use consuming, fallible ToJS conversions for struct fields and preserve upstream field tracing, Dict support, numeric conversions, and Web IDL function lengths. Cover non-Clone payload conversion errors and optional-argument lengths under both compatibility settings.

Validation: Rust formatting and Clippy pass for all Rust rule targets under src/rust, including unit and integration tests. The full Rust suite passes all 117 applicable test targets; two sanitizer targets are skipped.
@nathanwhit
nathanwhit force-pushed the nathanwhit/null-or-undefined branch from a281e31 to a56bdff Compare October 28, 2026 23:21
@nathanwhit
nathanwhit enabled auto-merge October 28, 2026 23:21
@nathanwhit
nathanwhit merged commit f03ec01 into main Oct 29, 2026
25 of 26 checks passed
@nathanwhit
nathanwhit deleted the nathanwhit/null-or-undefined branch October 29, 2026 02:11
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.

4 participants