expo-modules-rs 0.1.0

Rust SDK for writing Expo native modules via direct JSI integration
Documentation
#pragma once

// This header is included by the cxx-generated bridge code.
// IMPORTANT: Do NOT define ValueKind, FfiValue, or RuntimeHandle here —
// those are shared types owned by the cxx bridge (defined in bridge.rs).
// Only declare C++ functions that the cxx bridge needs to link against.

// When building as part of Expo/React Native, these come from the build system.
// For standalone compilation, we provide minimal forward declarations.
#ifdef EXPO_RUST_JSI_STANDALONE
#include "jsi_types.h"
#else
#include <jsi/jsi.h>
#endif

#include "rust/cxx.h"
#include <memory>
#include <string>
#include <vector>
#include <functional>
#include <cstdint>
#include <mutex>
#include <unordered_map>

namespace expo {
namespace rust_jsi {

// Forward declarations for cxx-owned types.
// Full definitions are generated by the cxx bridge.
struct FfiValue;
struct RuntimeHandle;

// ---- C++ functions callable from Rust (declared in the cxx bridge) ----

// Value constructors
FfiValue jsi_make_undefined();
FfiValue jsi_make_null();
FfiValue jsi_make_bool(bool val);
FfiValue jsi_make_number(double val);
FfiValue jsi_make_string(rust::Str val);

// Object operations
FfiValue jsi_create_object(const RuntimeHandle& rt);
void jsi_object_set_property(const RuntimeHandle& rt, uint64_t obj_handle,
                             rust::Str name, const FfiValue& value);
FfiValue jsi_object_get_property(const RuntimeHandle& rt, uint64_t obj_handle,
                                 rust::Str name);

// Array operations
FfiValue jsi_create_array(const RuntimeHandle& rt, uint32_t length);
void jsi_array_set_value(const RuntimeHandle& rt, uint64_t arr_handle,
                         uint32_t index, const FfiValue& value);
FfiValue jsi_array_get_value(const RuntimeHandle& rt, uint64_t arr_handle,
                             uint32_t index);
uint32_t jsi_array_length(const RuntimeHandle& rt, uint64_t arr_handle);

// Module registration
void jsi_register_module(const RuntimeHandle& rt, rust::Str name,
                         uint64_t obj_handle);

// Host function creation
FfiValue jsi_create_host_function(const RuntimeHandle& rt, rust::Str name,
                                  uint32_t param_count, uint64_t callback_id);
void jsi_object_set_host_function(const RuntimeHandle& rt, uint64_t obj_handle,
                                  rust::Str name, uint64_t fn_handle);

// Error throwing
void jsi_throw_error(const RuntimeHandle& rt, rust::Str message);

// Promise creation and function calling
FfiValue jsi_create_promise(const RuntimeHandle& rt);
void jsi_call_function(const RuntimeHandle& rt, uint64_t fn_handle,
                       const FfiValue& arg);

// ---- Types and classes NOT part of the cxx bridge ----

// Rust callback types for HostObject property access
using RustPropertyGetter = FfiValue(*)(void* ctx, const char* name, size_t name_len);
using RustPropertySetter = void(*)(void* ctx, const char* name, size_t name_len, const FfiValue& value);

#ifndef EXPO_RUST_JSI_STANDALONE
class RustHostObject : public facebook::jsi::HostObject {
public:
  RustHostObject(void* rust_ctx,
                 RustPropertyGetter getter,
                 RustPropertySetter setter,
                 std::vector<std::string> property_names);
  ~RustHostObject() override;

  facebook::jsi::Value get(facebook::jsi::Runtime& rt,
                           const facebook::jsi::PropNameID& name) override;
  void set(facebook::jsi::Runtime& rt,
           const facebook::jsi::PropNameID& name,
           const facebook::jsi::Value& value) override;
  std::vector<facebook::jsi::PropNameID> getPropertyNames(
      facebook::jsi::Runtime& rt) override;

private:
  void* rust_ctx_;
  RustPropertyGetter getter_;
  RustPropertySetter setter_;
  std::vector<std::string> property_names_;
};

// Conversion between jsi::Value and FfiValue
FfiValue jsi_value_to_ffi(facebook::jsi::Runtime& rt,
                          const facebook::jsi::Value& value);
facebook::jsi::Value ffi_to_jsi_value(facebook::jsi::Runtime& rt,
                                       const FfiValue& value);
#endif

// Handle table for tracking JSI objects across FFI boundary
class HandleTable {
public:
  static HandleTable& instance();

  uint64_t store(std::shared_ptr<void> obj);
  std::shared_ptr<void> get(uint64_t handle);
  void release(uint64_t handle);

private:
  HandleTable() = default;
  std::unordered_map<uint64_t, std::shared_ptr<void>> table_;
  uint64_t next_handle_ = 1;
  std::mutex mutex_;
};

} // namespace rust_jsi
} // namespace expo