Replies: 3 comments 1 reply
-
|
From DM with @vinhtc27 && @seemenkina, there were questions about how Nim dev in our organisation use a C API (either using directly the C ABI or writing a little wrapper similar to this one for example: https://github.com/arnetheduck/nim-sqlite3-abi/tree/master). Currently based on this: https://github.com/waku-org/nwaku/blob/master/waku/waku_rln_relay/rln/rln_interface.nim, it seems that this is the first choice (direct use of C ABI). However it seems like we could auto generate a Nim wrapper based on generated C headers (generated using safer_ffi tooling). Sounds like we start with a POC to see how easy it is. |
Beta Was this translation helpful? Give feedback.
-
|
A couple of thoughts that come to mind: Referring to "unsafe" in the pejorativeI don't think there is anything inherently wrong with I suggest looking into the proc-macro expansions such as the Bringing in the nim languageThe FFI story between C and Rust is well established. I don't see the benefit of thinking about the nim wrappers at this stage. The way I'm thinking of it, is that the Rust/Nim relationship will never exist: it will only ever be Rust/C, and if it just so happens to be used by Nim, then that will be because, after the fact, the C code that uses the FFI will be the result of the nim->C transpilation. Caveate: I'm not too well accomplished with nim to say this with any sort of authority. I do, however, feel suitably qualified to suggest that we initiate progress based on this as a "loosely held" position. This can change where we get input from someone that understands the technical peculiarities of Nim that highlight this as an unwise position to hold. |
Beta Was this translation helpful? Give feedback.
-
|
I get your point that "unsafe code" is not inherently problematic but that can be an issue for developers not used to deal with FFI. From what I read (and test) about safer_ffi, here is the advantages:
safer_ffi Here is a basic example showing the expanded code: #![deny(unsafe_code)] /* No `unsafe` needed! */
use ::safer_ffi::prelude::*;
/// Concatenate two input UTF-8 (_e.g._, ASCII) strings.
///
/// \remark The returned string must be freed with `rust_free_string`
#[ffi_export]
fn concat (fst: char_p::Ref<'_>, snd: char_p::Ref<'_>)
-> char_p::Box
{
let fst = fst.to_str(); // : &'_ str
let snd = snd.to_str(); // : &'_ str
format!("{}{}", fst, snd) // -------+
.try_into() // |
.unwrap() // <- no inner nulls --+
}
/// Frees a Rust-allocated string.
#[ffi_export]
fn rust_free_string (string: char_p::Box)
{
drop(string)
}and the expanded code: #![feature(prelude_import)]
#![deny(unsafe_code)]
#[prelude_import]
use std::prelude::rust_2024::*;
#[macro_use]
extern crate std;
use ::safer_ffi::prelude::*;
/// Concatenate two input UTF-8 (_e.g._, ASCII) strings.
///
/// \remark The returned string must be freed with `rust_free_string`
#[allow(improper_ctypes_definitions)]
#[forbid(elided_lifetimes_in_paths)]
extern "C" fn concat(fst: char_p::Ref<'_>, snd: char_p::Ref<'_>) -> char_p::Box {
{
/// Concatenate two input UTF-8 (_e.g._, ASCII) strings.
///
/// \remark The returned string must be freed with `rust_free_string`
#[allow(improper_ctypes_definitions)]
#[forbid(elided_lifetimes_in_paths)]
#[export_name = "concat"]
extern "C" fn concat__ffi_export__(
fst: <char_p::Ref<'_> as ::safer_ffi::ඞ::ConcreteReprC>::ConcreteCLayout,
snd: <char_p::Ref<'_> as ::safer_ffi::ඞ::ConcreteReprC>::ConcreteCLayout,
) -> <char_p::Box as ::safer_ffi::ඞ::ConcreteReprC>::ConcreteCLayout {
let abort_on_unwind_guard;
(
abort_on_unwind_guard = ::safer_ffi::ඞ::UnwindGuard("concat"),
unsafe {
::safer_ffi::layout::into_raw(
concat(
::safer_ffi::layout::from_raw_unchecked(fst),
::safer_ffi::layout::from_raw_unchecked(snd),
),
)
},
::safer_ffi::ඞ::mem::forget(abort_on_unwind_guard),
)
.1
}
}
let fst = fst.to_str();
let snd = snd.to_str();
::alloc::__export::must_use({
::alloc::fmt::format(format_args!("{0}{1}", fst, snd))
})
.try_into()
.unwrap()
}
/// Frees a Rust-allocated string.
#[allow(improper_ctypes_definitions)]
#[forbid(elided_lifetimes_in_paths)]
extern "C" fn rust_free_string(string: char_p::Box) {
{
/// Frees a Rust-allocated string.
#[allow(improper_ctypes_definitions)]
#[forbid(elided_lifetimes_in_paths)]
#[export_name = "rust_free_string"]
extern "C" fn rust_free_string__ffi_export__(
string: <char_p::Box as ::safer_ffi::ඞ::ConcreteReprC>::ConcreteCLayout,
) -> ::safer_ffi::ඞ::CLayoutOf<()> {
let abort_on_unwind_guard;
(
abort_on_unwind_guard = ::safer_ffi::ඞ::UnwindGuard(
"rust_free_string",
),
unsafe {
::safer_ffi::layout::into_raw(
rust_free_string(::safer_ffi::layout::from_raw_unchecked(string)),
)
},
::safer_ffi::ඞ::mem::forget(abort_on_unwind_guard),
)
.1
}
}
drop(string)
} |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Introduction
As Waku requested to pass data (aka bytes) in Big endian format, we start to think about how we pass arguments to the FFI (Foreign Function Interface) of Zerokit.
Current status
Using ffi in Zerokit is done by passing bytes as arguments to the FFI interface of Zerokit. For instance, here is the ffi
generate_rln_proof_with_witnessfunction:which is calling Rust function:
So for the developer this requires:
1- Allocate two buffers (input & output)
2- Serialize the arguments into the input buffer
3- Call the Rust function which deserialize the input buffer then serialize into the output buffer
Problems
FFI functions list (requiring some serialization / deserialization)
Merkle tree api (Not exhaustive list):
Zk api:
Utils api:
Solution
Solution for C: Opaque struct managed by Rust
A common way to pass data in FFI interface is to use opaque struct. The idea is to provide object (allocated by Zerokit rust lib) + a set of defined functions to manipulate them in C.
Note:
As a basic example, we could define:
And thus this lib can be used in C:
So at the end, we would define opaque struct for:
And for each type, the following functions:
Based on CFr struct, we could declare non opaque struct like:
And use them in C:
Solution for Wasm
Current rln-wasm also use the serialization & deserialization way to pass arguments ffi (+ use the wasm-bindgen crate to generate the JS / TS bindings).
But wasm-bindgen allow the use of opaque structure and so we could define something like:
And use it in JavaScript (tested with nodejs version 23.7):
Similarly to C FFI solution, we could create additional structures like:
and in JavaScript:
Notes:
#[wasm_bindgen(constructor)]allow to use the standard Javascript declaration like:let fr = new rln_wasm.WasmFr(BigInt(123))Limitations:
Vec<WasmFr>is ok.Conclusion
The proposed solution has enormous potential to improve Zerokit's FFI as we have shown with the provided examples.
While I'm confident with the examples, the best way to transition, would be to start a POC with a more complete (but still not exhaustive) example like: generating a RLN proof and verifying it in C. It would also be worth checking about how the current examples would work with Nim.
Beta Was this translation helpful? Give feedback.
All reactions