{"id":25703407,"url":"https://github.com/dashpay/ferment","last_synced_at":"2026-03-02T03:03:39.961Z","repository":{"id":262208154,"uuid":"866960013","full_name":"dashpay/ferment","owner":"dashpay","description":null,"archived":false,"fork":false,"pushed_at":"2025-06-17T05:18:54.000Z","size":7743,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-06-17T06:25:47.729Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"RenderScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dashpay.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2024-10-03T07:41:28.000Z","updated_at":"2025-06-17T05:18:41.000Z","dependencies_parsed_at":"2025-01-15T12:26:34.180Z","dependency_job_id":"73bf4c4b-db41-4256-95be-2bca339ee67d","html_url":"https://github.com/dashpay/ferment","commit_stats":null,"previous_names":["dashpay/ferment"],"tags_count":11,"template":false,"template_full_name":null,"purl":"pkg:github/dashpay/ferment","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dashpay%2Fferment","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dashpay%2Fferment/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dashpay%2Fferment/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dashpay%2Fferment/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dashpay","download_url":"https://codeload.github.com/dashpay/ferment/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dashpay%2Fferment/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29991299,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-02T01:47:34.672Z","status":"online","status_checked_at":"2026-03-02T02:00:07.342Z","response_time":60,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-02-25T05:29:14.764Z","updated_at":"2026-03-02T03:03:39.949Z","avatar_url":"https://github.com/dashpay.png","language":"RenderScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# ferment\nSyntax-tree morphing tool for FFI (work in progress)\n\nAllows to generate an FFI-compliant equivalent for rust types (structures, enums, types, functions).\n\nThe project is a rust-workspace consisting several crates:\n1. `ferment`: A traits that provide conversion methods from/to FFI-compatible types and some helper functions and structures\n2. `ferment-sys`: a tool for morphing FFI-compatible syntax trees that uses the power of the `syn` crate.\n3. `ferment-macro`: a procedural macro that just catch target code as syn-based item.\n4. `ferment-example`: provides example of usage.\n\nA procedural macro consists of 2 macros:\n\n1. `export` - for structures / enums / functions / types\n2. `register` - for custom-defined conversions\n3. `opaque` - for opaque pointers (deprecated: now every object considered as opaque by default)\n\n**Usage**\n\nCrate is not published yet, so use it for example locally\n\n```toml\nferment = { path = \"../../ferment/ferment\" }\nferment-macro = { path = \"../../ferment/ferment-macro\" }\nferment-sys = { path = \"../../ferment/ferment-sys\" }\n```\n\nUsing the tool implies using `cbindgen` with a configuration like this:\n\n```rust\nextern crate cbindgen;\nextern crate ferment_sys;\n\nfn main() {\n    const SELF_NAME: \u0026str = \"example_nested\";\n    match ferment_sys::Ferment::with_crate_name(SELF_NAME)\n        .with_default_mod_name()\n        .with_cbindgen_config_from_file(\"cbindgen.toml\")\n        .with_external_crates(vec![\n            \"versioned-feature-core\",\n            \"example-simple\",\n            \"dashcore\",\n            \"dpp\",\n            \"platform-value\",\n            \"platform-version\"\n        ])\n        .with_languages(vec![\n            #[cfg(feature = \"objc\")]\n            ferment_sys::Lang::ObjC(ferment_sys::ObjC::new(ferment_sys::XCodeConfig {\n                class_prefix: \"DS\".to_string(),\n                framework_name: \"DSExampleNested\".to_string(),\n                header_name: SELF_NAME.to_string()\n            }\n            )),\n        ])\n        .generate() {\n        Ok(_) =\u003e println!(\"[ferment] [ok]: {SELF_NAME}\"),\n        Err(err) =\u003e panic!(\"[ferment] [err]: {}\", err)\n    }\n}\n```\n\n**Examples**\n\nFor traits marked for export like this:\n```rust\n#[ferment_macro::export]\npub trait IHaveChainSettings {\n    fn name(\u0026self) -\u003e String;\n}\n```\nYou can also use macro with comma-separated trait names \n```rust\n#[ferment_macro::export(IHaveChainSettings)]\npub enum ChainType {\n    MainNet,\n    TestNet,\n    DevNet(DevnetType)\n}\n```\nThis will expose bindings for trait methods for particular types\n\nFor the structure labeled with `ferment_macro::export`\n\n```rust\n#[derive(Clone)]\n#[ferment_macro::export]\npub struct LLMQSnapshot {\n    pub member_list: Vec\u003cu8\u003e,\n    pub skip_list: Vec\u003ci32\u003e,\n    pub skip_list_mode: LLMQSnapshotSkipMode,\n    pub option_vec: Option\u003cVec\u003cu8\u003e\u003e,\n}\n```\nthe following code with FFI-compatible fields and corresponding from/to conversions will be generated:\n```rust\n#[doc = \"FFI-representation of the \"crate::model::snapshot::LLMQSnapshot\"\"]\n#[repr(C)]\n#[derive(Clone)]\n#[allow(non_camel_case_types)]\npub struct LLMQSnapshot {\n    pub member_list: *mut crate::fermented::generics::Vec_u8,\n    pub skip_list: *mut crate::fermented::generics::Vec_i32,\n    pub skip_list_mode: *mut crate::fermented::types::model::snapshot::LLMQSnapshotSkipMode,\n    pub option_vec: *mut crate::fermented::generics::Vec_u8,\n}\nimpl ferment::FFIConversionFrom\u003ccrate::model::snapshot::LLMQSnapshot\u003e for LLMQSnapshot {\n    unsafe fn ffi_from_const(ffi: *const LLMQSnapshot) -\u003e crate::model::snapshot::LLMQSnapshot {\n        let ffi_ref = \u0026*ffi;\n        crate::model::snapshot::LLMQSnapshot {\n            member_list: ferment::FFIConversionFrom::ffi_from(ffi_ref.member_list),\n            skip_list: ferment::FFIConversionFrom::ffi_from(ffi_ref.skip_list),\n            skip_list_mode: ferment::FFIConversionFrom::ffi_from(ffi_ref.skip_list_mode),\n            option_vec: ferment::FFIConversionFrom::ffi_from_opt(ffi_ref.option_vec),\n        }\n    }\n}\nimpl ferment::FFIConversionTo\u003ccrate::model::snapshot::LLMQSnapshot\u003e for LLMQSnapshot {\n    unsafe fn ffi_to_const(obj: crate::model::snapshot::LLMQSnapshot) -\u003e *const LLMQSnapshot {\n        ferment::boxed(LLMQSnapshot {\n            member_list: ferment::FFIConversionTo::ffi_to(obj.member_list),\n            skip_list: ferment::FFIConversionTo::ffi_to(obj.skip_list),\n            skip_list_mode: ferment::FFIConversionTo::ffi_to(obj.skip_list_mode),\n            option_vec: match obj.option_vec {\n                Some(vec) =\u003e ferment::FFIConversionTo::ffi_to(vec),\n                None =\u003e std::ptr::null_mut(),\n            },\n        })\n    }\n}\nimpl ferment::FFIConversionDestroy\u003ccrate::model::snapshot::LLMQSnapshot\u003e for LLMQSnapshot {\n    unsafe fn destroy(ffi: *mut LLMQSnapshot) {\n       ferment::unbox_any(ffi);\n    }\n}\nimpl Drop for LLMQSnapshot {\n    fn drop(\u0026mut self) {\n        unsafe {\n            let ffi_ref = self;\n            ferment::unbox_any(ffi_ref.member_list);\n           ferment::unbox_any(ffi_ref.skip_list);\n            \u003ccrate::fermented::types::model::snapshot::LLMQSnapshotSkipMode as ferment::FFIConversionDestroy\u003ccrate::model::snapshot::LLMQSnapshotSkipMode\u003e\u003e::\n            destroy(ffi_ref.skip_list_mode);\n            if !ffi_ref.option_vec.is_null() {\n                ferment::unbox_any(ffi_ref.option_vec);\n            };\n        }\n    }\n}\n#[doc = \"# Safety\"]\n#[allow(non_snake_case)]\n#[no_mangle]\npub unsafe extern \"C\" fn LLMQSnapshot_ctor(\n    member_list: *mut crate::fermented::generics::Vec_u8,\n    skip_list: *mut crate::fermented::generics::Vec_i32,\n    skip_list_mode: *mut crate::fermented::types::model::snapshot::LLMQSnapshotSkipMode,\n    option_vec: *mut crate::fermented::generics::Vec_u8)\n    -\u003e *mut LLMQSnapshot {\n   ferment::boxed(LLMQSnapshot {\n        member_list,\n        skip_list,\n        skip_list_mode,\n        option_vec,\n    })\n}\n#[doc = \"# Safety\"]\n#[allow(non_snake_case)]\n#[no_mangle]\npub unsafe extern \"C\" fn LLMQSnapshot_destroy(ffi: *mut LLMQSnapshot) {\n   ferment::unbox_any(ffi);\n}\n\n```\n\nFor the function labeled with `export`\n\n```rust\n#[ferment_macro::export]\npub fn address_with_script_pubkey(script: Vec\u003cu8\u003e) -\u003e Option\u003cString\u003e {\n    Some(format_args!(\"{0:?}\", script).to_string())\n}\n```\nthe following code will be generated:\n```rust\n#[doc = \"FFI-representation of the \"address_with_script_pubkey\"\"]\n#[doc = \"# Safety\"]\n#[no_mangle]\npub unsafe extern \"C\" fn ffi_address_with_script_pubkey(script: *mut crate::fermented::generics::Vec_u8) -\u003e *mut std::os::raw::c_char {\n    let conversion = ferment::FFIConversionFrom::ffi_from(script);\n    let obj = crate::example::address::address_with_script_pubkey(conversion);\n   ferment::FFIConversionTo::ffi_to_opt(obj)\n}\n```\n\nFor type aliases labeled with `export`\n\n```rust\n#[ferment_macro::export]\npub type HashID = [u8; 32];\n```\nthe following code will be generated in `crate::fermented::types::*` with similar conversions and bindings:\n```rust\n#[repr(C)]\n#[derive(Clone, Debug)]\npub struct HashID(*mut [u8; 32]);\n```\n\nFor traits labeled with `export`\n```rust\n#[ferment_macro::export]\npub trait IHaveChainSettings { \n    // ..\n}\n```\nThere will be vtable and trait obj generated\n```rust\n#[repr(C)]\n#[derive(Clone)]\n#[allow(non_camel_case_types)]\npub struct IHaveChainSettings_VTable { \n    // ..\n}\n#[repr(C)]\n#[derive(Clone)]\n#[allow(non_camel_case_types)]\npub struct IHaveChainSettings_TraitObject {\n    pub object: *const (),\n    pub vtable: *const IHaveChainSettings_VTable,\n}\n```\nand bindings for their implementors like this:\n```rust\n#[doc = \"# Safety\"]\n#[allow(non_snake_case)]\n#[no_mangle]\npub extern \"C\" fn ChainType_as_IHaveChainSettings_TraitObject(\n    obj: *const crate::chain::common::chain_type::ChainType) \n    -\u003e IHaveChainSettings_TraitObject {\n    IHaveChainSettings_TraitObject {\n        object: obj as *const (),\n        vtable: \u0026ChainType_IHaveChainSettings_VTable,\n    }\n}\n#[doc = \"# Safety\"]\n#[allow(non_snake_case)]\n#[no_mangle]\npub unsafe extern \"C\" fn ChainType_as_IHaveChainSettings_TraitObject_destroy(obj: IHaveChainSettings_TraitObject) {\n   ferment::unbox_any(obj.object as *mut crate::chain::common::chain_type::ChainType);\n}\n\n```\nusing this code cbindgen will be able to generate binding \n```\nstruct IHaveChainSettings_TraitObject ChainType_as_IHaveChainSettings_TraitObject(const struct ChainType *obj);\nvoid ChainType_as_IHaveChainSettings_TraitObject_destroy(struct IHaveChainSettings_TraitObject obj);\n\n```\nCurrent limitations:\n- We should mark all structures that involved into export with the macro definition\n- There is some difficulty with handling type aliases. Therefore, if possible, they should be avoided. Because, in order to guarantee that it can be processed, one has to wrap it in an unnamed struct. Which is, for most cases, less efficient than using the type it uses directly. That is, `pub type KeyID = u32` becomes `pub struct KeyID_FFI(u32)` There will be a support at some point.\n\n**Generic mangling rules**\n\nConversion follows some mangling rules and gives the name for ffi structure. \nExamples for translated names:\n- `Vec\u003cu8\u003e` -\u003e `Vec_u8`\n- `Vec\u003cu32\u003e` -\u003e `Vec_u32`\n- `Vec\u003cVec\u003cu32\u003e\u003e` -\u003e `Vec_Vec_u32`\n- `BTreeMap\u003cHashID, Vec\u003cu32\u003e\u003e` -\u003e `std_collections_Map_keys_crate_HashID_values_Vec_u32`\n- `BTreeMap\u003cHashID, Vec\u003cu32\u003e\u003e` -\u003e `std_collections_Map_keys_u32_values_Vec_u32`\n- `BTreeMap\u003cHashID, BTreeMap\u003cHashID, Vec\u003cu32\u003e\u003e\u003e` -\u003e `std_collections_Map_keys_crate_HashID_values_std_collections_Map_keys_crate_HashID_values_Vec_u32`\n- etc\n\nThen macro implements the necessary conversions for these structures. Example for `BTreeMap\u003cHashID, Vec\u003cHashID\u003e\u003e`:\n```rust\n#[repr(C)]\n#[derive(Clone)]\n#[allow(non_camel_case_types)]\npub struct std_collections_Map_keys_crate_nested_HashID_values_Vec_crate_nested_HashID {\n    pub count: usize,\n    pub keys: *mut *mut crate::fermented::types::nested::HashID,\n    pub values: *mut *mut crate::fermented::generics::Vec_crate_nested_HashID,\n}\nimpl ferment::FFIConversionFrom\u003cstd::collections::BTreeMap\u003ccrate::nested::HashID, Vec\u003ccrate::nested::HashID\u003e\u003e\u003e\nfor std_collections_Map_keys_crate_nested_HashID_values_Vec_crate_nested_HashID {\n    unsafe fn ffi_from_const(\n        ffi: *const std_collections_Map_keys_crate_nested_HashID_values_Vec_crate_nested_HashID)\n        -\u003e std::collections::BTreeMap\u003ccrate::nested::HashID, Vec\u003ccrate::nested::HashID\u003e\u003e {\n        let ffi_ref = \u0026*ffi;\n       ferment::from_complex_map(ffi_ref.count, ffi_ref.keys, ffi_ref.values)\n    }\n}\nimpl ferment::FFIConversionTo\u003cstd::collections::BTreeMap\u003ccrate::nested::HashID, Vec\u003ccrate::nested::HashID\u003e\u003e\u003e\nfor std_collections_Map_keys_crate_nested_HashID_values_Vec_crate_nested_HashID {\n    unsafe fn ffi_to_const(\n        obj: std::collections::BTreeMap\u003ccrate::nested::HashID, Vec\u003ccrate::nested::HashID\u003e\u003e)\n        -\u003e *const std_collections_Map_keys_crate_nested_HashID_values_Vec_crate_nested_HashID {\n       ferment::boxed(Self {\n            count: obj.len(),\n            keys: ferment::to_complex_group(obj.keys().cloned()),\n            values: ferment::to_complex_group(obj.values().cloned()),\n        })\n    }\n}\nimpl ferment::FFIConversionDestroy\u003cstd::collections::BTreeMap\u003ccrate::nested::HashID, Vec\u003ccrate::nested::HashID\u003e\u003e\u003e\nfor std_collections_Map_keys_crate_nested_HashID_values_Vec_crate_nested_HashID {\n    unsafe fn destroy(ffi: *mut std_collections_Map_keys_crate_nested_HashID_values_Vec_crate_nested_HashID) {\n       ferment::unbox_any(ffi);\n    }\n}\nimpl Drop for std_collections_Map_keys_crate_nested_HashID_values_Vec_crate_nested_HashID {\n    fn drop(\u0026mut self) {\n        unsafe { \n           ferment::unbox_any_vec_ptr(self.keys, self.count);\n           ferment::unbox_any_vec_ptr(self.values, self.count);\n        }\n    }\n}\n```\n\nThe final generated code is placed in the file specified in the configuration like this:\n```rust\npub mod types {\n    // package relationships are inherited\n    // so type like crate::some_module::SomeStruct will be expanded like this:\n    pub mod some_module {\n        pub struct SomeStruct {\n            // ...\n        }\n    }\n}\npub mod generics {\n    // We expand generic types separately here to avoid duplication\n    #[allow(non_camel_case_types)]\n    pub struct std_collections_Map_keys_crate_nested_HashID_values_Vec_crate_nested_HashID {\n        // ..\n    }\n}\n```\n\n**Manual conversion support** \n- We can use `[ferment_macro::register(SomeFFIIncompatibleStructOrWhatever)]`\n- It allows us to manually create custom conversions for types.\n- It's especially important for non-fermentable code like types from rust std lib or from any other 3-rd party-crates.\n- [Example](https://github.com/pankcuf/ferment/blob/ff10bec42c55935a3d2b5c457d50e6b5352b418c/ferment-example/src/asyn/query.rs#L1C1-L26C3)\n\n**[TODO](https://github.com/pankcuf/ferment/blob/master/TODO.md)**\n\n**[CHANGELOG](https://github.com/pankcuf/ferment/blob/master/CHANGELOG.md)**\n\n**Memory Cleanup Responsibility**\nAssuming we have the following structures and method:\n```rust\n#[ferment_macro::export]\npub struct InnerStruct {\n    pub i1: u64,\n    pub i2: u64,\n}\n#[ferment_macro::export]\npub struct OuterStruct {\n    pub o1: InnerStruct,\n    pub o2: InnerStruct,\n}\n#[ferment_macro::export]\npub fn create_outer(o1: InnerStruct, o2: InnerStruct) -\u003e OuterStruct {\n    OuterStruct {\n        o1,\n        o2,\n    }\n}\n```\nFerment will produce the following FFI-compatible code:\n```rust\n#[doc = \"FFI-representation of the [`crate::OuterStruct`]\"]\n#[repr(C)]\n#[derive(Clone)]\npub struct OuterStruct {\n    pub o1: *mut crate::fermented::types::InnerStruct,\n    pub o2: *mut crate::fermented::types::InnerStruct,\n}\nimpl ferment::FFIConversionFrom\u003ccrate::OuterStruct\u003e for OuterStruct {\n    unsafe fn ffi_from_const(ffi: *const OuterStruct) -\u003e crate::OuterStruct {\n        let ffi_ref = \u0026*ffi;\n        crate::OuterStruct {\n            o1: ferment::FFIConversionFrom::ffi_from(ffi_ref.o1),\n            o2: ferment::FFIConversionFrom::ffi_from(ffi_ref.o2),\n        }\n    }\n}\nimpl ferment::FFIConversionTo\u003ccrate::OuterStruct\u003e for OuterStruct {\n    unsafe fn ffi_to_const(obj: crate::OuterStruct) -\u003e *const OuterStruct {\n       ferment::boxed(OuterStruct {\n            o1: ferment::FFIConversionTo::ffi_to(obj.o1),\n            o2: ferment::FFIConversionTo::ffi_to(obj.o2),\n        })\n    }\n}\nimpl ferment::FFIConversionDestroy\u003ccrate::OuterStruct\u003e for OuterStruct {\n    unsafe fn destroy(ffi: *mut OuterStruct) {\n       ferment::unbox_any(ffi);\n    }\n}\nimpl Drop for OuterStruct {\n    fn drop(\u0026mut self) {\n        unsafe {\n            let ffi_ref = self;\n           ferment::unbox_any(ffi_ref.o1);\n           ferment::unbox_any(ffi_ref.o2);\n        }\n    }\n}\n#[doc = r\" # Safety\"]\n#[no_mangle]\n#[inline(never)]\npub unsafe extern \"C\" fn OuterStruct_ctor(\n    o1: *mut crate::fermented::types::InnerStruct,\n    o2: *mut crate::fermented::types::InnerStruct,\n) -\u003e *mut OuterStruct {\n   ferment::boxed(OuterStruct { o1, o2 })\n}\n#[doc = r\" # Safety\"]\n#[no_mangle]\npub unsafe extern \"C\" fn OuterStruct_destroy(ffi: *mut OuterStruct) {\n   ferment::unbox_any(ffi);\n}\n\n#[doc = \"FFI-representation of the [`create_outer`]\"]\n#[doc = r\" # Safety\"]\n#[no_mangle]\npub unsafe extern \"C\" fn create_outer(\n    o1: *mut crate::fermented::types::InnerStruct,\n    o2: *mut crate::fermented::types::InnerStruct,\n) -\u003e *mut crate::fermented::types::OuterStruct {\n    let obj = crate::create_outer(\n       ferment::FFIConversionFrom::ffi_from(o1),\n        ferment::FFIConversionFrom::ffi_from(o2),\n    );\n   ferment::FFIConversionTo::ffi_to(obj)\n}\n```\nThis will produce C-bindings like this:\n```c\nstruct OuterStruct *OuterStruct_ctor(struct InnerStruct *o1, struct InnerStruct *o2);\nvoid OuterStruct_destroy(struct OuterStruct *ffi);\nstruct InnerStruct *InnerStruct_ctor(uint64_t i1, uint64_t i2);\nvoid InnerStruct_destroy(struct InnerStruct *ffi);\nstruct OuterStruct *create_outer(struct InnerStruct *o1, struct InnerStruct *o2);\n```\nSo here we have 2 different approaches, in `OuterStruct_ctor` and in `create_outer`. Although, from C perspective they look similar. This makes the difference in memory management. \n\n1. In the ctor approach, cloning does not occur. Instead, ownership of the pointers is transferred to the rust. We create a structure without conversions, and the ownership of pointers is transferred to the fields of the structure. And after that these pointers cannot be used in C. Accordingly, Rust is responsible for cleaning the transferred pointers. \n    ```\n    struct InnerStruct* is1 = InnerStruct_ctor(1, 2);\n    struct InnerStruct* is2 = InnerStruct_ctor(3, 4);\n    struct OuterStruct* os1 = OuterStruct_ctor(is1, is2);\n    // At this point, `is1` and `is2` should not be used or freed in C.\n    OuterStruct_destroy(os1); // Rust frees `os1` and its `InnerStruct` instances.\n    ```\n\n2. Cloning occurs in the regular function approach. In this case, it will be accordingly that C will be responsible for clearing these pointers.\n    ```\n    struct InnerStruct* is3 = InnerStruct_ctor(5, 6);\n    struct InnerStruct* is4 = InnerStruct_ctor(7, 8);\n    struct OuterStruct* os2 = create_outer(is3, is4);\n    // `is3` and `is4` are cloned by Rust, so C still owns `is3` and `is4` and must free them.\n    InnerStruct_destroy(is3); // C frees `is3`.\n    InnerStruct_destroy(is4); // C frees `is4`.\n    OuterStruct_destroy(os2); // Rust kills `os2` and its cloned `InnerStruct` instances.\n    ```\n\nBack in the days decisions were made from the point of view of efficiency, it would be better to always give pointer's ownership to the rust. But to do this, you will have to write code in rust only in an FFI-compatible style (which is ridiculous), or modify the `ferment` to the state where not only FFI-compatible methods/structures are fermented, but also the code itself inside them.\n\n\n\n\nFAQ: \n- if you see no opaque pointers in cbindgen header makes sure you did include crate-owner in the list in settings \n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdashpay%2Fferment","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdashpay%2Fferment","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdashpay%2Fferment/lists"}