refactor(codegen): name RET status codes in C++/Rust callbacks

Avoid magic ret-code integers in the generated callback helpers. The C
trampolines already reference NIMFFI_RET_STALE_WARN; mirror that in the other
two backends: define the canonical NIMFFI_RET_* set once in the C++ header
prelude (guarded so a mixed TU keeps a single definition) and emit module-level
NIMFFI_RET_* consts in the Rust crate, then use the named constants for the
stale-warn, ok, and missing-callback checks instead of 3/0/2.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Ivan FB 2026-07-11 01:07:04 +02:00
parent 825ae5af06
commit 7e7632a8e9
No known key found for this signature in database
GPG Key ID: DF0C67A04C543270
6 changed files with 57 additions and 17 deletions

View File

@ -29,6 +29,16 @@ extern "C" {
#include <tinycbor/cbor.h>
}
// nim-ffi result-callback status codes (mirror ffi/ffi_types.nim and the C
// header). Guarded so a translation unit that also pulls in the C header keeps
// a single definition.
#ifndef NIMFFI_RET_OK
#define NIMFFI_RET_OK 0
#define NIMFFI_RET_ERR 1
#define NIMFFI_RET_MISSING_CALLBACK 2
#define NIMFFI_RET_STALE_WARN 3
#endif
// ============================================================
// Result<T> — exception-free error channel
// ============================================================
@ -452,7 +462,7 @@ inline void ffi_cb_(int ret, const char* msg, size_t len, void* ud) {
// still running. This blocking wrapper only reports the final result, so
// ignore it WITHOUT touching `ud` — a terminal callback still owns the
// shared handle and will free it.
if (ret == 3) return;
if (ret == NIMFFI_RET_STALE_WARN) return;
// ffi_call_ heap-allocated a shared_ptr and passed its address as ud;
// take ownership here so it's freed on every exit path.
@ -461,7 +471,7 @@ inline void ffi_cb_(int ret, const char* msg, size_t len, void* ud) {
FFICallState_& s = **handle;
std::lock_guard<std::mutex> lock(s.mtx);
s.ok = (ret == 0);
s.ok = (ret == NIMFFI_RET_OK);
if (msg && len > 0) {
const auto* p = reinterpret_cast<const std::uint8_t*>(msg);
if (s.ok) s.bytes.assign(p, p + len);
@ -478,7 +488,7 @@ inline Result<std::vector<std::uint8_t>> ffi_call_(
auto state = std::make_shared<FFICallState_>();
auto* cb_ref = new std::shared_ptr<FFICallState_>(state);
const int ret = f(ffi_cb_, cb_ref);
if (ret == 2) {
if (ret == NIMFFI_RET_MISSING_CALLBACK) {
delete cb_ref;
return Result<Bytes>::err("RET_MISSING_CALLBACK (internal error)");
}

View File

@ -29,6 +29,16 @@ extern "C" {
#include <tinycbor/cbor.h>
}
// nim-ffi result-callback status codes (mirror ffi/ffi_types.nim and the C
// header). Guarded so a translation unit that also pulls in the C header keeps
// a single definition.
#ifndef NIMFFI_RET_OK
#define NIMFFI_RET_OK 0
#define NIMFFI_RET_ERR 1
#define NIMFFI_RET_MISSING_CALLBACK 2
#define NIMFFI_RET_STALE_WARN 3
#endif
#include <unordered_map>
// ============================================================
// Result<T> — exception-free error channel
@ -752,7 +762,7 @@ inline void ffi_cb_(int ret, const char* msg, size_t len, void* ud) {
// still running. This blocking wrapper only reports the final result, so
// ignore it WITHOUT touching `ud` — a terminal callback still owns the
// shared handle and will free it.
if (ret == 3) return;
if (ret == NIMFFI_RET_STALE_WARN) return;
// ffi_call_ heap-allocated a shared_ptr and passed its address as ud;
// take ownership here so it's freed on every exit path.
@ -761,7 +771,7 @@ inline void ffi_cb_(int ret, const char* msg, size_t len, void* ud) {
FFICallState_& s = **handle;
std::lock_guard<std::mutex> lock(s.mtx);
s.ok = (ret == 0);
s.ok = (ret == NIMFFI_RET_OK);
if (msg && len > 0) {
const auto* p = reinterpret_cast<const std::uint8_t*>(msg);
if (s.ok) s.bytes.assign(p, p + len);
@ -778,7 +788,7 @@ inline Result<std::vector<std::uint8_t>> ffi_call_(
auto state = std::make_shared<FFICallState_>();
auto* cb_ref = new std::shared_ptr<FFICallState_>(state);
const int ret = f(ffi_cb_, cb_ref);
if (ret == 2) {
if (ret == NIMFFI_RET_MISSING_CALLBACK) {
delete cb_ref;
return Result<Bytes>::err("RET_MISSING_CALLBACK (internal error)");
}

View File

@ -31,10 +31,15 @@ unsafe fn ffi_payload(ret: c_int, msg: *const c_char, len: usize) -> FFIResult {
} else {
slice::from_raw_parts(msg as *const u8, len).to_vec()
};
if ret == 0 { Ok(bytes) }
if ret == NIMFFI_RET_OK { Ok(bytes) }
else { Err(String::from_utf8_lossy(&bytes).into_owned()) }
}
// nim-ffi result-callback status codes (mirror ffi/ffi_types.nim).
const NIMFFI_RET_OK: c_int = 0;
const NIMFFI_RET_MISSING_CALLBACK: c_int = 2;
const NIMFFI_RET_STALE_WARN: c_int = 3;
unsafe extern "C" fn on_result(
ret: c_int,
msg: *const c_char,
@ -44,7 +49,7 @@ unsafe extern "C" fn on_result(
// NIMFFI_RET_STALE_WARN (3) is a non-terminal progress ping: the request
// is still running. This wrapper only delivers the final result, so ignore
// it WITHOUT reclaiming the box — a terminal callback still owns the Sender.
if ret == 3 { return; }
if ret == NIMFFI_RET_STALE_WARN { return; }
// Take ownership of the boxed Sender — dropping it at end of scope
// releases the only outstanding handle.
@ -71,7 +76,7 @@ where
let (tx, rx) = flume::bounded::<FFIResult>(1);
let raw = Box::into_raw(Box::new(tx)) as *mut c_void;
let ret = f(on_result, raw);
if ret == 2 {
if ret == NIMFFI_RET_MISSING_CALLBACK {
// Callback will never fire; reclaim the box to avoid a leak.
drop(unsafe { Box::from_raw(raw as *mut FFISender) });
return Err("RET_MISSING_CALLBACK (internal error)".into());
@ -92,7 +97,7 @@ where
let (tx, rx) = flume::bounded::<FFIResult>(1);
let raw = Box::into_raw(Box::new(tx)) as *mut c_void;
let ret = f(on_result, raw);
if ret == 2 {
if ret == NIMFFI_RET_MISSING_CALLBACK {
drop(unsafe { Box::from_raw(raw as *mut FFISender) });
return Err("RET_MISSING_CALLBACK (internal error)".into());
}

View File

@ -368,10 +368,15 @@ proc generateApiRs*(
lines.add(" } else {")
lines.add(" slice::from_raw_parts(msg as *const u8, len).to_vec()")
lines.add(" };")
lines.add(" if ret == 0 { Ok(bytes) }")
lines.add(" if ret == NIMFFI_RET_OK { Ok(bytes) }")
lines.add(" else { Err(String::from_utf8_lossy(&bytes).into_owned()) }")
lines.add("}")
lines.add("")
lines.add("// nim-ffi result-callback status codes (mirror ffi/ffi_types.nim).")
lines.add("const NIMFFI_RET_OK: c_int = 0;")
lines.add("const NIMFFI_RET_MISSING_CALLBACK: c_int = 2;")
lines.add("const NIMFFI_RET_STALE_WARN: c_int = 3;")
lines.add("")
lines.add("unsafe extern \"C\" fn on_result(")
lines.add(" ret: c_int,")
lines.add(" msg: *const c_char,")
@ -387,7 +392,7 @@ proc generateApiRs*(
lines.add(
" // it WITHOUT reclaiming the box — a terminal callback still owns the Sender."
)
lines.add(" if ret == 3 { return; }")
lines.add(" if ret == NIMFFI_RET_STALE_WARN { return; }")
lines.add("")
lines.add(" // Take ownership of the boxed Sender — dropping it at end of scope")
lines.add(" // releases the only outstanding handle.")
@ -430,7 +435,7 @@ proc generateApiRs*(
lines.add(" let (tx, rx) = flume::bounded::<FFIResult>(1);")
lines.add(" let raw = Box::into_raw(Box::new(tx)) as *mut c_void;")
lines.add(" let ret = f(on_result, raw);")
lines.add(" if ret == 2 {")
lines.add(" if ret == NIMFFI_RET_MISSING_CALLBACK {")
lines.add(" // Callback will never fire; reclaim the box to avoid a leak.")
lines.add(" drop(unsafe { Box::from_raw(raw as *mut FFISender) });")
lines.add(" return Err(\"RET_MISSING_CALLBACK (internal error)\".into());")
@ -453,7 +458,7 @@ proc generateApiRs*(
lines.add(" let (tx, rx) = flume::bounded::<FFIResult>(1);")
lines.add(" let raw = Box::into_raw(Box::new(tx)) as *mut c_void;")
lines.add(" let ret = f(on_result, raw);")
lines.add(" if ret == 2 {")
lines.add(" if ret == NIMFFI_RET_MISSING_CALLBACK {")
lines.add(" drop(unsafe { Box::from_raw(raw as *mut FFISender) });")
lines.add(" return Err(\"RET_MISSING_CALLBACK (internal error)\".into());")
lines.add(" }")

View File

@ -28,3 +28,13 @@
extern "C" {
#include <tinycbor/cbor.h>
}
// nim-ffi result-callback status codes (mirror ffi/ffi_types.nim and the C
// header). Guarded so a translation unit that also pulls in the C header keeps
// a single definition.
#ifndef NIMFFI_RET_OK
#define NIMFFI_RET_OK 0
#define NIMFFI_RET_ERR 1
#define NIMFFI_RET_MISSING_CALLBACK 2
#define NIMFFI_RET_STALE_WARN 3
#endif

View File

@ -21,7 +21,7 @@ inline void ffi_cb_(int ret, const char* msg, size_t len, void* ud) {
// still running. This blocking wrapper only reports the final result, so
// ignore it WITHOUT touching `ud` — a terminal callback still owns the
// shared handle and will free it.
if (ret == 3) return;
if (ret == NIMFFI_RET_STALE_WARN) return;
// ffi_call_ heap-allocated a shared_ptr and passed its address as ud;
// take ownership here so it's freed on every exit path.
@ -30,7 +30,7 @@ inline void ffi_cb_(int ret, const char* msg, size_t len, void* ud) {
FFICallState_& s = **handle;
std::lock_guard<std::mutex> lock(s.mtx);
s.ok = (ret == 0);
s.ok = (ret == NIMFFI_RET_OK);
if (msg && len > 0) {
const auto* p = reinterpret_cast<const std::uint8_t*>(msg);
if (s.ok) s.bytes.assign(p, p + len);
@ -47,7 +47,7 @@ inline Result<std::vector<std::uint8_t>> ffi_call_(
auto state = std::make_shared<FFICallState_>();
auto* cb_ref = new std::shared_ptr<FFICallState_>(state);
const int ret = f(ffi_cb_, cb_ref);
if (ret == 2) {
if (ret == NIMFFI_RET_MISSING_CALLBACK) {
delete cb_ref;
return Result<Bytes>::err("RET_MISSING_CALLBACK (internal error)");
}