Document FFI safety contracts for bridge entry points - #3
Conversation
The two C ABI entry points exposed to the iOS shell — goose_bridge_handle_json and goose_bridge_free_string — were marked unsafe extern "C" without a documented safety contract. Clippy flagged this (missing_safety_doc); more importantly, the bridge IS the cross-language interface, so its preconditions should not have to be reconstructed from the implementation. Add rustdoc # Safety sections covering: - pointer validity, null handling, and UTF-8 expectations for the request buffer - aliasing rules during the call - ownership transfer of the returned C string and the requirement to free it through goose_bridge_free_string (not free(3) — allocator mismatch is UB) - double-free and foreign-allocator pitfalls on the free side No behaviour change. cargo build --lib stays clean; the two clippy::missing_safety_doc warnings on bridge.rs:2505 and bridge.rs:2529 are now resolved (0 remaining).
| /// - `request_json` is either null **or** a valid pointer to a | ||
| /// null-terminated UTF-8 C string that remains valid (and unmodified by | ||
| /// other threads) for the duration of this call. | ||
| /// - The buffer referenced by `request_json` is not aliased by any mutable |
There was a problem hiding this comment.
Missing thread-safety contract.
The # Safety section correctly covers pointer validity, UTF-8, aliasing for the duration of a single call, and ownership transfer. What is not stated: whether goose_bridge_handle_json is safe to call concurrently from multiple threads.
If the bridge dispatches into stateful Rust (SQLite via rusqlite, any global), concurrent calls could race. If it is safe (because each call opens its own connection and the Rust side is stateless), that guarantee is as important to document as the pointer invariants — it determines whether a Swift caller can dispatch bridge calls from concurrent DispatchQueues without a lock.
Suggest adding: /// This function may be called concurrently from multiple threads — [safe/unsafe because ...]
| /// `goose_core_*` function. | ||
| /// | ||
| /// # Safety | ||
| /// |
There was a problem hiding this comment.
Doc-drift risk — no CI hook keeps the safety text in sync with behavior.
The goose_bridge_free_string safety section correctly documents ownership transfer and the double-free pitfall. However, if the implementation changes (e.g. the allocation strategy behind CString changes, or a wrapper is added), these docs will silently become wrong.
Minimum mitigation: reference the exact implementation so a future maintainer editing the code knows to update the doc:
/// Releases the string by calling [`CString::from_raw`] on `value`,
/// which reclaims the memory via the Rust allocator that produced it
/// (see the `string_to_c_string` helper). Do not pass a pointer
/// allocated by any other means.
b-nnett#3 (v2.8.6): v26 PpgHr record-rate notch (#194) — de-artifact the per-record comb before autocorrelation so a sub-60-bpm sleeper can't snap to a false 60 bpm; gated on a record-boundary discontinuity so a true 60 is preserved. b-nnett#4 (v4.0.0): port FitnessAgeEngine + VitalityEngine (HUNT-study VO2max / fitness-age regression + a hazard-weighted Body Age) verbatim, and surface a self-contained Vitality / Body Age hero on the Health Monitor screen that computes live from the last 14 days of repo.days + profile — no precompute pipeline needed. Honest: a fitness comparison, not a biological/medical age. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
What
The two C ABI entry points exposed to the iOS shell —
goose_bridge_handle_jsonandgoose_bridge_free_stringinRust/core/src/bridge.rs— are declaredpub unsafe extern "C"but ship without a documented safety contract. Clippy flags both withmissing_safety_doc, and more importantly the bridge is the cross-language interface, so its preconditions shouldn't have to be reconstructed from the implementation.This PR adds rustdoc
# Safetysections covering:request_jsongoose_bridge_free_string(notfree(3)— the Rust allocator backingCStringis not guaranteed to match the host's, and Swift'sswift_demangle-style helpers can hide this)Treating this header as a stable contract is also the spirit of #2 — a documented safety contract is a prerequisite for the C ABI being something a future Android/JNI or Flutter/Dart FFI port can rely on without reading Rust source.
Why this matters in practice
The Swift bridge code (
GooseRustBridge.swift) already has to obey these rules implicitly. Documenting them:request_json) to any future contributor wiring a new platform shell.Change
Rust/core/src/bridge.rsonly — two doc-comment blocks added, no code changes.Verification
cargo build --lib→ clean (1m 23s, no new warnings, no new errors).cargo clippy --lib --no-deps→ the twomissing_safety_docwarnings onbridge.rs:2505andbridge.rs:2529are gone (0 remaining for that lint on this file).Test plan
cargo build --lib)missing_safety_docwarnings on bridge entry points resolvedNotes
--fixsweep, nothing outside the two functions. If you'd like me to follow up with a broader safety/clippy pass (there are ~120 other warnings, mostlycollapsible_ifandtoo_many_argumentsstyle nits), happy to send that as a separate PR.