SIGN IN SIGN UP

feat: Rust bindings (sys crate + safe wrapper) over the native C ABI (LEO-325) (#19)

* feat: Rust bindings (sys crate + safe wrapper) over the native C ABI (LEO-325)

Add idiomatic Rust bindings over the sw_* C ABI as a two-crate workspace
under rust/, mirroring the shipped Python (cffi) and C++ (header-only) bindings.

sensorwatch-sys (raw FFI):
- build.rs compiles the C core straight into the crate via cc with -DSW_STATIC
  (single artifact, no separate DLL — sidesteps the SECURITY.md 2.1 search-order
  risk). Compiled on all platforms, so the safe crate links everywhere and
  returns UnsupportedPlatform off Windows rather than failing to link.
- FFI declarations are pre-generated with bindgen and checked in
  (src/bindings.rs), so building needs only a C compiler, never libclang. A CI
  bindgen-drift job regenerates them (pinned bindgen 0.72.1 + libclang 18.1.1)
  and git-diffs the result, so header drift fails CI.

sensorwatch (safe wrapper):
- Move-only Session/Snapshot freed by Drop (ownership makes close/free
  exactly-once automatic, no double-free). Reading value type; ReadingType enum
  folding unrecognized categories to Unknown; Error enum carrying the sw_error_t
  code + sw_error_string text, with every non-SW_OK surfaced as Result::Err.
  Session::new() verifies the ABI version, then opens the source.

CI: a rust job (build/clippy/fmt/test on ubuntu + windows — ubuntu asserts
UnsupportedPlatform, windows runs a live-snapshot test that self-skips when
HWiNFO is down) plus the bindgen-drift job. Docs: README Rust section, C_ABI.md
"Binding Notes -> Rust" flipped from sketch to shipped, SECURITY.md mitigation
register + header comment updated.

crates.io publishing is split into a follow-up (LEO-326).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix: address PR #19 review round 1 (Send/Sync, NaN equality, MSRV CI)

- Reading: NaN-reflexive PartialEq (manual impl + scalar_eq), so a Reading
  carrying a NaN scalar equals itself -- mirrors the C++ binding and de-flakes
  the live-snapshot assert_eq! checks (kimi #2).
- Snapshot: `unsafe impl Send + Sync` -- the snapshot owns an immutable copy and
  the ABI documents concurrent queries on a live snapshot as safe, so it can be
  shared across threads. Session: `unsafe impl Send` (kept !Sync -- same-session
  use must be synchronized, snapshot() is &mut self). Both were conservatively
  !Send/!Sync before (a raw pointer is unconditionally !Send/!Sync regardless of
  pointee); these restore the thread-safety the ABI actually guarantees. Docs and
  a compile-time marker test updated to match; the misleading "!Send Snapshot"
  test comment fixed (Copilot inline, kimi #1/#3, claude).
- query_string: treat a length-query needed==0 (an ABI contract violation) as
  CorruptData instead of re-calling with a zero-length buffer (kimi #6).
- CI: new rust-msrv job (cargo check on 1.82, the declared floor) so the MSRV is
  verified not just declared (kimi #5, claude); cache bindgen-cli in the
  bindgen-drift job to skip the ~2-3 min rebuild on cache hits (kimi #4).

Verified locally: fmt/clippy (-D warnings) clean, 7 integration tests + doctest
pass, and `cargo +1.82.0 check --workspace --all-targets --locked` passes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Leonard Janke <lcjanke2020@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
A
Agent for Leonard Janke committed
774cfcfaa9cd3523c1b6694aac740c89488b1f1f
Parent: 521dc89
Committed by GitHub <noreply@github.com> on 7/1/2026, 6:27:40 PM