ark_api_ffi/ffi/example_manual.rs
1use crate::ErrorCode;
2use bytemuck::CheckedBitPattern;
3use bytemuck::NoUninit;
4use num_enum::IntoPrimitive;
5use num_enum::TryFromPrimitive;
6
7// 3. By applying #[repr(C)] we're telling Rust that we want to layout `IsReady`
8// exactly as if this struct was declared in C itself.
9//
10// By deriving the `NoUninit` and `CheckedBitPattern` traits, we are adding compile-time
11// checks that our type meets a few important invariants that all FFI types must meet.
12// More about this later.
13#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, NoUninit, CheckedBitPattern)]
14#[repr(C)]
15pub struct IsReady {
16 pub is_ready: bool,
17 // More details on why this is necessary later, but basically, if this wasn't
18 // here then there would be padding bytes in this struct layout so that `size`
19 // could be aligned. We take those padding bytes up with "explicit"/"manual" padding
20 // here instead, so that we can satisfy the requirements of `NoUninit`
21 pub _pad: [u8; 3],
22 pub size: u32,
23}
24
25// 4. By applying #[repr(u32)] we're telling Rust that we want `State` to
26// be represented as a primitive. `NoUninit`, `CheckedBitPattern`,
27// `IntoPrimitive` and `TryFromPrimitive` are needed as well to safely convert/cast
28// between the primitive type and the enum.
29#[repr(u32)]
30#[derive(Clone, Copy, IntoPrimitive, TryFromPrimitive, NoUninit, CheckedBitPattern)]
31pub enum State {
32 SomeStateX,
33 SomeStateY,
34}
35
36#[link(wasm_import_module = "example-manual")]
37extern "C" {
38 // We need to rename the function to add a prefix. This is standard in
39 // most larger C APIs to avoid name clashes in the presence of other
40 // external code. In `ark`, the convention is the snake case name of the
41 // API you are exposing, joined with the "normal" function name by `__`
42 pub fn example__is_ready(
43 // 1. &str is a ultimately an array of bytes, so we need to pull apart
44 // the string into FFI compatible components, which are the pointer
45 // to the beginning of that array, and the length of it.
46 //
47 // Note that since Wasm is 32-bit only (atm), we use `u32` for lengths
48 // that in Rust would normally be usize, the size of the pointer for
49 // the target architecture
50 name_ptr: *const u8,
51 name_len: u32,
52 // This one is fine!
53 max: u32,
54 // 4. As `State` is being represented as a `u32` primitive type we use `u32`
55 // as the function parameter.
56 state: u32,
57 // 2. Another limitation of the C ABI (but not wasm actually) is that
58 // you can only have a single return value. Since we want to use a
59 // Result of either an Ok(IsReady) or Err(ErrorCode) we are kind of
60 // stuck since we have to get rid of the Result since we can't represent
61 // it in the C ABI.
62 //
63 // So, we instead change the signature entirely, and always return an
64 // `ErrorCode` (which has a Success value for Ok), but then send a
65 // mutable pointer to an `IsReady` that the host side can fill out to
66 // give us an additional return value
67 is_ready_out_ptr: *mut IsReady,
68 ) -> ErrorCode;
69}