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