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}