Skip to content

Change UniFFI macro #[uniffi::Object] to #[uniffi::Record] for gl-sdk #665

@ShahanaFarooqui

Description

@ShahanaFarooqui

Benefits

1. Reduced SDK Binary Size

  • Record generates simpler data representations (e.g., Kotlin data class, Swift struct)
  • No reference counting (Arc) infrastructure
  • No handle management or method dispatch machinery
  • Less generated glue code per type
  • Overall, this reduces SDK size and simplifies the generated bindings.

2. Direct Field Access

  • Fields are exposed directly in foreign languages
  • No need for explicit getter methods
  • Produces a cleaner, more idiomatic API for pure data types

3. Simpler Memory Model

  • Passed by value rather than by reference
  • No Arc wrapper required in Rust
  • Easier to reason about and use in Rust code

4. Hash and Equality Support

  • Record types can derive Hash, Eq, and related traits
  • Object types using Arc cannot easily derive these
  • Enables use in HashMap, HashSet, etc.

5. Clearer Semantics

  • Record accurately models value/data types
  • Object should be reserved for types with behavior, identity, or lifecycle management
  • Improves architectural clarity and API design

Implementation Notes

Before (Object-based)

#[derive(uniffi::Object)]
pub struct OnchainReceiveResponse {
    bech32: String,
    p2tr: String,
}

#[uniffi::export]
impl OnchainReceiveResponse {
    /// Get the bech32 (native segwit) address
    pub fn bech32(&self) -> String {
        self.bech32.clone()
    }

    /// Get the taproot (P2TR) address
    pub fn p2tr(&self) -> String {
        self.p2tr.clone()
    }
}

After (Record-based)

#[derive(uniffi::Record)]
pub struct OnchainReceiveResponse {
    pub bech32: String,
    pub p2tr: String,
}

This removes the need for exported getters and eliminates unnecessary object infrastructure.

Considerations

  • Applicable for existing types from ./libs/gl-sdk/src/node.rs:
    • OnchainSendResponse
    • OnchainReceiveResponse
    • SendResponse
    • ReceiveResponse
  • Record is well-suited for small to medium-sized pure data containers, as the entire struct is serialized and deserialized each time it crosses the FFI boundary (which can become costly for large types or frequent transfers). In contrast, Object passes only a handle (a u64, 8 bytes), making it more efficient for large data structures, types that are passed frequently, or types with methods and mutable state.
  • This is a breaking change for consumers currently accessing these types via getters. However, since gl-sdk is still in active development and the getters were introduced only recently, this is an ideal time to make the change before wider adoption.
  • ⚠️ Current limitation: This approach does not build with uniffi v0.29.4 and may require downgrading to v0.28.

Reference:

#662 (comment)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions