Skip to content

Stabilize debug_closure_helpers - #146099

Open
coolreader18 wants to merge 1 commit into
rust-lang:mainfrom
coolreader18:stabilize-debug_closure_helpers
Open

coolreader18 wants to merge 1 commit into
rust-lang:mainfrom
coolreader18:stabilize-debug_closure_helpers

Conversation

@coolreader18

@coolreader18 coolreader18 commented Sep 1, 2025

Copy link
Copy Markdown
Contributor

View all comments

Resolves #117729. The tracking issue still needs an FCP, but I'm hoping that creating a stabilization PR will prompt one.

r? libs-api

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-libs Relevant to the library team, which will review and decide on the PR/issue. T-rustdoc Relevant to the rustdoc team, which will review and decide on the PR/issue. labels Sep 1, 2025
@coolreader18 coolreader18 changed the title Stabilize debug closure helpers Stabilize debug_closure_helpers Sep 1, 2025
@tgross35 tgross35 added the I-libs-api-nominated [DEPRECATED; DO NOT USE] label Sep 2, 2025
@Amanieu Amanieu added I-libs-api-nominated [DEPRECATED; DO NOT USE] and removed I-libs-api-nominated [DEPRECATED; DO NOT USE] labels Sep 23, 2025
@bors

bors commented Sep 27, 2025

Copy link
Copy Markdown
Collaborator

☔ The latest upstream changes (presumably #146636) made this pull request unmergeable. Please resolve the merge conflicts.

@Amanieu Amanieu removed the I-libs-api-nominated [DEPRECATED; DO NOT USE] label Sep 30, 2025
@coolreader18

Copy link
Copy Markdown
Contributor Author

Once the FCP is complete, I think it might make sense to merge #145915 first and then merge this on top of that.

Comment thread library/core/src/fmt/builders.rs Outdated
Comment on lines 141 to 143
pub fn field_with<F>(&mut self, name: &str, value_fmt: F) -> &mut Self
where
F: FnOnce(&mut fmt::Formatter<'_>) -> fmt::Result,

@hanna-kruppe hanna-kruppe Oct 20, 2025

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Having the closure as a generic parameter (and not type-erasing it internally) leads to a lot of code bloat compared to the corresponding stable APIs that take &dyn Debug as value to format. According to cargo +nightly llvm-lines a simple program formatting a struct with two fields generates 694 lines of LLVM IR while the same program using field() generates 42 lines. So at least with the current implementation, these functions have an annoying downside compared to e.g. .field("foo", &fmt::from_fn(|f| ...)

I think this can be addressed after stabilization, without changing the signatures. But I wanted to flag it so the reviewer can think about it as well. It's not entirely obvious to me for the helpers that deal in FnOnce. I'm aware of one workaround (put the closure into an Option, then pass a &dyn FnMut that unwraps this option to call the original impl FnOnce) but it's not quite zero cost in several dimensions.

@jmillikin jmillikin Oct 21, 2025

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Could this be solved by putting the main logic back into field(), and having field_with be an #[inline] wrapper around it?

I don't want to send in any PRs that might cause Git conflicts with the stabilization, but if the public API is good then I'd be happy to experiment with internal reorganization / optimization after both have landed.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

field takes a &dyn Debug. How do you invoke a FnOnce or FnMut from the &self argument of Debug::fmt?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Cell<Option<F>> , then .replace(None).unwrap()? It doesn't need to survive more than one call.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I think if field_with were changed to use dyn, it'd be important to ensure that it doesn't cause undue stack usage, since that was brought up as an issue with the existing functions in #117729 (comment)

@hanna-kruppe hanna-kruppe Oct 22, 2025

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

That report is in the context of a total stack size of 4 KiB. The DebugStruct::field function in my nightly toolchain's libcore.rlib allocates a very reasonable amount of stack space (add $0x48,%rsp). It then goes on to call other functions, but those seem to be functions shared with most of the formatting infrastructure. It's frankly a miracle that any variation of the code fits within a 4 KiB stack, and the cases that work probably depends on the code being duplicated and specialized a bit for the particular usage, which is fundamentally at odds with optimizing for code size (as core::fmt generally does). So I wouldn't give much weight to that report.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@hanna-kruppe would you mind creating an issue for this? And/or a PR to change it? I think you're probably the best equipped to do so.

I'm ready to approve this but would like your confirmation that we have room to improve this within the current design.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I can extract my above comment into an issue but I probably won't have time to try out a fix in the next 2-3 weeks. There's definitely room for improvement (e.g., the Cell<Option<F>>-via-&dyn Fn() approach discussed above). But without trying it out, I can't quantify how much improvement it is.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Created #149745

@jmillikin

Copy link
Copy Markdown
Contributor

The stabilization PR for fmt::from_fn has merged, so this one is now ready to rebase.

gentle ping @the8472 for review

@coolreader18
coolreader18 force-pushed the stabilize-debug_closure_helpers branch from a51fcbe to b1bde94 Compare November 4, 2025 18:52
@rustbot

This comment has been minimized.

@bors

bors commented Nov 6, 2025

Copy link
Copy Markdown
Collaborator

☔ The latest upstream changes (presumably #148544) made this pull request unmergeable. Please resolve the merge conflicts.

@coolreader18
coolreader18 force-pushed the stabilize-debug_closure_helpers branch from b1bde94 to 1e39a61 Compare November 6, 2025 02:49
@rustbot

This comment has been minimized.

@rust-log-analyzer

This comment has been minimized.

@jmillikin

Copy link
Copy Markdown
Contributor

@bors retry

@bors

bors commented Nov 6, 2025

Copy link
Copy Markdown
Collaborator

@jmillikin: 🔑 Insufficient privileges: not in try users

@jmillikin

Copy link
Copy Markdown
Contributor

@the8472 gentle ping?

@jmillikin

Copy link
Copy Markdown
Contributor

r? libs-api

@rustbot rustbot added the T-libs-api [DEPRECATED; DO NOT USE] label Dec 1, 2025
@rustbot rustbot assigned BurntSushi and unassigned the8472 Dec 1, 2025
@bors

bors commented Dec 6, 2025

Copy link
Copy Markdown
Collaborator

☔ The latest upstream changes (presumably #149704) made this pull request unmergeable. Please resolve the merge conflicts.

@tgross35

tgross35 commented Dec 6, 2025

Copy link
Copy Markdown
Member

r? tgross35

Fyi you can r? libs rather than libs-api for stabilization PRs, libs does more reviews. libs-api just needs to do the FCP.

The diff here LGTM, but I'd just like to make sure we have room to improve on what Hanna mentioned.

BoringMan314 pushed a commit to BoringMan314/Beanfun that referenced this pull request Aug 25, 2026
Scaffolds the Rust ↔ Vue IPC boundary every P10.2+ business command
will plug into. No WPF parity mapping (the C# tree dispatches UI
events directly through the WPF view-model); this chunk lands the
plumbing tauri-specta derives bindings off and the CommandError
contract every later command returns.

commands/error (CommandError DTO + domain conversions):
- CommandError { code: String, message: String, details:
  Option<serde_json::Value> } derives Serialize + specta::Type so
  every fallible command returns the same shape across the IPC wire.
  Builder helpers new(code, message) and with_details(v) keep call
  sites readable.
- `From` for all 7 domain errors (LoginError 42 variants,
  StorageError 8, ConfigError 4, ProcessError 8, RegistryError 2,
  GameError 7, UpdaterError 4) — one CommandError code per variant
  using the <domain>.<variant_snake_case> convention so the
  compiler's exhaustive match enforces that every new variant gets
  a stable wire code (renaming a variant won't silently change the
  frontend i18n key). Structural fields (url, pid, path, http
  status, ShellExecute code, …) flow into `details` as JSON; non-
  Serialize embeddings (reqwest::Error, serde_json::Error,
  io::Error) use display-string + side-channel flags (is_timeout,
  line/column, io_kind) so the frontend still gets structured
  context without dragging foreign types into the specta graph.
- Helper fns io_kind_str / reqwest_details / serde_json_details
  centralise the display-string shape so every From impl that
  embeds a foreign error writes the same `details` contract.
- Module docs include the full code registry (7 domains + the
  command-layer system.* codes for app_data_missing /
  spawn_blocking_failed) so the frontend can cross-reference
  without grepping source.

commands/state (AppState shared runtime state):
- AppState { storage_root: PathBuf, session:
  tokio::sync::RwLock<Option<Session>> } injected through
  Builder::manage so every command receives it via State<'_,
  AppState>. Session is an empty placeholder (P10.2 fills avatar /
  token / account_list). http_client deliberately deferred to P10.2
  — holding a reqwest::Client the P10.1 smoke commands never call
  would surface as a dead_code warning now and a premature design
  commitment later.
- tokio::sync::RwLock (not std) so guards are `Send` and survive
  .await points inside async command bodies.

commands/system (smoke commands):
- #[tauri::command] pub fn version() -> VersionInfo { app, tauri }
  exercises the synchronous-infallible IPC path and returns a
  struct DTO so tauri-specta's TS emission is tested against
  non-trivial shape, not just a scalar.
- #[tauri::command] pub async fn ping(message: String) -> Result<
  String, CommandError> exercises the async + tokio::task::
  spawn_blocking path every P10.2+ Win32-wrapping command will
  follow. JoinError maps to system.spawn_blocking_failed. The 60ms
  sleep is long enough to prove the work truly ran off-runtime
  without slowing the suite.

commands/mod (build_specta_builder helper):
- Single-source-of-truth `pub fn build_specta_builder<R:
  tauri::Runtime>() -> Builder<R>` wraps the collect_commands!
  call so adding a command is a one-line edit rather than three
  (runtime invoke_handler, bindings export, future mock-invoke
  integration test). Generic over R so a P10.2+ mock-invoke test
  can instantiate it with tauri::test::MockRuntime without
  retrofitting the signature.
- Module-level docs carry the IPC architecture diagram and the
  add-a-command howto so the expected workflow survives the next
  contributor.

lib.rs (boot sequence + bindings export):
- resolve_storage_root() pulls %APPDATA%\Beanfun on Windows
  (matches the WPF client's SpecialFolder.ApplicationData
  convention so on-disk state lands in the same place) and falls
  back to std::env::temp_dir().join("Beanfun") for non-Windows dev
  builds. Missing APPDATA surfaces as CommandError with code
  system.app_data_missing; run() exits with a concise diagnostic
  rather than panicking when the env var really isn't set.
- export_specta_bindings<R: Runtime> writes bindings.ts to
  <CARGO_MANIFEST_DIR>/../src/types on every debug boot (auto-
  keeping the frontend in lock-step with renamed commands). Gated
  behind #[cfg(debug_assertions)] so release installers — which
  often live under Program Files and would error out on a source-
  tree write — skip the path; failures inside the helper are
  non-fatal and logged to stderr so a broken dev checkout still
  boots with whatever bindings.ts is already on disk.
- greet scaffold removed (not part of the Beanfun feature set).

App.vue:
- Scaffold `greet` form + ref + invoke call stripped to match the
  backend cleanup. Left a blank app shell for P11 UI work.

Cargo.toml:
- tauri-specta pinned to =2.0.0-rc.21 (with =2.0.0-rc.22 specta +
  =0.0.9 specta-typescript). rc.24 pulls specta rc.24 which uses
  fmt::from_fn from the unstable debug_closure_helpers feature
  (rust-lang/rust#117729), breaking the stable rustc 1.92 build.
  Section comment pins the rationale + rust-lang/rust#146099 as
  the stabilisation watchpoint for a future upgrade.
- tauri gains "specta" feature (wires Tauri's State / Window
  types into the specta type graph).
- specta gains "derive" + "serde_json" (the latter needed for
  CommandError's Option<serde_json::Value> field to derive
  specta::Type).

Tests (430 → 462, +32):
- 20 error From-impl cases (≥1 per domain, covering Option<String>
  fields, non-Serialize embeddings, and structured details JSON).
- 3 AppState cases (new / session lifecycle / storage_root).
- 3 system cases (version fields, ping echo, ping Unicode).
- 1 bindings_file_tests::bindings_file_contains_all_p101_symbols
  lib-level guard that reads the committed bindings.ts and grep-
  asserts `version` / `ping` / `CommandError` / `VersionInfo`
  appear on `export`-prefixed lines; skips with a stderr hint on
  fresh clones where bindings.ts doesn't yet exist.
- 5 mod.rs doc examples compile-verified by rustdoc.

D11 scope note: the original plan was
`tests/ipc_smoke.rs::mock_invoke`, but any test binary that
statically instantiates `tauri_specta::Builder<R>` (for any R,
including MockRuntime) pulls `tauri-runtime-wry` →
`webview2-com-sys` into the link graph, which declares
WebView2Loader.dll as a regular (non-delay-loaded) import. Windows
then refuses to load the test .exe with STATUS_ENTRYPOINT_NOT_FOUND
whenever the DLL isn't discoverable on PATH, even with the WebView2
runtime itself installed. The production `cargo tauri dev` path
works because Tauri's own build setup places the DLL next to the
main binary. Rather than duplicate the specta export pipeline or
fight the native-DLL story with a workaround, bindings_file_tests
treats bindings.ts as a committed artefact (the frontend imports
from it anyway) and asserts on disk — catching drift on every CI
`cargo test` without needing a Tauri runtime.

Quality gates: cargo fmt --check / cargo clippy --all-targets --
-D warnings / cargo test --lib 462/462 / cargo test --tests (all
existing integration files 0 failed; no new integration binary) /
RUSTDOCFLAGS="-D warnings" cargo doc --no-deps.
@coolreader18
coolreader18 force-pushed the stabilize-debug_closure_helpers branch from d7204e5 to c47f842 Compare August 31, 2026 15:20
@rustbot

rustbot commented Aug 31, 2026

Copy link
Copy Markdown
Collaborator

The parser was modified, potentially altering the grammar of (stable) Rust
which would be a breaking change.

cc @fmease

@rustbot

rustbot commented Aug 31, 2026

Copy link
Copy Markdown
Collaborator

This PR was rebased onto a different main commit. Here's a range-diff highlighting what actually changed.

Rebasing is a normal part of keeping PRs up to date, so no action is needed—this note is just to help reviewers.

@clarfonthey clarfonthey added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. and removed S-blocked Status: Blocked on something else such as an RFC or other implementation work. labels Sep 13, 2026
@clarfonthey

This comment was marked as outdated.

@clarfonthey

Copy link
Copy Markdown
Contributor

@rfcbot fcp merge libs

@rust-rfcbot

rust-rfcbot commented Sep 13, 2026

Copy link
Copy Markdown
Collaborator

@clarfonthey has proposed to merge this. The next step is review by the rest of the tagged team members:

No concerns currently listed.

Once a majority of reviewers approve (and at most 2 approvals are outstanding), this will enter its final comment period. If you spot a major issue that hasn't been raised at any point in this process, please speak up!

See this document for info about what commands tagged team members can give me.

@rust-rfcbot rust-rfcbot added proposed-final-comment-period Proposed to merge/close by relevant subteam, see T-<team> label. Will enter FCP once signed off. disposition-merge This issue / PR is in PFCP or FCP with a disposition to merge it. labels Sep 13, 2026
@clarfonthey clarfonthey added S-waiting-on-fcp Status: PR is in FCP and is awaiting for FCP to complete. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Sep 13, 2026
@clarfonthey

Copy link
Copy Markdown
Contributor

Clarification on FCP: the FCP was a long while ago and stabilisation was ultimately blocked on the code size issues being fixed.

We've now decided those issues are substantially better now, but want to redo FCP anyway given all that.

@tgross35

Copy link
Copy Markdown
Member

@coolreader18 could you rebase this? Making sure there aren't new failures since this has been around for a while

@clarfonthey

Copy link
Copy Markdown
Contributor

@bors try

@rust-bors

This comment has been minimized.

rust-bors Bot pushed a commit that referenced this pull request Sep 15, 2026
… r=<try>

Stabilize `debug_closure_helpers`
@rust-bors

rust-bors Bot commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

☀️ Try build successful (CI)
Build commit: ea402e4 (ea402e47e2330450b216b82a6220fb0fb78150f3)
Base parent: 5392d2f (5392d2f545c6836dc79f209bcc14ac7179dbc4f2)

@clarfonthey

Copy link
Copy Markdown
Contributor

(I mean, we can still rebase, but it seems fine.)

@coolreader18

Copy link
Copy Markdown
Contributor Author

I just rebased it 2 weeks ago, to fix merge conflicts.

@rust-rfcbot rust-rfcbot added final-comment-period In the final comment period and will be merged soon unless new substantive objections are raised. and removed proposed-final-comment-period Proposed to merge/close by relevant subteam, see T-<team> label. Will enter FCP once signed off. labels Sep 15, 2026
@rust-rfcbot

Copy link
Copy Markdown
Collaborator

🔔 This is now entering its final comment period, as per the review above. 🔔

Comment on lines 973 to 976
/// # Panics
///
/// `key` must be called before `value` and each call to `key` must be followed
/// by a corresponding call to `value`. Otherwise this method will panic.

@tgross35 tgross35 Sep 15, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

key and value have these panic sections. They should be updated here to mention key_with and value_with, and key_with and value_with should get the same thing.

Expect a minor conflict with #162825 if that merges first.

View changes since the review

@tgross35

Copy link
Copy Markdown
Member

Wish I had noticed this earlier: we specify F: FnOnce here, but a lot of API has been moving to impl FnOnce instead. Is this something we want to change? I have a slight preference for switching to impl FnOnce since that's technically less surface area and we can always add back the F if it's actually useful.

Nominating since this is already in FCP.

@rustbot label +I-libs-nominated

@rustbot rustbot added the I-libs-nominated Nominated for discussion during a libs team meeting. label Sep 15, 2026
@clarfonthey

Copy link
Copy Markdown
Contributor

(Note: if you want to make that concern blocking, would need someone with checkbox privs to register it, but you can do that as a libs member.)

@rust-bors

rust-bors Bot commented Sep 17, 2026

Copy link
Copy Markdown
Contributor

☔ The latest upstream changes (presumably #162907) made this pull request unmergeable. Please resolve the merge conflicts by rebasing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

disposition-merge This issue / PR is in PFCP or FCP with a disposition to merge it. final-comment-period In the final comment period and will be merged soon unless new substantive objections are raised. I-libs-nominated Nominated for discussion during a libs team meeting. S-waiting-on-fcp Status: PR is in FCP and is awaiting for FCP to complete. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-libs Relevant to the library team, which will review and decide on the PR/issue. T-rustdoc Relevant to the rustdoc team, which will review and decide on the PR/issue.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Tracking Issue for debug_closure_helpers