diff --git a/examples/timer/rust_native_bindings/README.md b/examples/timer/rust_native_bindings/README.md
index 55ffcac..1b4f55d 100644
--- a/examples/timer/rust_native_bindings/README.md
+++ b/examples/timer/rust_native_bindings/README.md
@@ -21,8 +21,12 @@ Regenerate with `nimble genbindings_rust_native`.
## Status
-First cut — scalar / string / bool / float / nested-struct fields (create,
-version, echo). Methods taking sequences or optionals (complex, schedule) are
-`// SKIPPED`; those plus native typed events are the next increments. Linking is
-left to the consumer (`-L
-l my_timer` + an rpath, as in `examples/demo.rs`);
-a build.rs that compiles the dylib (like the CBOR crate) can be added later.
+Requests are fully supported: scalar / string / bool / float / nested struct
+**and now sequences (`Vec`) and optionals (`Option`)** — create, version, echo,
+complex, schedule all generate and round-trip typed values. `to_c` returns a
+holder that owns the `CString`s and C-array backing (heap, so the C struct's raw
+pointers stay valid across the move and for the call).
+
+Still to come: native typed events and the native-bare / `_cbor` reconciliation.
+Linking is left to the consumer (`-L -l my_timer` + rpath, as in
+`examples/demo.rs`); a build.rs that compiles the dylib can be added later.
diff --git a/examples/timer/rust_native_bindings/examples/demo.rs b/examples/timer/rust_native_bindings/examples/demo.rs
index 691852c..e4b3815 100644
--- a/examples/timer/rust_native_bindings/examples/demo.rs
+++ b/examples/timer/rust_native_bindings/examples/demo.rs
@@ -4,4 +4,20 @@ fn main() {
println!("version: {}", node.version().unwrap());
let r = node.echo(EchoRequest { message: "hello from generated Rust".into(), delay_ms: 5 }).unwrap();
println!("echo: echoed={} timer_name={}", r.echoed, r.timer_name);
+
+ let c = node.complex(ComplexRequest {
+ messages: vec![EchoRequest { message: "one".into(), delay_ms: 0 },
+ EchoRequest { message: "two".into(), delay_ms: 0 }],
+ tags: vec!["a".into(), "b".into()],
+ note: Some("a note".into()),
+ retries: Some(3),
+ }).unwrap();
+ println!("complex: item_count={} has_note={} summary={:?}", c.item_count, c.has_note, c.summary);
+
+ let s = node.schedule(
+ JobSpec { name: "nightly".into(), payload: vec!["x".into()], priority: 5 },
+ RetryPolicy { max_attempts: 3, backoff_ms: 100, retry_on: vec!["timeout".into()] },
+ ScheduleConfig { start_at_ms: 1000, interval_ms: 5000, jitter: Some(50) },
+ ).unwrap();
+ println!("schedule: job_id={:?} will_run_count={}", s.job_id, s.will_run_count);
}
diff --git a/examples/timer/rust_native_bindings/src/api.rs b/examples/timer/rust_native_bindings/src/api.rs
index 9fee077..38b2b30 100644
--- a/examples/timer/rust_native_bindings/src/api.rs
+++ b/examples/timer/rust_native_bindings/src/api.rs
@@ -31,6 +31,20 @@ unsafe extern "C" fn cb_my_timer_echo(ret: c_int, msg: *const c_char, len: usize
} else { Err(err_text(msg, len)) };
let _ = tx.send(r);
}
+unsafe extern "C" fn cb_my_timer_complex(ret: c_int, msg: *const c_char, len: usize, ud: *mut c_void) {
+ let tx = Box::from_raw(ud as *mut SyncSender>);
+ let r = if ret == RET_OK && !msg.is_null() {
+ Ok(ComplexResponse::from_c(&*(msg as *const ffi::ComplexResponse)))
+ } else { Err(err_text(msg, len)) };
+ let _ = tx.send(r);
+}
+unsafe extern "C" fn cb_my_timer_schedule(ret: c_int, msg: *const c_char, len: usize, ud: *mut c_void) {
+ let tx = Box::from_raw(ud as *mut SyncSender>);
+ let r = if ret == RET_OK && !msg.is_null() {
+ Ok(ScheduleResult::from_c(&*(msg as *const ffi::ScheduleResult)))
+ } else { Err(err_text(msg, len)) };
+ let _ = tx.send(r);
+}
pub struct MyTimerNode { ctx: *mut c_void }
unsafe impl Send for MyTimerNode {}
@@ -38,29 +52,25 @@ unsafe impl Sync for MyTimerNode {}
impl MyTimerNode {
pub fn new(config: TimerConfig) -> Result {
- let mut strings = Vec::new();
- let c_config = config.to_c_inner(&mut strings);
+ let c_config = config.to_c();
let (tx, rx) = sync_channel::>(1);
let raw = Box::into_raw(Box::new(tx)) as *mut c_void;
- let ctx = unsafe { ffi::my_timer_create(c_config, ack_cb, raw) };
+ let ctx = unsafe { ffi::my_timer_create(c_config.c, ack_cb, raw) };
let res = rx.recv().map_err(|_| String::from("callback channel closed"))?;
res?;
if ctx.is_null() { return Err(String::from("my_timer_create returned null")); }
- let _ = strings;
Ok(MyTimerNode { ctx })
}
pub fn echo(&self, req: EchoRequest) -> Result {
- let mut strings = Vec::new();
- let c_req = req.to_c_inner(&mut strings);
+ let c_req = req.to_c();
let (tx, rx) = sync_channel::>(1);
let raw = Box::into_raw(Box::new(tx)) as *mut c_void;
- let rc = unsafe { ffi::my_timer_echo(self.ctx, cb_my_timer_echo, raw, c_req) };
+ let rc = unsafe { ffi::my_timer_echo(self.ctx, cb_my_timer_echo, raw, c_req.c) };
if rc != RET_OK {
drop(unsafe { Box::from_raw(raw as *mut SyncSender>) });
return Err(String::from("my_timer_echo dispatch failed"));
}
- let _ = strings;
rx.recv().map_err(|_| String::from("callback channel closed"))?
}
@@ -75,8 +85,32 @@ impl MyTimerNode {
rx.recv().map_err(|_| String::from("callback channel closed"))?
}
- // SKIPPED my_timer_complex: seq/Option params not yet supported by native Rust codegen
- // SKIPPED my_timer_schedule: seq/Option params not yet supported by native Rust codegen
+ pub fn complex(&self, req: ComplexRequest) -> Result {
+ let c_req = req.to_c();
+ let (tx, rx) = sync_channel::>(1);
+ let raw = Box::into_raw(Box::new(tx)) as *mut c_void;
+ let rc = unsafe { ffi::my_timer_complex(self.ctx, cb_my_timer_complex, raw, c_req.c) };
+ if rc != RET_OK {
+ drop(unsafe { Box::from_raw(raw as *mut SyncSender>) });
+ return Err(String::from("my_timer_complex dispatch failed"));
+ }
+ rx.recv().map_err(|_| String::from("callback channel closed"))?
+ }
+
+ pub fn schedule(&self, job: JobSpec, retry: RetryPolicy, schedule: ScheduleConfig) -> Result {
+ let c_job = job.to_c();
+ let c_retry = retry.to_c();
+ let c_schedule = schedule.to_c();
+ let (tx, rx) = sync_channel::>(1);
+ let raw = Box::into_raw(Box::new(tx)) as *mut c_void;
+ let rc = unsafe { ffi::my_timer_schedule(self.ctx, cb_my_timer_schedule, raw, c_job.c, c_retry.c, c_schedule.c) };
+ if rc != RET_OK {
+ drop(unsafe { Box::from_raw(raw as *mut SyncSender>) });
+ return Err(String::from("my_timer_schedule dispatch failed"));
+ }
+ rx.recv().map_err(|_| String::from("callback channel closed"))?
+ }
+
}
impl Drop for MyTimerNode {
diff --git a/examples/timer/rust_native_bindings/src/ffi.rs b/examples/timer/rust_native_bindings/src/ffi.rs
index 1850461..7ada8fd 100644
--- a/examples/timer/rust_native_bindings/src/ffi.rs
+++ b/examples/timer/rust_native_bindings/src/ffi.rs
@@ -25,6 +25,19 @@ pub struct EchoResponse {
pub timer_name: *const c_char,
}
+#[repr(C)]
+#[derive(Clone, Copy)]
+pub struct ComplexRequest {
+ pub messages: *const EchoRequest,
+ pub messages_len: usize,
+ pub tags: *const *const c_char,
+ pub tags_len: usize,
+ pub note_present: c_int,
+ pub note: *const c_char,
+ pub retries_present: c_int,
+ pub retries: i64,
+}
+
#[repr(C)]
#[derive(Clone, Copy)]
pub struct ComplexResponse {
@@ -40,6 +53,33 @@ pub struct EchoEvent {
pub echo_count: i64,
}
+#[repr(C)]
+#[derive(Clone, Copy)]
+pub struct JobSpec {
+ pub name: *const c_char,
+ pub payload: *const *const c_char,
+ pub payload_len: usize,
+ pub priority: i64,
+}
+
+#[repr(C)]
+#[derive(Clone, Copy)]
+pub struct RetryPolicy {
+ pub max_attempts: i64,
+ pub backoff_ms: i64,
+ pub retry_on: *const *const c_char,
+ pub retry_on_len: usize,
+}
+
+#[repr(C)]
+#[derive(Clone, Copy)]
+pub struct ScheduleConfig {
+ pub start_at_ms: i64,
+ pub interval_ms: i64,
+ pub jitter_present: c_int,
+ pub jitter: i64,
+}
+
#[repr(C)]
#[derive(Clone, Copy)]
pub struct ScheduleResult {
@@ -54,5 +94,7 @@ extern "C" {
pub fn my_timer_create(config: TimerConfig, callback: FFICallback, user_data: *mut c_void) -> *mut c_void;
pub fn my_timer_echo(ctx: *mut c_void, callback: FFICallback, user_data: *mut c_void, req: EchoRequest) -> c_int;
pub fn my_timer_version(ctx: *mut c_void, callback: FFICallback, user_data: *mut c_void) -> c_int;
+ pub fn my_timer_complex(ctx: *mut c_void, callback: FFICallback, user_data: *mut c_void, req: ComplexRequest) -> c_int;
+ pub fn my_timer_schedule(ctx: *mut c_void, callback: FFICallback, user_data: *mut c_void, job: JobSpec, retry: RetryPolicy, schedule: ScheduleConfig) -> c_int;
pub fn my_timer_destroy(ctx: *mut c_void) -> c_int;
}
\ No newline at end of file
diff --git a/examples/timer/rust_native_bindings/src/types.rs b/examples/timer/rust_native_bindings/src/types.rs
index 150a381..f33c54b 100644
--- a/examples/timer/rust_native_bindings/src/types.rs
+++ b/examples/timer/rust_native_bindings/src/types.rs
@@ -1,4 +1,5 @@
// Generated by nim-ffi native Rust codegen. Do not edit by hand.
+#![allow(dead_code)]
use std::ffi::{CStr, CString};
use std::os::raw::c_char;
use super::ffi;
@@ -13,14 +14,18 @@ pub struct TimerConfig {
pub name: String,
}
+pub struct TimerConfigC {
+ pub c: ffi::TimerConfig,
+ _s: Vec,
+}
+
impl TimerConfig {
- pub fn to_c_inner(&self, strings: &mut Vec) -> ffi::TimerConfig {
- let name_c = CString::new(self.name.as_str()).unwrap_or_default();
- let name_p = name_c.as_ptr();
- strings.push(name_c);
- ffi::TimerConfig {
- name: name_p,
- }
+ pub fn to_c(&self) -> TimerConfigC {
+ #[allow(unused_mut)]
+ let mut h = TimerConfigC { c: unsafe { std::mem::zeroed() }, _s: Vec::new(),
+ };
+ { let cs = CString::new(self.name.as_str()).unwrap_or_default(); h.c.name = cs.as_ptr(); h._s.push(cs); }
+ h
}
pub fn from_c(c: &ffi::TimerConfig) -> Self {
TimerConfig {
@@ -35,15 +40,19 @@ pub struct EchoRequest {
pub delay_ms: i64,
}
+pub struct EchoRequestC {
+ pub c: ffi::EchoRequest,
+ _s: Vec,
+}
+
impl EchoRequest {
- pub fn to_c_inner(&self, strings: &mut Vec) -> ffi::EchoRequest {
- let message_c = CString::new(self.message.as_str()).unwrap_or_default();
- let message_p = message_c.as_ptr();
- strings.push(message_c);
- ffi::EchoRequest {
- message: message_p,
- delay_ms: self.delay_ms,
- }
+ pub fn to_c(&self) -> EchoRequestC {
+ #[allow(unused_mut)]
+ let mut h = EchoRequestC { c: unsafe { std::mem::zeroed() }, _s: Vec::new(),
+ };
+ { let cs = CString::new(self.message.as_str()).unwrap_or_default(); h.c.message = cs.as_ptr(); h._s.push(cs); }
+ h.c.delay_ms = self.delay_ms;
+ h
}
pub fn from_c(c: &ffi::EchoRequest) -> Self {
EchoRequest {
@@ -59,18 +68,19 @@ pub struct EchoResponse {
pub timer_name: String,
}
+pub struct EchoResponseC {
+ pub c: ffi::EchoResponse,
+ _s: Vec,
+}
+
impl EchoResponse {
- pub fn to_c_inner(&self, strings: &mut Vec) -> ffi::EchoResponse {
- let echoed_c = CString::new(self.echoed.as_str()).unwrap_or_default();
- let echoed_p = echoed_c.as_ptr();
- strings.push(echoed_c);
- let timer_name_c = CString::new(self.timer_name.as_str()).unwrap_or_default();
- let timer_name_p = timer_name_c.as_ptr();
- strings.push(timer_name_c);
- ffi::EchoResponse {
- echoed: echoed_p,
- timer_name: timer_name_p,
- }
+ pub fn to_c(&self) -> EchoResponseC {
+ #[allow(unused_mut)]
+ let mut h = EchoResponseC { c: unsafe { std::mem::zeroed() }, _s: Vec::new(),
+ };
+ { let cs = CString::new(self.echoed.as_str()).unwrap_or_default(); h.c.echoed = cs.as_ptr(); h._s.push(cs); }
+ { let cs = CString::new(self.timer_name.as_str()).unwrap_or_default(); h.c.timer_name = cs.as_ptr(); h._s.push(cs); }
+ h
}
pub fn from_c(c: &ffi::EchoResponse) -> Self {
EchoResponse {
@@ -80,6 +90,61 @@ impl EchoResponse {
}
}
+#[derive(Clone, Debug, Default)]
+pub struct ComplexRequest {
+ pub messages: Vec,
+ pub tags: Vec,
+ pub note: Option,
+ pub retries: Option,
+}
+
+pub struct ComplexRequestC {
+ pub c: ffi::ComplexRequest,
+ _s: Vec,
+ _a_messages: Vec,
+ _h_messages: Vec,
+ _a_tags: Vec<*const c_char>,
+}
+
+impl ComplexRequest {
+ pub fn to_c(&self) -> ComplexRequestC {
+ #[allow(unused_mut)]
+ let mut h = ComplexRequestC { c: unsafe { std::mem::zeroed() }, _s: Vec::new(),
+ _a_messages: Vec::new(),
+ _h_messages: Vec::new(),
+ _a_tags: Vec::new(),
+ };
+ for x in &self.messages {
+ let eh = x.to_c();
+ h._a_messages.push(eh.c);
+ h._h_messages.push(eh);
+ }
+ h.c.messages = if h._a_messages.is_empty() { std::ptr::null() } else { h._a_messages.as_ptr() };
+ h.c.messages_len = h._a_messages.len();
+ for x in &self.tags {
+ let cs = CString::new(x.as_str()).unwrap_or_default();
+ h._a_tags.push(cs.as_ptr());
+ h._s.push(cs);
+ }
+ h.c.tags = if h._a_tags.is_empty() { std::ptr::null() } else { h._a_tags.as_ptr() };
+ h.c.tags_len = h._a_tags.len();
+ if let Some(x) = &self.note {
+ let cs = CString::new(x.as_str()).unwrap_or_default();
+ h.c.note = cs.as_ptr(); h.c.note_present = 1; h._s.push(cs);
+ }
+ if let Some(v) = self.retries { h.c.retries_present = 1; h.c.retries = v; }
+ h
+ }
+ pub fn from_c(c: &ffi::ComplexRequest) -> Self {
+ ComplexRequest {
+ messages: if c.messages.is_null() { Vec::new() } else { unsafe { std::slice::from_raw_parts(c.messages, c.messages_len) }.iter().map(|e| EchoRequest::from_c(e)).collect() },
+ tags: if c.tags.is_null() { Vec::new() } else { unsafe { std::slice::from_raw_parts(c.tags, c.tags_len) }.iter().map(|&p| cstr(p)).collect() },
+ note: if c.note_present != 0 { Some(cstr(c.note)) } else { None },
+ retries: if c.retries_present != 0 { Some(c.retries) } else { None },
+ }
+ }
+}
+
#[derive(Clone, Debug, Default)]
pub struct ComplexResponse {
pub summary: String,
@@ -87,16 +152,20 @@ pub struct ComplexResponse {
pub has_note: bool,
}
+pub struct ComplexResponseC {
+ pub c: ffi::ComplexResponse,
+ _s: Vec,
+}
+
impl ComplexResponse {
- pub fn to_c_inner(&self, strings: &mut Vec) -> ffi::ComplexResponse {
- let summary_c = CString::new(self.summary.as_str()).unwrap_or_default();
- let summary_p = summary_c.as_ptr();
- strings.push(summary_c);
- ffi::ComplexResponse {
- summary: summary_p,
- item_count: self.item_count,
- has_note: if self.has_note { 1 } else { 0 },
- }
+ pub fn to_c(&self) -> ComplexResponseC {
+ #[allow(unused_mut)]
+ let mut h = ComplexResponseC { c: unsafe { std::mem::zeroed() }, _s: Vec::new(),
+ };
+ { let cs = CString::new(self.summary.as_str()).unwrap_or_default(); h.c.summary = cs.as_ptr(); h._s.push(cs); }
+ h.c.item_count = self.item_count;
+ h.c.has_note = if self.has_note { 1 } else { 0 };
+ h
}
pub fn from_c(c: &ffi::ComplexResponse) -> Self {
ComplexResponse {
@@ -113,15 +182,19 @@ pub struct EchoEvent {
pub echo_count: i64,
}
+pub struct EchoEventC {
+ pub c: ffi::EchoEvent,
+ _s: Vec,
+}
+
impl EchoEvent {
- pub fn to_c_inner(&self, strings: &mut Vec) -> ffi::EchoEvent {
- let message_c = CString::new(self.message.as_str()).unwrap_or_default();
- let message_p = message_c.as_ptr();
- strings.push(message_c);
- ffi::EchoEvent {
- message: message_p,
- echo_count: self.echo_count,
- }
+ pub fn to_c(&self) -> EchoEventC {
+ #[allow(unused_mut)]
+ let mut h = EchoEventC { c: unsafe { std::mem::zeroed() }, _s: Vec::new(),
+ };
+ { let cs = CString::new(self.message.as_str()).unwrap_or_default(); h.c.message = cs.as_ptr(); h._s.push(cs); }
+ h.c.echo_count = self.echo_count;
+ h
}
pub fn from_c(c: &ffi::EchoEvent) -> Self {
EchoEvent {
@@ -131,6 +204,115 @@ impl EchoEvent {
}
}
+#[derive(Clone, Debug, Default)]
+pub struct JobSpec {
+ pub name: String,
+ pub payload: Vec,
+ pub priority: i64,
+}
+
+pub struct JobSpecC {
+ pub c: ffi::JobSpec,
+ _s: Vec,
+ _a_payload: Vec<*const c_char>,
+}
+
+impl JobSpec {
+ pub fn to_c(&self) -> JobSpecC {
+ #[allow(unused_mut)]
+ let mut h = JobSpecC { c: unsafe { std::mem::zeroed() }, _s: Vec::new(),
+ _a_payload: Vec::new(),
+ };
+ { let cs = CString::new(self.name.as_str()).unwrap_or_default(); h.c.name = cs.as_ptr(); h._s.push(cs); }
+ for x in &self.payload {
+ let cs = CString::new(x.as_str()).unwrap_or_default();
+ h._a_payload.push(cs.as_ptr());
+ h._s.push(cs);
+ }
+ h.c.payload = if h._a_payload.is_empty() { std::ptr::null() } else { h._a_payload.as_ptr() };
+ h.c.payload_len = h._a_payload.len();
+ h.c.priority = self.priority;
+ h
+ }
+ pub fn from_c(c: &ffi::JobSpec) -> Self {
+ JobSpec {
+ name: cstr(c.name),
+ payload: if c.payload.is_null() { Vec::new() } else { unsafe { std::slice::from_raw_parts(c.payload, c.payload_len) }.iter().map(|&p| cstr(p)).collect() },
+ priority: c.priority,
+ }
+ }
+}
+
+#[derive(Clone, Debug, Default)]
+pub struct RetryPolicy {
+ pub max_attempts: i64,
+ pub backoff_ms: i64,
+ pub retry_on: Vec,
+}
+
+pub struct RetryPolicyC {
+ pub c: ffi::RetryPolicy,
+ _s: Vec,
+ _a_retry_on: Vec<*const c_char>,
+}
+
+impl RetryPolicy {
+ pub fn to_c(&self) -> RetryPolicyC {
+ #[allow(unused_mut)]
+ let mut h = RetryPolicyC { c: unsafe { std::mem::zeroed() }, _s: Vec::new(),
+ _a_retry_on: Vec::new(),
+ };
+ h.c.max_attempts = self.max_attempts;
+ h.c.backoff_ms = self.backoff_ms;
+ for x in &self.retry_on {
+ let cs = CString::new(x.as_str()).unwrap_or_default();
+ h._a_retry_on.push(cs.as_ptr());
+ h._s.push(cs);
+ }
+ h.c.retry_on = if h._a_retry_on.is_empty() { std::ptr::null() } else { h._a_retry_on.as_ptr() };
+ h.c.retry_on_len = h._a_retry_on.len();
+ h
+ }
+ pub fn from_c(c: &ffi::RetryPolicy) -> Self {
+ RetryPolicy {
+ max_attempts: c.max_attempts,
+ backoff_ms: c.backoff_ms,
+ retry_on: if c.retry_on.is_null() { Vec::new() } else { unsafe { std::slice::from_raw_parts(c.retry_on, c.retry_on_len) }.iter().map(|&p| cstr(p)).collect() },
+ }
+ }
+}
+
+#[derive(Clone, Debug, Default)]
+pub struct ScheduleConfig {
+ pub start_at_ms: i64,
+ pub interval_ms: i64,
+ pub jitter: Option,
+}
+
+pub struct ScheduleConfigC {
+ pub c: ffi::ScheduleConfig,
+ _s: Vec,
+}
+
+impl ScheduleConfig {
+ pub fn to_c(&self) -> ScheduleConfigC {
+ #[allow(unused_mut)]
+ let mut h = ScheduleConfigC { c: unsafe { std::mem::zeroed() }, _s: Vec::new(),
+ };
+ h.c.start_at_ms = self.start_at_ms;
+ h.c.interval_ms = self.interval_ms;
+ if let Some(v) = self.jitter { h.c.jitter_present = 1; h.c.jitter = v; }
+ h
+ }
+ pub fn from_c(c: &ffi::ScheduleConfig) -> Self {
+ ScheduleConfig {
+ start_at_ms: c.start_at_ms,
+ interval_ms: c.interval_ms,
+ jitter: if c.jitter_present != 0 { Some(c.jitter) } else { None },
+ }
+ }
+}
+
#[derive(Clone, Debug, Default)]
pub struct ScheduleResult {
pub job_id: String,
@@ -139,17 +321,21 @@ pub struct ScheduleResult {
pub effective_backoff_ms: i64,
}
+pub struct ScheduleResultC {
+ pub c: ffi::ScheduleResult,
+ _s: Vec,
+}
+
impl ScheduleResult {
- pub fn to_c_inner(&self, strings: &mut Vec) -> ffi::ScheduleResult {
- let job_id_c = CString::new(self.job_id.as_str()).unwrap_or_default();
- let job_id_p = job_id_c.as_ptr();
- strings.push(job_id_c);
- ffi::ScheduleResult {
- job_id: job_id_p,
- will_run_count: self.will_run_count,
- first_run_at_ms: self.first_run_at_ms,
- effective_backoff_ms: self.effective_backoff_ms,
- }
+ pub fn to_c(&self) -> ScheduleResultC {
+ #[allow(unused_mut)]
+ let mut h = ScheduleResultC { c: unsafe { std::mem::zeroed() }, _s: Vec::new(),
+ };
+ { let cs = CString::new(self.job_id.as_str()).unwrap_or_default(); h.c.job_id = cs.as_ptr(); h._s.push(cs); }
+ h.c.will_run_count = self.will_run_count;
+ h.c.first_run_at_ms = self.first_run_at_ms;
+ h.c.effective_backoff_ms = self.effective_backoff_ms;
+ h
}
pub fn from_c(c: &ffi::ScheduleResult) -> Self {
ScheduleResult {
diff --git a/ffi/codegen/rust_native.nim b/ffi/codegen/rust_native.nim
index e23a905..bc0a75f 100644
--- a/ffi/codegen/rust_native.nim
+++ b/ffi/codegen/rust_native.nim
@@ -30,6 +30,14 @@ proc isStructT(t: string, types: seq[FFITypeMeta]): bool =
return true
false
+proc seqElem(t: string): string =
+ t.strip()["seq[".len .. ^2].strip()
+
+proc optElem(t: string): string =
+ let s = t.strip()
+ let p = if s.startsWith("Maybe["): "Maybe[".len else: "Option[".len
+ s[p .. ^2].strip()
+
proc rustScalar(t: string): string =
case t.strip()
of "int", "int64", "clong": "i64"
@@ -47,35 +55,28 @@ proc rustScalar(t: string): string =
proc rustIdiomatic(t: string): string =
let s = t.strip()
- if isStringT(s): "String"
+ if isSeqT(s): "Vec<" & rustIdiomatic(seqElem(s)) & ">"
+ elif isOptT(s): "Option<" & rustIdiomatic(optElem(s)) & ">"
+ elif isStringT(s): "String"
else: rustScalar(s)
-proc rustCField(t: string, types: seq[FFITypeMeta]): string =
- ## repr(C) field type, matching codegen/c.emitCStructs.
+proc rustCElem(t: string, types: seq[FFITypeMeta], qualify = false): string =
+ ## repr(C) type for one seq element / option payload (matches emitCStructs).
+ ## `qualify` prefixes struct names with `ffi::` (needed outside the ffi module).
let s = t.strip()
if isStringT(s): "*const c_char"
elif s == "bool": "c_int"
- elif isStructT(s, types): capitalizeFirstLetter(s) # sibling repr(C) struct
+ elif isStructT(s, types): (if qualify: "ffi::" else: "") & capitalizeFirstLetter(s)
else: rustScalar(s)
-proc typeSimple(t: FFITypeMeta): bool =
- for f in t.fields:
- if isSeqT(f.typeName) or isOptT(f.typeName):
- return false
- true
-
proc procSimple(p: FFIProcMeta, types: seq[FFITypeMeta]): bool =
- ## A proc is "simple" if no param is a raw pointer, a bare seq/Option, or a
- ## struct that itself carries seq/Option fields (those structs aren't emitted
- ## yet, so referencing them would not compile).
+ ## All scalar / string / struct params are supported (structs may carry
+ ## seq/Option fields). Bare seq/Option top-level params and raw pointers are
+ ## not (the native ABI doesn't expose them either).
for ep in p.extraParams:
let t = ep.typeName.strip()
if ep.isPtr or isSeqT(t) or isOptT(t):
return false
- if isStructT(t, types):
- for ty in types:
- if ty.name == t and not typeSimple(ty):
- return false
true
# ── ffi.rs ──────────────────────────────────────────────────────────────────
@@ -90,15 +91,22 @@ proc emitFfiRs(
L.add("pub type FFICallback =")
L.add(" unsafe extern \"C\" fn(ret: c_int, msg: *const c_char, len: usize, user_data: *mut c_void);")
L.add("")
- # repr(C) POD mirrors.
+ # repr(C) POD mirrors — field layout mirrors codegen/c.emitCStructs.
for t in types:
- if not typeSimple(t):
- continue
L.add("#[repr(C)]")
L.add("#[derive(Clone, Copy)]")
L.add("pub struct " & capitalizeFirstLetter(t.name) & " {")
for f in t.fields:
- L.add(" pub " & camelToSnakeCase(f.name) & ": " & rustCField(f.typeName, types) & ",")
+ let snake = camelToSnakeCase(f.name)
+ let ft = f.typeName.strip()
+ if isSeqT(ft):
+ L.add(" pub " & snake & ": *const " & rustCElem(seqElem(ft), types) & ",")
+ L.add(" pub " & snake & "_len: usize,")
+ elif isOptT(ft):
+ L.add(" pub " & snake & "_present: c_int,")
+ L.add(" pub " & snake & ": " & rustCElem(optElem(ft), types) & ",")
+ else:
+ L.add(" pub " & snake & ": " & rustCElem(ft, types) & ",")
L.add("}")
L.add("")
L.add("#[link(name = \"" & libName & "\")]")
@@ -108,7 +116,7 @@ proc emitFfiRs(
of FFIKind.CTOR:
var ps: seq[string] = @[]
for ep in p.extraParams:
- ps.add(camelToSnakeCase(ep.name) & ": " & rustCField(ep.typeName, types))
+ ps.add(camelToSnakeCase(ep.name) & ": " & rustCElem(ep.typeName, types))
ps.add("callback: FFICallback")
ps.add("user_data: *mut c_void")
L.add(" pub fn " & p.procName & "(" & ps.join(", ") & ") -> *mut c_void;")
@@ -117,7 +125,7 @@ proc emitFfiRs(
continue
var ps = @["ctx: *mut c_void", "callback: FFICallback", "user_data: *mut c_void"]
for ep in p.extraParams:
- ps.add(camelToSnakeCase(ep.name) & ": " & rustCField(ep.typeName, types))
+ ps.add(camelToSnakeCase(ep.name) & ": " & rustCElem(ep.typeName, types))
L.add(" pub fn " & p.procName & "(" & ps.join(", ") & ") -> c_int;")
of FFIKind.DTOR:
L.add(" pub fn " & p.procName & "(ctx: *mut c_void) -> c_int;")
@@ -128,6 +136,7 @@ proc emitFfiRs(
proc emitTypesRs(types: seq[FFITypeMeta]): string =
var L: seq[string] = @[]
L.add("// Generated by nim-ffi native Rust codegen. Do not edit by hand.")
+ L.add("#![allow(dead_code)]")
L.add("use std::ffi::{CStr, CString};")
L.add("use std::os::raw::c_char;")
L.add("use super::ffi;")
@@ -138,55 +147,122 @@ proc emitTypesRs(types: seq[FFITypeMeta]): string =
L.add("}")
L.add("")
for t in types:
- if not typeSimple(t):
- continue
let rn = capitalizeFirstLetter(t.name)
+ let hn = rn & "C"
+ # idiomatic struct
L.add("#[derive(Clone, Debug, Default)]")
L.add("pub struct " & rn & " {")
for f in t.fields:
L.add(" pub " & camelToSnakeCase(f.name) & ": " & rustIdiomatic(f.typeName) & ",")
L.add("}")
L.add("")
- L.add("impl " & rn & " {")
- # to_c_inner: build the C struct, pushing owned CStrings into `strings`.
- L.add(" pub fn to_c_inner(&self, strings: &mut Vec) -> ffi::" & rn & " {")
+ # holder: owns the C struct + all heap backing (CStrings, arrays, nested
+ # holders). Returned by value; the backing lives on the heap, so the C
+ # struct's raw pointers stay valid across the move and for the call.
+ L.add("pub struct " & hn & " {")
+ L.add(" pub c: ffi::" & rn & ",")
+ L.add(" _s: Vec,")
for f in t.fields:
- let snake = camelToSnakeCase(f.name)
+ let s = camelToSnakeCase(f.name)
let ft = f.typeName.strip()
- if isStringT(ft):
- L.add(" let " & snake & "_c = CString::new(self." & snake &
- ".as_str()).unwrap_or_default();")
- L.add(" let " & snake & "_p = " & snake & "_c.as_ptr();")
- L.add(" strings.push(" & snake & "_c);")
- L.add(" ffi::" & rn & " {")
- for f in t.fields:
- let snake = camelToSnakeCase(f.name)
- let ft = f.typeName.strip()
- if isStringT(ft):
- L.add(" " & snake & ": " & snake & "_p,")
- elif ft == "bool":
- L.add(" " & snake & ": if self." & snake & " { 1 } else { 0 },")
+ if isSeqT(ft):
+ L.add(" _a_" & s & ": Vec<" & rustCElem(seqElem(ft), types, qualify = true) & ">,")
+ if isStructT(seqElem(ft), types):
+ L.add(" _h_" & s & ": Vec<" & capitalizeFirstLetter(seqElem(ft)) & "C>,")
+ elif isOptT(ft) and isStructT(optElem(ft), types):
+ L.add(" _h_" & s & ": Vec<" & capitalizeFirstLetter(optElem(ft)) & "C>,")
elif isStructT(ft, types):
- L.add(" " & snake & ": self." & snake & ".to_c_inner(strings),")
+ L.add(" _h_" & s & ": Vec<" & capitalizeFirstLetter(ft) & "C>,")
+ L.add("}")
+ L.add("")
+ L.add("impl " & rn & " {")
+ L.add(" pub fn to_c(&self) -> " & hn & " {")
+ L.add(" #[allow(unused_mut)]")
+ L.add(" let mut h = " & hn & " { c: unsafe { std::mem::zeroed() }, _s: Vec::new(),")
+ for f in t.fields:
+ let s = camelToSnakeCase(f.name)
+ let ft = f.typeName.strip()
+ if isSeqT(ft):
+ L.add(" _a_" & s & ": Vec::new(),")
+ if isStructT(seqElem(ft), types):
+ L.add(" _h_" & s & ": Vec::new(),")
+ elif (isOptT(ft) and isStructT(optElem(ft), types)) or isStructT(ft, types):
+ L.add(" _h_" & s & ": Vec::new(),")
+ L.add(" };")
+ for f in t.fields:
+ let s = camelToSnakeCase(f.name)
+ let ft = f.typeName.strip()
+ if isSeqT(ft):
+ let e = seqElem(ft)
+ L.add(" for x in &self." & s & " {")
+ if isStringT(e):
+ L.add(" let cs = CString::new(x.as_str()).unwrap_or_default();")
+ L.add(" h._a_" & s & ".push(cs.as_ptr());")
+ L.add(" h._s.push(cs);")
+ elif isStructT(e, types):
+ L.add(" let eh = x.to_c();")
+ L.add(" h._a_" & s & ".push(eh.c);")
+ L.add(" h._h_" & s & ".push(eh);")
+ elif e == "bool":
+ L.add(" h._a_" & s & ".push(if *x { 1 } else { 0 });")
+ else:
+ L.add(" h._a_" & s & ".push(*x);")
+ L.add(" }")
+ L.add(" h.c." & s & " = if h._a_" & s & ".is_empty() { std::ptr::null() } else { h._a_" & s & ".as_ptr() };")
+ L.add(" h.c." & s & "_len = h._a_" & s & ".len();")
+ elif isOptT(ft):
+ let e = optElem(ft)
+ if isStringT(e):
+ L.add(" if let Some(x) = &self." & s & " {")
+ L.add(" let cs = CString::new(x.as_str()).unwrap_or_default();")
+ L.add(" h.c." & s & " = cs.as_ptr(); h.c." & s & "_present = 1; h._s.push(cs);")
+ L.add(" }")
+ elif isStructT(e, types):
+ L.add(" if let Some(x) = &self." & s & " {")
+ L.add(" let eh = x.to_c(); h.c." & s & " = eh.c; h.c." & s & "_present = 1; h._h_" & s & ".push(eh);")
+ L.add(" }")
+ elif e == "bool":
+ L.add(" if let Some(v) = self." & s & " { h.c." & s & "_present = 1; h.c." & s & " = if v { 1 } else { 0 }; }")
+ else:
+ L.add(" if let Some(v) = self." & s & " { h.c." & s & "_present = 1; h.c." & s & " = v; }")
+ elif isStringT(ft):
+ L.add(" { let cs = CString::new(self." & s & ".as_str()).unwrap_or_default(); h.c." & s & " = cs.as_ptr(); h._s.push(cs); }")
+ elif ft == "bool":
+ L.add(" h.c." & s & " = if self." & s & " { 1 } else { 0 };")
+ elif isStructT(ft, types):
+ L.add(" h._h_" & s & ".push(self." & s & ".to_c()); h.c." & s & " = h._h_" & s & "[0].c;")
else:
- L.add(" " & snake & ": self." & snake & ",")
- L.add(" }")
+ L.add(" h.c." & s & " = self." & s & ";")
+ L.add(" h")
L.add(" }")
# from_c
L.add(" pub fn from_c(c: &ffi::" & rn & ") -> Self {")
L.add(" " & rn & " {")
for f in t.fields:
- let snake = camelToSnakeCase(f.name)
+ let s = camelToSnakeCase(f.name)
let ft = f.typeName.strip()
- if isStringT(ft):
- L.add(" " & snake & ": cstr(c." & snake & "),")
+ if isSeqT(ft):
+ let e = seqElem(ft)
+ var conv = "|&v| v"
+ if isStringT(e): conv = "|&p| cstr(p)"
+ elif isStructT(e, types): conv = "|e| " & capitalizeFirstLetter(e) & "::from_c(e)"
+ elif e == "bool": conv = "|&v| v != 0"
+ L.add(" " & s & ": if c." & s & ".is_null() { Vec::new() } else { unsafe { std::slice::from_raw_parts(c." & s & ", c." & s & "_len) }.iter().map(" & conv & ").collect() },")
+ elif isOptT(ft):
+ let e = optElem(ft)
+ var val = "c." & s
+ if isStringT(e): val = "cstr(c." & s & ")"
+ elif isStructT(e, types): val = capitalizeFirstLetter(e) & "::from_c(&c." & s & ")"
+ elif e == "bool": val = "c." & s & " != 0"
+ L.add(" " & s & ": if c." & s & "_present != 0 { Some(" & val & ") } else { None },")
+ elif isStringT(ft):
+ L.add(" " & s & ": cstr(c." & s & "),")
elif ft == "bool":
- L.add(" " & snake & ": c." & snake & " != 0,")
+ L.add(" " & s & ": c." & s & " != 0,")
elif isStructT(ft, types):
- L.add(" " & snake & ": " & capitalizeFirstLetter(ft) &
- "::from_c(&c." & snake & "),")
+ L.add(" " & s & ": " & capitalizeFirstLetter(ft) & "::from_c(&c." & s & "),")
else:
- L.add(" " & snake & ": c." & snake & ",")
+ L.add(" " & s & ": c." & s & ",")
L.add(" }")
L.add(" }")
L.add("}")
@@ -257,11 +333,12 @@ proc emitApiRs(
for ep in p.extraParams:
params.add(camelToSnakeCase(ep.name) & ": " & capitalizeFirstLetter(ep.typeName))
L.add(" pub fn new(" & params.join(", ") & ") -> Result {")
- L.add(" let mut strings = Vec::new();")
var args: seq[string] = @[]
for ep in p.extraParams:
- L.add(" let c_" & camelToSnakeCase(ep.name) & " = " & camelToSnakeCase(ep.name) & ".to_c_inner(&mut strings);")
- args.add("c_" & camelToSnakeCase(ep.name))
+ # The holder must outlive the call (it owns the CStrings/arrays the C
+ # struct points into); the native ctor deep-copies before returning.
+ L.add(" let c_" & camelToSnakeCase(ep.name) & " = " & camelToSnakeCase(ep.name) & ".to_c();")
+ args.add("c_" & camelToSnakeCase(ep.name) & ".c")
L.add(" let (tx, rx) = sync_channel::>(1);")
L.add(" let raw = Box::into_raw(Box::new(tx)) as *mut c_void;")
let cargs = (args & @["ack_cb", "raw"]).join(", ")
@@ -269,7 +346,6 @@ proc emitApiRs(
L.add(" let res = rx.recv().map_err(|_| String::from(\"callback channel closed\"))?;")
L.add(" res?;")
L.add(" if ctx.is_null() { return Err(String::from(\"" & p.procName & " returned null\")); }")
- L.add(" let _ = strings;")
L.add(" Ok(" & nodeT & " { ctx })")
L.add(" }")
L.add("")
@@ -288,12 +364,10 @@ proc emitApiRs(
var callArgs: seq[string] = @["self.ctx", cbName, "raw"]
for ep in p.extraParams:
params.add(camelToSnakeCase(ep.name) & ": " & capitalizeFirstLetter(ep.typeName))
- conv.add(" let c_" & camelToSnakeCase(ep.name) & " = " & camelToSnakeCase(ep.name) & ".to_c_inner(&mut strings);")
- callArgs.add("c_" & camelToSnakeCase(ep.name))
+ conv.add(" let c_" & camelToSnakeCase(ep.name) & " = " & camelToSnakeCase(ep.name) & ".to_c();")
+ callArgs.add("c_" & camelToSnakeCase(ep.name) & ".c")
L.add(" pub fn " & rustMethod(p.procName, libName) & "(&self" & (if params.len > 0: ", " & params.join(", ") else: "") & ") -> Result<" & retT & ", String> {")
- if conv.len > 0:
- L.add(" let mut strings = Vec::new();")
- for c in conv: L.add(c)
+ for c in conv: L.add(c)
L.add(" let (tx, rx) = sync_channel::>(1);")
L.add(" let raw = Box::into_raw(Box::new(tx)) as *mut c_void;")
L.add(" let rc = unsafe { ffi::" & p.procName & "(" & callArgs.join(", ") & ") };")
@@ -301,8 +375,6 @@ proc emitApiRs(
L.add(" drop(unsafe { Box::from_raw(raw as *mut SyncSender>) });")
L.add(" return Err(String::from(\"" & p.procName & " dispatch failed\"));")
L.add(" }")
- if conv.len > 0:
- L.add(" let _ = strings;")
L.add(" rx.recv().map_err(|_| String::from(\"callback channel closed\"))?")
L.add(" }")
L.add("")