├── .cargo └── config.toml ├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── doc ├── benchmark.png └── fcplug_schematic.png ├── go.work ├── publish.sh ├── rust ├── fcplug-build │ ├── Cargo.toml │ └── src │ │ ├── config.rs │ │ ├── gen_go_os_arch_rs.sh │ │ ├── gen_rust_os_arch_rs.sh │ │ ├── generator.rs │ │ ├── go_os_arch_gen.rs │ │ ├── lib.rs │ │ ├── os_arch.rs │ │ ├── rust_os_arch_gen.rs │ │ ├── with_codec │ │ ├── gen_go_codec.rs │ │ ├── gen_rust_codec.rs │ │ └── mod.rs │ │ └── without_codec │ │ ├── gen_go_no_codec.rs │ │ ├── gen_rust_no_codec.rs │ │ └── mod.rs └── fcplug │ ├── Cargo.toml │ └── src │ ├── basic.rs │ ├── lib.rs │ ├── log.rs │ ├── protobuf.rs │ └── serde.rs └── samples ├── echo ├── Cargo.toml ├── README.md ├── build.rs ├── build.sh ├── cgobin │ ├── clib_goffi_gen.go │ └── clib_goffi_impl.go ├── echo.thrift ├── echo_gen.go ├── echo_test.go ├── go.mod └── src │ ├── echo_ffi │ ├── echo_gen.rs │ └── mod.rs │ └── lib.rs ├── echo_pb ├── Cargo.toml ├── README.md ├── build.rs ├── build.sh ├── cgobin │ ├── clib_goffi_gen_darwin_amd64.go │ ├── clib_goffi_gen_linux_amd64.go │ └── clib_goffi_impl.go ├── echo.proto ├── echo_pb.pb.go ├── echo_pb_gen_darwin_amd64.go ├── echo_pb_gen_linux_amd64.go ├── go.mod ├── go_call_rust_test.go └── src │ ├── echo_pb_ffi │ ├── echo_pb_gen.rs │ └── mod.rs │ ├── lib.rs │ └── main.rs └── echo_thrift ├── Cargo.toml ├── README.md ├── build.rs ├── build.sh ├── cgobin ├── clib_goffi_gen.go └── clib_goffi_impl.go ├── echo.thrift ├── echo_thrift.thrift.go ├── echo_thrift_gen.go ├── go.mod ├── go_call_rust_test.go └── src ├── echo_thrift_ffi ├── echo_thrift_gen.rs └── mod.rs └── lib.rs /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [env] 2 | CARGO_WORKSPACE_DIR = { value = "", relative = true } 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .vscode/ 3 | 4 | *.a 5 | *.h 6 | 7 | target/ 8 | Cargo.lock 9 | 10 | go.work.sum 11 | go.sum 12 | samples/echo_thrift/echo_thrift 13 | samples/echo_pb/echo_pb 14 | samples/echo/echo 15 | *.rlib 16 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 2 | 3 | [profile.release] 4 | opt-level = 3 5 | debug = true 6 | debug-assertions = false 7 | overflow-checks = false 8 | lto = true 9 | incremental = false 10 | codegen-units = 1 11 | rpath = false 12 | 13 | [profile.dev.build-override] 14 | opt-level = 0 15 | debug = true 16 | 17 | [workspace] 18 | resolver = "2" 19 | members = ["rust/fcplug", "rust/fcplug-build", "samples/echo_pb"] 20 | exclude = ["samples/echo_thrift", "samples/echo"] 21 | 22 | [workspace.package] 23 | edition = "2021" 24 | version = "0.4.6" 25 | authors = ["Andeya "] 26 | description = "Foreign-Clang-Plugin solution, such as solving rust and go two-way calls" 27 | repository = "https://github.com/andeya/fcplug" 28 | license = "Apache-2.0" 29 | readme = "README.md" 30 | documentation = "https://docs.rs/fcplug" 31 | keywords = ["go-rust", "rust-go", "ffi", "cgo"] 32 | categories = ["development-tools::ffi", "external-ffi-bindings"] 33 | 34 | [workspace.dependencies] 35 | fcplug = { path = "rust/fcplug" } 36 | fcplug-build = { path = "rust/fcplug-build" } 37 | 38 | pilota-build = { version = "0.7.13", package = "pilota-build2" } 39 | 40 | syn = { version = "1.0.109", features = ["full"] } 41 | quote = "1.0.29" 42 | proc-macro2 = "1.0.63" 43 | protobuf = "3.2.0" 44 | protobuf-parse = "3.2.0" 45 | protobuf-codegen = "3.2.0" 46 | cbindgen = "0.24.5" 47 | lazy_static = "1.4.0" 48 | regex = "1" 49 | defer-lite = "1.0.0" 50 | tracing = "0.1" 51 | tracing-appender = "0.2" 52 | tracing-subscriber = "0.3" 53 | flatbuffers = "23.5.26" 54 | flatc-rust = "0.2.0" 55 | bindgen = "0.66.1" 56 | anyhow = "1" 57 | pilota = "0.7.0" 58 | serde = "1" 59 | serde_json = "1" 60 | walkdir = "2.3.3" 61 | md5 = "0.7.0" 62 | strum = { version = "0.25", features = ["derive"] } 63 | backtrace = "0.3" 64 | pathdiff = "0.2" 65 | 66 | [patch.crates-io] 67 | fcplug-build = { path = "rust/fcplug-build" } 68 | fcplug = { path = "rust/fcplug" } 69 | #pilota-build = { path = "../pilota-build", package = "pilota-build2" } 70 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2023 Andeya. 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # fcplug 2 | 3 | Foreign-Clang-Plugin solution, such as solving rust and go two-way calls. 4 | 5 | [![Crates.io][crates-badge]][crates-url] 6 | [![Apache-2.0 licensed][mit-badge]][mit-url] 7 | [![API Docs][docs-badge]][docs-url] 8 | 9 | [crates-badge]: https://img.shields.io/crates/v/fcplug-build.svg 10 | [crates-url]: https://crates.io/crates/fcplug-build 11 | [mit-badge]: https://img.shields.io/badge/license-Apache2.0-blue.svg 12 | [mit-url]: https://github.com/andeya/fcplug/blob/main/LICENSE 13 | [docs-badge]: https://img.shields.io/badge/API-Docs-green.svg 14 | [docs-url]: https://docs.rs/fcplug-build 15 | 16 | ## Features 17 | 18 | | ⇊Caller \ Callee⇉ | Go | Rust | 19 | |-------------------|:--:|:----:| 20 | | Go | - | ✅ | 21 | | Rust | ✅ | - | 22 | 23 | - Protobuf IDL codec solution: Supported! 24 | - Thrift IDL codec solution: In development... 25 | - No codec solution: In development... 26 | 27 | ## Schematic 28 | 29 | ![Fcplug Schematic](https://github.com/andeya/fcplug/raw/HEAD/doc/fcplug_schematic.png) 30 | 31 | ## Prepare 32 | 33 | - Install rust nightly 34 | 35 | ```shell 36 | curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh 37 | rustup default nightly 38 | ``` 39 | 40 | - Install go 41 | 42 | > Download [Go](https://go.dev/doc/install) 43 | > 44 | > Version go≥1.18 45 | > 46 | > Set environment variables: `CGO_ENABLED=1` 47 | 48 | - Install protoc 49 | 50 | > Use [protoc v23.2](https://github.com/protocolbuffers/protobuf/releases/tag/v23.2) 51 | > 52 | > Use [protoc-gen-go v1.5.3](https://pkg.go.dev/github.com/golang/protobuf@v1.5.3/protoc-gen-go) 53 | > ```shell 54 | > go install github.com/golang/protobuf/protoc-gen-go@v1.5.3 55 | > ``` 56 | 57 | ## Example of use 58 | 59 | Take Protobuf IDL serialization solution as an example. 60 | 61 | See the [echo_pb](https://github.com/andeya/fcplug/raw/HEAD/samples/echo_pb) 62 | 63 | #### Step 1: create/prepare a crate 64 | 65 | > Generally, Fcplug is executed in a Crate's build.sh, 66 | > and the code is automatically generated to the current Crate. 67 | 68 | - If you do not have a Crate, execute the following command to create it: 69 | 70 | ```shell 71 | cargo new --lib {crate_name} 72 | ``` 73 | 74 | - Add `staticlib` crate-type and some dependent packages, open debug log of `build.rs`, edited in Cargo.toml as 75 | follows: 76 | 77 | ```toml 78 | [lib] 79 | crate-type = ["rlib", "staticlib"] 80 | 81 | [profile.dev.build-override] 82 | opt-level = 0 83 | debug = true 84 | 85 | [dependencies] 86 | fcplug = "0.3" 87 | pilota = "0.7.0" 88 | serde = "1" 89 | serde_json = "1" 90 | 91 | [build-dependencies] 92 | fcplug-build = "0.3" 93 | ``` 94 | 95 | #### Step 2: Write the IDL file that defines the FFI interface 96 | 97 | > Write the IDL file {ffi_name} .proto in ProtoBuf format, you can put it in the root directory of {crate_name}, 98 | > the content example is as follows: 99 | 100 | ```protobuf 101 | syntax = "proto3"; 102 | 103 | message Ping { 104 | string msg = 1; 105 | } 106 | 107 | message Pong { 108 | string msg = 1; 109 | } 110 | 111 | // go call rust 112 | service RustFFI { 113 | rpc echo_rs (Ping) returns (Pong) {} 114 | } 115 | 116 | // rust call go 117 | service GoFFI { 118 | rpc echo_go (Ping) returns (Pong) {} 119 | } 120 | ``` 121 | 122 | #### Step 3: Scripting auto-generated code `build.rs` 123 | 124 | ```rust 125 | #![allow(unused_imports)] 126 | 127 | use fcplug_build::{Config, generate_code, UnitLikeStructPath}; 128 | 129 | fn main() { 130 | generate_code(Config { 131 | idl_file: "./echo.proto".into(), 132 | target_crate_dir: None, 133 | // go command dir, default to find from $GOROOT > $PATH 134 | go_root_path: None, 135 | go_mod_parent: "github.com/andeya/fcplug/samples", 136 | use_goffi_cdylib: false, 137 | add_clib_to_git: false, 138 | }); 139 | } 140 | ``` 141 | 142 | #### Step 4: Preliminary Code Generation 143 | 144 | - Execute under the current Crate: 145 | 146 | ```shell 147 | cargo build 148 | # `cargo test` and `cargo install` will also trigger the execution of build.rs to generate code 149 | ``` 150 | 151 | - Attach the generated src/{ffi_name}_ffi mod to Crate, that is, add mod {ffi_name}_ffi to the `lib.rs` file 152 | 153 | #### Step 5: Implement the FFI interface 154 | 155 | - On the rust side, you need to implement the specific trait RustFfi and trait GoFfi methods in the newly initialized 156 | file src/{ffi_name}_ffi/mod.rs. 157 |
The complete sample code of the file is as follows: 158 | 159 | ```rust 160 | #![allow(unused_variables)] 161 | 162 | pub use echo_pb_gen::*; 163 | use fcplug::{GoFfiResult, TryIntoTBytes}; 164 | use fcplug::protobuf::PbMessage; 165 | 166 | mod echo_pb_gen; 167 | 168 | impl RustFfi for FfiImpl { 169 | fn echo_rs(mut req: ::fcplug::RustFfiArg) -> ::fcplug::ABIResult<::fcplug::TBytes> { 170 | let _req = req.try_to_object::>(); 171 | #[cfg(debug_assertions)] 172 | println!("rust receive req: {:?}", _req); 173 | Pong { 174 | msg: "this is pong from rust".to_string(), 175 | } 176 | .try_into_tbytes::>() 177 | } 178 | } 179 | 180 | impl GoFfi for FfiImpl { 181 | #[allow(unused_mut)] 182 | unsafe fn echo_go_set_result(mut go_ret: ::fcplug::RustFfiArg) -> ::fcplug::GoFfiResult { 183 | #[cfg(debug_assertions)] 184 | return GoFfiResult::from_ok(go_ret.try_to_object::>()?); 185 | #[cfg(not(debug_assertions))] 186 | return GoFfiResult::from_ok(go_ret.bytes().to_owned()); 187 | } 188 | } 189 | ``` 190 | 191 | - Implement the go GoFfi interface in the one-time generated file ./cgobin/clib_goffi_impl.go. 192 |
The complete sample code of this file is as follows: 193 | 194 | ```go 195 | package main 196 | 197 | import ( 198 | "fmt" 199 | 200 | "github.com/andeya/fcplug/samples/echo_pb" 201 | "github.com/andeya/gust" 202 | ) 203 | 204 | func init() { 205 | // TODO: Replace with your own implementation, then re-execute `cargo build` 206 | GlobalGoFfi = GoFfiImpl{} 207 | } 208 | 209 | type GoFfiImpl struct{} 210 | 211 | func (g GoFfiImpl) EchoGo(req echo_pb.TBytes[echo_pb.Ping]) gust.EnumResult[echo_pb.TBytes[*echo_pb.Pong], ResultMsg] { 212 | _ = req.PbUnmarshalUnchecked() 213 | fmt.Printf("go receive req: %v\n", req.PbUnmarshalUnchecked()) 214 | return gust.EnumOk[echo_pb.TBytes[*echo_pb.Pong], ResultMsg](echo_pb.TBytesFromPbUnchecked(&echo_pb.Pong{ 215 | Msg: "this is pong from go", 216 | })) 217 | } 218 | 219 | ``` 220 | 221 | #### Step 6: Generate Final Code 222 | 223 | Execute `cargo build` `cargo test` or `cargo install` under the current Crate, trigger the execution of build.rs, and 224 | generate code. 225 | 226 | > Note: When GoFfi is defined, after compiling or changing the code for the first time, 227 | > a warning similar to the following will occur, 228 | > and you should execute cargo build twice at this time 229 | > 230 | > *warning: ... to re-execute 'cargo build' to ensure the correctness of 'libgo_echo.a'* 231 | 232 | Therefore, it is recommended to repeat cargo build three times directly in the `build.sh` script 233 | 234 | ```bash 235 | #!/bin/bash 236 | 237 | cargo build --release 238 | cargo build --release 239 | cargo build --release 240 | ``` 241 | 242 | #### Step 7: Testing 243 | 244 | - Rust calls Go tests, you can add test functions in `lib.rs`, 245 |
the sample code is as follows: 246 | 247 | ```rust 248 | #![feature(test)] 249 | 250 | extern crate test; 251 | 252 | mod echo_pb_ffi; 253 | 254 | 255 | #[cfg(test)] 256 | mod tests { 257 | use test::Bencher; 258 | 259 | use fcplug::protobuf::PbMessage; 260 | use fcplug::TryIntoTBytes; 261 | 262 | use crate::echo_pb_ffi::{FfiImpl, GoFfiCall, Ping, Pong}; 263 | 264 | #[test] 265 | fn test_call_echo_go() { 266 | let pong = unsafe { 267 | FfiImpl::echo_go::(Ping { 268 | msg: "this is ping from rust".to_string(), 269 | }.try_into_tbytes::>().unwrap()) 270 | }; 271 | println!("{:?}", pong); 272 | } 273 | 274 | #[bench] 275 | fn bench_call_echo_go(b: &mut Bencher) { 276 | let req = Ping { 277 | msg: "this is ping from rust".to_string(), 278 | } 279 | .try_into_tbytes::>() 280 | .unwrap(); 281 | b.iter(|| { 282 | let pong = unsafe { FfiImpl::echo_go::>(req.clone()) }; 283 | let _ = test::black_box(pong); 284 | }); 285 | } 286 | } 287 | ``` 288 | 289 | - Go calls Rust test, add the file `go_call_rust_test.go` in the root directory, 290 |
the sample code is as follows: 291 | 292 | ```go 293 | package echo_pb_test 294 | 295 | import ( 296 | "testing" 297 | 298 | "github.com/andeya/fcplug/samples/echo_pb" 299 | ) 300 | 301 | func TestEcho(t *testing.T) { 302 | ret := echo_pb.GlobalRustFfi.EchoRs(echo_pb.TBytesFromPbUnchecked[*echo_pb.Ping](&echo_pb.Ping{ 303 | Msg: "this is ping from go", 304 | })) 305 | if ret.IsOk() { 306 | t.Logf("%#v", ret.PbUnmarshalUnchecked()) 307 | } else { 308 | t.Logf("fail: err=%v", ret.AsError()) 309 | } 310 | ret.Free() 311 | } 312 | 313 | ``` 314 | 315 | ## Asynchronous programming 316 | 317 | - Rust Tokio asynchronous function calling Go synchronous function 318 | 319 | ```rust 320 | use fcplug::protobuf::PbMessage; 321 | use fcplug::TryIntoTBytes; 322 | use fcplug-build::task; 323 | 324 | use crate::echo_ffi::{FfiImpl, GoFfiCall, Ping, Pong}; 325 | 326 | let pong = task::spawn_blocking(move | | { 327 | // The opened task runs in a dedicated thread pool. 328 | // If this task is blocked, it will not affect the completion of other tasks 329 | unsafe { 330 | FfiImpl::echo_go::< Pong > (Ping { 331 | msg: "this is ping from rust".to_string(), 332 | }.try_into_tbytes::< PbMessage < _ > > ().unwrap()) 333 | } 334 | }).await?; 335 | 336 | ``` 337 | 338 | - Go calls Rust, at least one side is an async function 339 | 340 | > in development 341 | 342 | ## Benchmark 343 | 344 | [See benchmark code](https://github.com/andeya/fcplug/blob/HEAD/samples/echo_pb/go_call_rust_test.go) 345 | 346 | ```text 347 | goos: darwin 348 | goarch: amd64 349 | pkg: github.com/andeya/fcplug/demo 350 | cpu: Intel(R) Core(TM) i7-1068NG7 CPU @ 2.30GHz 351 | ``` 352 | 353 | ![Benchmark: fcplug(cgo->rust) vs pure go](https://github.com/andeya/fcplug/raw/HEAD/doc/benchmark.png) 354 | -------------------------------------------------------------------------------- /doc/benchmark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andeya/fcplug/04fd56ad2331af25627800aeebc8f00376f9ca3d/doc/benchmark.png -------------------------------------------------------------------------------- /doc/fcplug_schematic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andeya/fcplug/04fd56ad2331af25627800aeebc8f00376f9ca3d/doc/fcplug_schematic.png -------------------------------------------------------------------------------- /go.work: -------------------------------------------------------------------------------- 1 | use ( 2 | ./samples/echo_pb 3 | // ./samples/echo 4 | // ./samples/echo_thrift 5 | ) 6 | -------------------------------------------------------------------------------- /publish.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # cargo login --registry crates-io 4 | cargo publish --registry crates-io -p fcplug-build 5 | cargo publish --registry crates-io -p fcplug 6 | -------------------------------------------------------------------------------- /rust/fcplug-build/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "fcplug-build" 3 | edition.workspace = true 4 | version.workspace = true 5 | authors.workspace = true 6 | description.workspace = true 7 | repository.workspace = true 8 | license.workspace = true 9 | readme.workspace = true 10 | documentation.workspace = true 11 | keywords.workspace = true 12 | categories.workspace = true 13 | 14 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 15 | 16 | [dependencies] 17 | cbindgen = { workspace = true } 18 | bindgen = { workspace = true } 19 | protobuf = { workspace = true } 20 | protobuf-parse = { workspace = true } 21 | protobuf-codegen = { workspace = true } 22 | lazy_static = { workspace = true } 23 | regex = { workspace = true } 24 | flatc-rust = { workspace = true } 25 | defer-lite = { workspace = true } 26 | anyhow = { workspace = true } 27 | pilota-build = { workspace = true } 28 | walkdir = { workspace = true } 29 | md5 = { workspace = true } 30 | strum = { workspace = true } 31 | backtrace = { workspace = true } 32 | pathdiff = { workspace = true } 33 | 34 | [features] 35 | default = [] 36 | no-codec = [] 37 | -------------------------------------------------------------------------------- /rust/fcplug-build/src/config.rs: -------------------------------------------------------------------------------- 1 | use std::fs::OpenOptions; 2 | use std::path::PathBuf; 3 | use std::process::Command; 4 | use std::str::FromStr; 5 | use std::{env, fs}; 6 | 7 | use pilota_build::ir::ItemKind; 8 | use pilota_build::parser::{Parser, ProtobufParser, ThriftParser}; 9 | 10 | use crate::{ 11 | deal_output, exit_with_warning, os_arch::get_go_os_arch_from_env, GenMode, BUILD_MODE, GEN_MODE, 12 | }; 13 | 14 | const CGOBIN: &'static str = "cgobin"; 15 | 16 | #[derive(Default, Debug, Clone)] 17 | pub struct Config { 18 | pub idl_file: PathBuf, 19 | /// Target crate directory for code generation 20 | pub target_crate_dir: Option, 21 | /// go command dir, default to find from $GOROOT > $PATH 22 | pub go_root_path: Option, 23 | pub go_mod_parent: &'static str, 24 | /// If use_goffi_cdylib is true, go will be compiled into a c dynamic library. 25 | pub use_goffi_cdylib: bool, 26 | /// If add_clib_to_git is true, the c lib files will be automatically added to the git version management list. 27 | pub add_clib_to_git: bool, 28 | } 29 | 30 | #[derive(Debug, Clone)] 31 | pub(crate) enum IdlType { 32 | Proto, 33 | Thrift, 34 | ProtoNoCodec, 35 | ThriftNoCodec, 36 | } 37 | impl Default for IdlType { 38 | fn default() -> Self { 39 | IdlType::Proto 40 | } 41 | } 42 | /// unit-like struct path, e.g. `::mycrate::Abc` 43 | #[derive(Debug, Clone)] 44 | pub struct UnitLikeStructPath(pub &'static str); 45 | 46 | #[derive(Debug, Clone)] 47 | pub struct GoObjectPath { 48 | /// e.g. `github.com/xxx/mypkg` 49 | pub import: String, 50 | /// e.g. `mypkg.Abc` 51 | pub object_ident: String, 52 | } 53 | 54 | #[derive(Default, Debug, Clone)] 55 | pub(crate) struct WorkConfig { 56 | config: Config, 57 | pub(crate) go_buildmode: &'static str, 58 | pub(crate) rustc_link_kind_goffi: &'static str, 59 | pub(crate) idl_file: PathBuf, 60 | pub(crate) idl_include_dir: PathBuf, 61 | pub(crate) idl_type: IdlType, 62 | pub(crate) rust_clib_name_base: String, 63 | pub(crate) go_clib_name_base: String, 64 | pub(crate) target_out_dir: PathBuf, 65 | pub(crate) pkg_dir: PathBuf, 66 | pub(crate) pkg_name: String, 67 | pub(crate) gomod_name: String, 68 | pub(crate) gomod_path: String, 69 | pub(crate) gomod_file: PathBuf, 70 | pub(crate) rust_mod_dir: PathBuf, 71 | pub(crate) rust_mod_gen_file: PathBuf, 72 | pub(crate) rust_mod_impl_file: PathBuf, 73 | pub(crate) rust_mod_gen_name: String, 74 | pub(crate) go_lib_file: PathBuf, 75 | pub(crate) clib_gen_dir: PathBuf, 76 | pub(crate) go_main_dir: PathBuf, 77 | pub(crate) go_main_file: PathBuf, 78 | pub(crate) go_main_impl_file: PathBuf, 79 | pub(crate) rust_clib_file: PathBuf, 80 | pub(crate) rust_clib_header: PathBuf, 81 | pub(crate) go_clib_file: PathBuf, 82 | pub(crate) go_clib_header: PathBuf, 83 | pub(crate) has_goffi: bool, 84 | pub(crate) has_rustffi: bool, 85 | pub(crate) rust_mod_impl_name: String, 86 | pub(crate) fingerprint: String, 87 | pub(crate) fingerprint_path: PathBuf, 88 | } 89 | 90 | impl WorkConfig { 91 | pub(crate) fn new(config: Config) -> WorkConfig { 92 | let mut c = WorkConfig::default(); 93 | c.config = config; 94 | c.rust_mod_impl_name = "FfiImpl".to_string(); 95 | c.go_buildmode = if c.config.use_goffi_cdylib { 96 | "c-shared" 97 | } else { 98 | "c-archive" 99 | }; 100 | c.rustc_link_kind_goffi = if c.config.use_goffi_cdylib { 101 | "dylib" 102 | } else { 103 | "static" 104 | }; 105 | c.idl_file = c.config.idl_file.clone(); 106 | c.idl_include_dir = c.idl_file.parent().unwrap().to_path_buf(); 107 | c.idl_type = Self::new_idl_type(&c.idl_file); 108 | c.rust_clib_name_base = env::var("CARGO_PKG_NAME").unwrap().replace("-", "_"); 109 | c.go_clib_name_base = "go_".to_string() + &c.rust_clib_name_base; 110 | c.target_out_dir = Self::new_target_out_dir(); 111 | c.clib_gen_dir = c.target_out_dir.clone(); 112 | c.fingerprint_path = c.clib_gen_dir.join("fcplug.fingerprint"); 113 | c.pkg_dir = Self::new_pkg_dir(&c.config.target_crate_dir); 114 | c.gomod_file = c.pkg_dir.join("go.mod"); 115 | c.pkg_name = Self::new_pkg_name(&c.pkg_dir); 116 | c.gomod_name = c.pkg_name.clone(); 117 | c.gomod_path = format!( 118 | "{}/{}", 119 | c.config.go_mod_parent.trim_end_matches("/"), 120 | c.gomod_name 121 | ); 122 | c.rust_mod_dir = c.pkg_dir.join("src").join(c.pkg_name.clone() + "_ffi"); 123 | c.rust_mod_gen_name = format!("{}_gen", c.pkg_name.clone()); 124 | let file_name_base = &c.rust_mod_gen_name; 125 | c.rust_mod_gen_file = c.rust_mod_dir.join(format!("{file_name_base}.rs")); 126 | c.rust_mod_impl_file = c.rust_mod_dir.join("mod.rs"); 127 | c.go_main_dir = c.pkg_dir.join(CGOBIN); 128 | let go_file_suffix = match get_go_os_arch_from_env() { 129 | Ok((os, arch)) => { 130 | format!("_{}_{}", os.as_ref(), arch.as_ref()) 131 | } 132 | Err(err) => { 133 | println!("cargo:warning={}", err); 134 | String::new() 135 | } 136 | }; 137 | c.go_lib_file = c 138 | .pkg_dir 139 | .join(format!("{file_name_base}{go_file_suffix}.go")); 140 | c.go_main_file = c 141 | .go_main_dir 142 | .join(format!("clib_goffi_gen{go_file_suffix}.go")); 143 | c.go_main_impl_file = c.go_main_dir.join("clib_goffi_impl.go"); 144 | c.set_rust_clib_paths(); 145 | c.set_go_clib_paths(); 146 | c.check_go_mod_path(); 147 | c.set_fingerprint(); 148 | c.clean_idl(); 149 | let _ = c 150 | .init_files() 151 | .inspect_err(|e| exit_with_warning(-2, format!("failed init files to {e:?}"))); 152 | c.git_add(); 153 | c 154 | } 155 | 156 | fn new_idl_type(idl_file: &PathBuf) -> IdlType { 157 | match idl_file.extension().unwrap().to_str().unwrap() { 158 | "thrift" => match GEN_MODE { 159 | GenMode::Codec => IdlType::Thrift, 160 | GenMode::NoCodec => IdlType::ThriftNoCodec, 161 | }, 162 | "proto" => match GEN_MODE { 163 | GenMode::Codec => IdlType::Proto, 164 | GenMode::NoCodec => IdlType::ProtoNoCodec, 165 | }, 166 | x => { 167 | println!("cargo:warning=unsupported idl file extension: {x}"); 168 | std::process::exit(404); 169 | } 170 | } 171 | } 172 | 173 | fn new_target_out_dir() -> PathBuf { 174 | let target_dir = env::var("CARGO_TARGET_DIR").map_or_else( 175 | |_| { 176 | PathBuf::from(env::var("CARGO_WORKSPACE_DIR").unwrap_or_else(|_| { 177 | let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap_or_default()); 178 | let mdir = env::var("CARGO_MANIFEST_DIR").unwrap_or_default(); 179 | if out_dir.starts_with(&mdir) { 180 | mdir 181 | } else { 182 | let mut p = PathBuf::new(); 183 | let mut coms = Vec::new(); 184 | let mut start = false; 185 | for x in out_dir.components().rev() { 186 | if !start && x.as_os_str() == "target" { 187 | start = true; 188 | continue; 189 | } 190 | if start { 191 | coms.insert(0, x); 192 | } 193 | } 194 | for x in coms { 195 | p = p.join(x); 196 | } 197 | p.to_str().unwrap().to_string() 198 | } 199 | })) 200 | .join("target") 201 | }, 202 | PathBuf::from, 203 | ); 204 | let full_target_dir = target_dir.join(env::var("TARGET").unwrap()); 205 | if full_target_dir.is_dir() 206 | && PathBuf::from(env::var("OUT_DIR").unwrap()) 207 | .canonicalize() 208 | .unwrap() 209 | .starts_with(full_target_dir.canonicalize().unwrap()) 210 | { 211 | full_target_dir 212 | } else { 213 | target_dir 214 | } 215 | .join(BUILD_MODE) 216 | .canonicalize() 217 | .unwrap() 218 | } 219 | 220 | fn new_pkg_dir(target_crate_dir: &Option) -> PathBuf { 221 | if let Some(target_crate_dir) = target_crate_dir { 222 | target_crate_dir.clone().canonicalize().unwrap() 223 | } else { 224 | PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()) 225 | .canonicalize() 226 | .unwrap() 227 | } 228 | } 229 | 230 | fn new_pkg_name(pkg_dir: &PathBuf) -> String { 231 | pkg_dir 232 | .file_name() 233 | .unwrap() 234 | .to_str() 235 | .unwrap() 236 | .replace(".", "_") 237 | .replace("-", "_") 238 | .trim_start_matches("_") 239 | .to_string() 240 | .trim_end_matches("_") 241 | .to_string() 242 | } 243 | 244 | fn set_rust_clib_paths(&mut self) { 245 | self.rust_clib_file = self 246 | .clib_gen_dir 247 | .join(format!("lib{}.a", self.rust_clib_name_base)); 248 | self.rust_clib_header = self 249 | .clib_gen_dir 250 | .join(format!("{}.h", self.rust_clib_name_base)); 251 | } 252 | 253 | fn set_go_clib_paths(&mut self) { 254 | self.go_clib_file = self.clib_gen_dir.join(format!( 255 | "lib{}{}", 256 | self.go_clib_name_base, 257 | if self.config.use_goffi_cdylib { 258 | ".so" 259 | } else { 260 | ".a" 261 | } 262 | )); 263 | self.go_clib_header = self 264 | .clib_gen_dir 265 | .join(format!("{}.h", self.go_clib_name_base)); 266 | } 267 | 268 | fn git_add(&self) { 269 | if !self.config.add_clib_to_git { 270 | return; 271 | } 272 | deal_output( 273 | Command::new("git") 274 | .arg("add") 275 | .arg("-f") 276 | .args([ 277 | self.go_clib_header.display().to_string(), 278 | self.go_clib_file.display().to_string(), 279 | self.rust_clib_header.display().to_string(), 280 | self.rust_clib_file.display().to_string(), 281 | self.fingerprint_path.display().to_string(), 282 | ]) 283 | .output(), 284 | ); 285 | } 286 | 287 | fn set_fingerprint(&mut self) { 288 | self.fingerprint = walkdir::WalkDir::new(&self.pkg_dir) 289 | .into_iter() 290 | .filter_map(|entry| entry.ok()) 291 | .filter(|entry| { 292 | if entry 293 | .path() 294 | .extension() 295 | .map(|ext| ext == "go" || ext == "rs" || ext == "toml" || ext == "proto") 296 | .unwrap_or_default() 297 | { 298 | if let Ok(metadata) = entry.metadata() { 299 | return metadata.is_file(); 300 | } 301 | }; 302 | return false; 303 | }) 304 | .fold(String::new(), |acc, m| { 305 | let digest = md5::compute(fs::read(m.path()).unwrap()); 306 | format!("{acc}|{digest:x}") 307 | }); 308 | } 309 | pub(crate) fn update_fingerprint(&self) -> bool { 310 | if fs::read_to_string(&self.fingerprint_path).unwrap_or_default() != self.fingerprint { 311 | fs::write(&self.fingerprint_path, self.fingerprint.as_str()).unwrap(); 312 | return true; 313 | } 314 | return false; 315 | } 316 | 317 | fn clean_idl(&mut self) { 318 | let mut ret = match self.idl_type { 319 | IdlType::Proto | IdlType::ProtoNoCodec => { 320 | let mut parser = ProtobufParser::default(); 321 | Parser::include_dirs(&mut parser, vec![self.idl_include_dir.clone()]); 322 | Parser::input(&mut parser, &self.idl_file); 323 | let (descs, ret) = parser.parse_and_typecheck(); 324 | for desc in descs { 325 | if desc.package.is_some() { 326 | exit_with_warning(-1, "IDL-Check: The 'package' should not be configured"); 327 | } 328 | if let Some(opt) = desc.options.as_ref() { 329 | if opt.go_package.is_some() { 330 | exit_with_warning( 331 | -1, 332 | "IDL-Check: The 'option go_package' should not be configured", 333 | ); 334 | } 335 | } 336 | } 337 | ret 338 | } 339 | IdlType::Thrift | IdlType::ThriftNoCodec => { 340 | let mut parser = ThriftParser::default(); 341 | Parser::include_dirs(&mut parser, vec![self.idl_include_dir.clone()]); 342 | Parser::input(&mut parser, &self.idl_file); 343 | let ret = parser.parse(); 344 | ret 345 | } 346 | }; 347 | 348 | let file = ret.files.pop().unwrap(); 349 | if !file.uses.is_empty() { 350 | match self.idl_type { 351 | IdlType::Proto | IdlType::ProtoNoCodec => { 352 | exit_with_warning(-1, "IDL-Check: Does not support Protobuf 'import'.") 353 | } 354 | IdlType::Thrift | IdlType::ThriftNoCodec => { 355 | exit_with_warning(-1, "IDL-Check: Does not support Thrift 'include'.") 356 | } 357 | } 358 | } 359 | 360 | for item in &file.items { 361 | match &item.kind { 362 | ItemKind::Message(_) => {} 363 | ItemKind::Service(service_item) => { 364 | match service_item.name.to_lowercase().as_str() { 365 | "goffi" => self.has_goffi = true, 366 | "rustffi" => self.has_rustffi = true, 367 | _ => exit_with_warning( 368 | -1, 369 | "IDL-Check: Protobuf Service name can only be: 'GoFFI', 'RustFFI'.", 370 | ), 371 | } 372 | } 373 | _ => match self.idl_type { 374 | IdlType::Proto | IdlType::ProtoNoCodec => exit_with_warning( 375 | -1, 376 | format!( 377 | "IDL-Check: Protobuf Item '{}' not supported.", 378 | format!("{:?}", item) 379 | .trim_start_matches("Item { kind: ") 380 | .split_once("(") 381 | .unwrap() 382 | .0 383 | .to_lowercase() 384 | ), 385 | ), 386 | IdlType::Thrift | IdlType::ThriftNoCodec => exit_with_warning( 387 | -1, 388 | format!( 389 | "Thrift Item '{}' not supported.", 390 | format!("{:?}", item) 391 | .split_once("(") 392 | .unwrap() 393 | .0 394 | .to_lowercase() 395 | ), 396 | ), 397 | }, 398 | } 399 | } 400 | self.tidy_idl() 401 | } 402 | 403 | fn tidy_idl(&mut self) { 404 | let go_mod_name = &self.gomod_name; 405 | match self.idl_type { 406 | IdlType::Proto | IdlType::ProtoNoCodec => { 407 | self.idl_file = self.target_out_dir.join(go_mod_name.clone() + ".proto"); 408 | fs::write( 409 | &self.idl_file, 410 | fs::read_to_string(&self.config.idl_file).unwrap() 411 | + &format!( 412 | "\noption go_package=\"./;{go_mod_name}\";\npackage {go_mod_name};\n" 413 | ), 414 | ) 415 | .unwrap(); 416 | } 417 | IdlType::Thrift | IdlType::ThriftNoCodec => { 418 | self.idl_file = self.target_out_dir.join(go_mod_name.clone() + ".thrift"); 419 | fs::copy(&self.config.idl_file, &self.idl_file).unwrap(); 420 | } 421 | }; 422 | self.idl_include_dir = self.idl_file.parent().unwrap().to_path_buf(); 423 | } 424 | 425 | // rustc-link-lib=[KIND=]NAME indicates that the specified value is a library name and should be passed to the compiler as a -l flag. The optional KIND can be one of static, dylib (the default), or framework, see rustc --help for more details. 426 | // 427 | // rustc-link-search=[KIND=]PATH indicates the specified value is a library search path and should be passed to the compiler as a -L flag. The optional KIND can be one of dependency, crate, native, framework or all (the default), see rustc --help for more details. 428 | // 429 | // rustc-flags=FLAGS is a set of flags passed to the compiler, only -l and -L flags are supported. 430 | pub(crate) fn rustc_link(&self) { 431 | println!( 432 | "cargo:rustc-link-search=native={}", 433 | self.clib_gen_dir.to_str().unwrap() 434 | ); 435 | println!( 436 | "cargo:rustc-link-search=dependency={}", 437 | self.clib_gen_dir.to_str().unwrap() 438 | ); 439 | println!( 440 | "cargo:rustc-link-lib={}={}", 441 | self.rustc_link_kind_goffi, self.go_clib_name_base 442 | ); 443 | } 444 | 445 | pub(crate) fn rerun_if_changed(&self) { 446 | println!("cargo:rerun-if-changed={}", self.pkg_dir.to_str().unwrap()); 447 | println!( 448 | "cargo:rerun-if-changed={}", 449 | self.target_out_dir.to_str().unwrap() 450 | ); 451 | } 452 | 453 | fn check_go_mod_path(&self) { 454 | let f = &self.gomod_file; 455 | if f.exists() { 456 | if !f.is_file() { 457 | exit_with_warning( 458 | 253, 459 | format!("go mod file {} does not exist", f.to_str().unwrap()), 460 | ); 461 | } else { 462 | let p = &self.gomod_path; 463 | let s = fs::read_to_string(f).unwrap(); 464 | if !s.contains(&format!("module {p}\n")) 465 | && !s.contains(&format!("module {p}\t")) 466 | && !s.contains(&format!("module {p}\r")) 467 | && !s.contains(&format!("module {p} ")) 468 | { 469 | exit_with_warning( 470 | 253, 471 | format!("go mod path should be {p}, file={}", f.to_str().unwrap()), 472 | ); 473 | } 474 | } 475 | } 476 | } 477 | 478 | fn init_files(&self) -> anyhow::Result<()> { 479 | fs::create_dir_all(&self.go_main_dir)?; 480 | fs::create_dir_all(&self.rust_mod_dir)?; 481 | fs::create_dir_all(&self.clib_gen_dir)?; 482 | for f in [ 483 | &self.rust_clib_file, 484 | &self.rust_clib_header, 485 | &self.go_clib_file, 486 | &self.go_clib_header, 487 | &self.fingerprint_path, 488 | ] { 489 | OpenOptions::new() 490 | .write(true) 491 | .create(true) 492 | .open(&self.clib_gen_dir.join(f))?; 493 | } 494 | Ok(()) 495 | } 496 | 497 | pub(crate) fn go_cmd_path(&self, cmd: &'static str) -> String { 498 | if let Some(go_root_path) = &self.config.go_root_path { 499 | go_root_path 500 | .join("bin") 501 | .join(cmd) 502 | .to_str() 503 | .unwrap() 504 | .to_string() 505 | } else if let Ok(go_root_path) = env::var("GOROOT") { 506 | PathBuf::from_str(&go_root_path) 507 | .unwrap() 508 | .join("bin") 509 | .join(cmd) 510 | .to_str() 511 | .unwrap() 512 | .to_string() 513 | } else { 514 | cmd.to_string() 515 | } 516 | } 517 | } 518 | -------------------------------------------------------------------------------- /rust/fcplug-build/src/gen_go_os_arch_rs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | json=$(go tool dist list -json); 4 | 5 | # 提取GOOS和GOARCH字段的值 6 | os_values=$(echo "$json" | grep -oE '"GOOS": "[^"]+"' | cut -d'"' -f4) 7 | arch_values+="$(echo "$json" | grep -oE '"GOARCH": "[^"]+"' | cut -d'"' -f4)" 8 | 9 | # 去重并生成rust的enum 10 | unique_os_values=$(echo "$os_values" | tr ' ' '\n' | sort -u) 11 | enum_os_values=$(echo "$unique_os_values" | sed 's/^[0-9].*/#\[strum(serialize = "&")\]_&/; s/^/ /; s/$/,/') 12 | unique_arch_values=$(echo "$arch_values" | tr ' ' '\n' | sort -u) 13 | enum_arch_values=$(echo "$unique_arch_values" | sed 's/^[0-9].*/#\[strum(serialize = "&")\]_&/; s/^/ /; s/$/,/') 14 | 15 | 16 | rs_file="go_os_arch_gen.rs" 17 | rm -f "$rs_file" 18 | 19 | echo "#![allow(non_camel_case_types)]" > $rs_file 20 | 21 | echo "" >> $rs_file 22 | 23 | # 输出rust的enum定义 24 | echo "#[derive(strum::AsRefStr, strum::EnumString)]" >> $rs_file 25 | echo "pub enum GoOS {" >> $rs_file 26 | echo "$enum_os_values" >> $rs_file 27 | echo "}" >> $rs_file 28 | 29 | echo "" >> $rs_file 30 | 31 | echo "#[derive(strum::AsRefStr, strum::EnumString)]" >> $rs_file 32 | echo "pub enum GoArch {" >> $rs_file 33 | echo "$enum_arch_values" >> $rs_file 34 | echo "}" >> $rs_file 35 | -------------------------------------------------------------------------------- /rust/fcplug-build/src/gen_rust_os_arch_rs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | output=$(rustc --print target-list) 4 | output=$(echo "$output" | sed 's/\./_/g') 5 | 6 | RustOS=() 7 | RustArch=() 8 | 9 | while IFS= read -r line; do 10 | IFS='-' read -ra a <<< "$line" 11 | if [[ ${#a[@]} -ge 3 ]]; then 12 | RustOS+=("${a[2]}") 13 | fi 14 | if [[ ${#a[@]} -ge 1 ]]; then 15 | RustArch+=("${a[0]}") # 将第一个元素添加到数组中 16 | fi 17 | done <<< "$output" 18 | 19 | unique_RustOS=$(printf "%s\n" "${RustOS[@]}" | sort -u) 20 | unique_RustArch=$(printf "%s\n" "${RustArch[@]}" | sort -u) 21 | 22 | rs_file="rust_os_arch_gen.rs" 23 | rm -f "$rs_file" 24 | 25 | echo "#![allow(non_camel_case_types)]" > $rs_file 26 | 27 | echo "" >> $rs_file 28 | 29 | echo "#[derive(strum::AsRefStr, strum::EnumString)]" >> $rs_file 30 | echo "pub enum RustOS {" >> $rs_file 31 | for os in "${unique_RustOS[@]}"; do 32 | echo "$os" | sed 's/^[0-9].*/#\[strum(serialize = "&")\]_&/; s/^/ /; s/$/,/' >> $rs_file 33 | done 34 | echo "}" >> $rs_file 35 | 36 | echo "" >> $rs_file 37 | 38 | echo "#[derive(strum::AsRefStr, strum::EnumString)]" >> $rs_file 39 | echo "pub enum RustArch {" >> $rs_file 40 | for arch in "${unique_RustArch[@]}"; do 41 | echo "$arch" | sed 's/^[0-9].*/#\[strum(serialize = "&")\]_&/; s/^/ /; s/$/,/' >> $rs_file 42 | done 43 | echo "}" >> $rs_file 44 | -------------------------------------------------------------------------------- /rust/fcplug-build/src/go_os_arch_gen.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_camel_case_types)] 2 | 3 | #[derive(strum::AsRefStr, strum::EnumString)] 4 | pub enum GoOS { 5 | aix, 6 | android, 7 | darwin, 8 | dragonfly, 9 | freebsd, 10 | illumos, 11 | ios, 12 | js, 13 | linux, 14 | netbsd, 15 | openbsd, 16 | plan9, 17 | solaris, 18 | windows, 19 | } 20 | 21 | #[derive(strum::AsRefStr, strum::EnumString)] 22 | pub enum GoArch { 23 | #[strum(serialize = "386")]_386, 24 | amd64, 25 | arm, 26 | arm64, 27 | loong64, 28 | mips, 29 | mips64, 30 | mips64le, 31 | mipsle, 32 | ppc64, 33 | ppc64le, 34 | riscv64, 35 | s390x, 36 | wasm, 37 | } 38 | -------------------------------------------------------------------------------- /rust/fcplug-build/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(try_trait_v2)] 2 | #![allow(dead_code)] 3 | #![feature(trait_alias)] 4 | 5 | use std::fmt::Debug; 6 | use std::io; 7 | use std::process::Output as CmdOutput; 8 | 9 | use backtrace::Backtrace; 10 | 11 | pub use config::{Config, GoObjectPath, UnitLikeStructPath}; 12 | 13 | use crate::generator::Generator; 14 | 15 | mod config; 16 | mod generator; 17 | mod go_os_arch_gen; 18 | mod os_arch; 19 | mod rust_os_arch_gen; 20 | #[cfg(not(feature = "no-codec"))] 21 | mod with_codec; 22 | #[cfg(feature = "no-codec")] 23 | mod without_codec; 24 | 25 | #[allow(dead_code)] 26 | enum GenMode { 27 | Codec, 28 | NoCodec, 29 | } 30 | 31 | #[cfg(feature = "no-codec")] 32 | const GEN_MODE: GenMode = GenMode::NoCodec; 33 | #[cfg(not(feature = "no-codec"))] 34 | const GEN_MODE: GenMode = GenMode::Codec; 35 | 36 | #[cfg(not(debug_assertions))] 37 | const BUILD_MODE: &'static str = "release"; 38 | #[cfg(debug_assertions)] 39 | const BUILD_MODE: &'static str = "debug"; 40 | 41 | pub fn generate_code(config: Config) { 42 | Generator::generate(config) 43 | } 44 | 45 | const CODE_UNKNOWN: i32 = -1; 46 | const CODE_CMD_UNKNOWN: i32 = -2; 47 | const CODE_IO: i32 = -3; 48 | const CODE_GIT: i32 = -4; 49 | 50 | fn exit_with_warning(code: i32, message: impl AsRef) { 51 | let mut frames = vec![]; 52 | for bf in Backtrace::new().frames()[4..].as_ref() { 53 | if bf 54 | .symbols() 55 | .get(0) 56 | .unwrap() 57 | .name() 58 | .unwrap() 59 | .to_string() 60 | .starts_with("build_script_build::main::") 61 | { 62 | break; 63 | } 64 | frames.push(bf.clone()); 65 | } 66 | println!( 67 | "cargo:warning={}\nbacktrace:\n{:?}", 68 | message.as_ref(), 69 | Backtrace::from(frames) 70 | ); 71 | std::process::exit(code); 72 | } 73 | 74 | fn deal_result(code: i32, result: Result) -> T { 75 | match result { 76 | Ok(t) => t, 77 | Err(e) => { 78 | exit_with_warning(code, format!("{e:?}")); 79 | unreachable!() 80 | } 81 | } 82 | } 83 | 84 | fn deal_output(output: io::Result) { 85 | match output { 86 | Ok(output) => { 87 | if !output.status.success() { 88 | exit_with_warning( 89 | output.status.code().unwrap_or(CODE_CMD_UNKNOWN), 90 | format!("{output:?}"), 91 | ); 92 | } else { 93 | if output.stderr.is_empty() { 94 | println!("{output:?}"); 95 | } else { 96 | println!( 97 | "cargo:warning={:?}", 98 | String::from_utf8(output.stderr.clone()).unwrap_or(format!("{output:?}")) 99 | ); 100 | } 101 | } 102 | } 103 | Err(e) => { 104 | exit_with_warning(CODE_UNKNOWN, format!("{e:?}")); 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /rust/fcplug-build/src/os_arch.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::str::FromStr; 3 | 4 | pub(crate) use crate::go_os_arch_gen::{GoArch, GoOS}; 5 | pub(crate) use crate::rust_os_arch_gen::{RustArch, RustOS}; 6 | 7 | pub(crate) fn parse_target_triple(target_triple: &str) -> Result<(RustOS, RustArch), String> { 8 | let a = target_triple 9 | .replace(".", "_") 10 | .split("-") 11 | .map(ToOwned::to_owned) 12 | .collect::>(); 13 | Ok(( 14 | RustOS::from_str(a.get(2).unwrap_or(&"".to_string())).map_err(|_| { format!("unknown os {:?}", a.get(2)) })?, 15 | RustArch::from_str(a.get(0).unwrap_or(&"".to_string())).map_err(|_| { format!("unknown arch {:?}", a.get(0)) })?, 16 | )) 17 | } 18 | 19 | pub(crate) fn parse_target_triple_from_env() -> Result<(RustOS, RustArch), String> { 20 | parse_target_triple(env::var("TARGET").unwrap().as_str()) 21 | } 22 | 23 | pub(crate) fn get_go_os_arch_from_env() -> Result<(GoOS, GoArch), String> { 24 | let (os, arch) = parse_target_triple_from_env()?; 25 | Ok((GoOS::try_from(os)?, GoArch::try_from(arch)?)) 26 | } 27 | 28 | impl TryFrom for GoArch { 29 | type Error = String; 30 | 31 | fn try_from(value: RustArch) -> Result { 32 | match value { 33 | RustArch::aarch64 => Ok(GoArch::arm64), 34 | RustArch::aarch64_be => Ok(GoArch::arm64), 35 | RustArch::arm => Ok(GoArch::arm), 36 | RustArch::arm64_32 => Ok(GoArch::arm64), 37 | RustArch::armeb => Ok(GoArch::arm), 38 | RustArch::armebv7r => Ok(GoArch::arm), 39 | RustArch::armv4t => Ok(GoArch::arm), 40 | RustArch::armv5te => Ok(GoArch::arm), 41 | RustArch::armv6 => Ok(GoArch::arm), 42 | RustArch::armv6k => Ok(GoArch::arm), 43 | RustArch::armv7 => Ok(GoArch::arm), 44 | RustArch::armv7a => Ok(GoArch::arm), 45 | RustArch::armv7k => Ok(GoArch::arm), 46 | RustArch::armv7r => Ok(GoArch::arm), 47 | RustArch::armv7s => Ok(GoArch::arm), 48 | RustArch::asmjs => Ok(GoArch::wasm), 49 | RustArch::avr => Err(format!("{} not supported by golang", value.as_ref())), 50 | RustArch::bpfeb => Err(format!("{} not supported by golang", value.as_ref())), 51 | RustArch::bpfel => Err(format!("{} not supported by golang", value.as_ref())), 52 | RustArch::hexagon => Err(format!("{} not supported by golang", value.as_ref())), 53 | RustArch::i386 => Ok(GoArch::_386), 54 | RustArch::i586 => Err(format!("{} not supported by golang", value.as_ref())), 55 | RustArch::i686 => Err(format!("{} not supported by golang", value.as_ref())), 56 | RustArch::loongarch64 => Ok(GoArch::loong64), 57 | RustArch::m68k => Err(format!("{} not supported by golang", value.as_ref())), 58 | RustArch::mips => Ok(GoArch::mips), 59 | RustArch::mips64 => Ok(GoArch::mips64), 60 | RustArch::mips64el => Ok(GoArch::mips64le), 61 | RustArch::mipsel => Ok(GoArch::mipsle), 62 | RustArch::mipsisa32r6 => Err(format!("{} not supported by golang", value.as_ref())), 63 | RustArch::mipsisa32r6el => Err(format!("{} not supported by golang", value.as_ref())), 64 | RustArch::mipsisa64r6 => Err(format!("{} not supported by golang", value.as_ref())), 65 | RustArch::mipsisa64r6el => Err(format!("{} not supported by golang", value.as_ref())), 66 | RustArch::msp430 => Err(format!("{} not supported by golang", value.as_ref())), 67 | RustArch::nvptx64 => Err(format!("{} not supported by golang", value.as_ref())), 68 | RustArch::powerpc => Err(format!("{} not supported by golang", value.as_ref())), 69 | RustArch::powerpc64 => Err(format!("{} not supported by golang", value.as_ref())), 70 | RustArch::powerpc64le => Err(format!("{} not supported by golang", value.as_ref())), 71 | RustArch::riscv32gc => Err(format!("{} not supported by golang", value.as_ref())), 72 | RustArch::riscv32i => Err(format!("{} not supported by golang", value.as_ref())), 73 | RustArch::riscv32im => Err(format!("{} not supported by golang", value.as_ref())), 74 | RustArch::riscv32imac => Err(format!("{} not supported by golang", value.as_ref())), 75 | RustArch::riscv32imc => Err(format!("{} not supported by golang", value.as_ref())), 76 | RustArch::riscv64gc => Ok(GoArch::riscv64), 77 | RustArch::riscv64imac => Ok(GoArch::riscv64), 78 | RustArch::s390x => Ok(GoArch::s390x), 79 | RustArch::sparc => Err(format!("{} not supported by golang", value.as_ref())), 80 | RustArch::sparc64 => Err(format!("{} not supported by golang", value.as_ref())), 81 | RustArch::sparcv9 => Err(format!("{} not supported by golang", value.as_ref())), 82 | RustArch::thumbv4t => Err(format!("{} not supported by golang", value.as_ref())), 83 | RustArch::thumbv5te => Err(format!("{} not supported by golang", value.as_ref())), 84 | RustArch::thumbv6m => Err(format!("{} not supported by golang", value.as_ref())), 85 | RustArch::thumbv7a => Err(format!("{} not supported by golang", value.as_ref())), 86 | RustArch::thumbv7em => Err(format!("{} not supported by golang", value.as_ref())), 87 | RustArch::thumbv7m => Err(format!("{} not supported by golang", value.as_ref())), 88 | RustArch::thumbv7neon => Err(format!("{} not supported by golang", value.as_ref())), 89 | RustArch::thumbv8m_base => Err(format!("{} not supported by golang", value.as_ref())), 90 | RustArch::thumbv8m_main => Err(format!("{} not supported by golang", value.as_ref())), 91 | RustArch::wasm32 => Ok(GoArch::wasm), 92 | RustArch::wasm64 => Ok(GoArch::wasm), 93 | RustArch::x86_64 => Ok(GoArch::amd64), 94 | RustArch::x86_64h => Ok(GoArch::amd64), 95 | } 96 | } 97 | } 98 | 99 | impl TryFrom for GoOS { 100 | type Error = String; 101 | 102 | fn try_from(value: RustOS) -> Result { 103 | match value { 104 | RustOS::_3ds => Err(format!("{} not supported by golang", value.as_ref())), 105 | RustOS::aix => Ok(GoOS::aix), 106 | RustOS::android => Ok(GoOS::android), 107 | RustOS::androideabi => Ok(GoOS::android), 108 | RustOS::cuda => Err(format!("{} not supported by golang", value.as_ref())), 109 | RustOS::darwin => Ok(GoOS::darwin), 110 | RustOS::dragonfly => Ok(GoOS::dragonfly), 111 | RustOS::eabi => Err(format!("{} not supported by golang", value.as_ref())), 112 | RustOS::eabihf => Err(format!("{} not supported by golang", value.as_ref())), 113 | RustOS::elf => Err(format!("{} not supported by golang", value.as_ref())), 114 | RustOS::emscripten => Err(format!("{} not supported by golang", value.as_ref())), 115 | RustOS::espidf => Err(format!("{} not supported by golang", value.as_ref())), 116 | RustOS::freebsd => Ok(GoOS::freebsd), 117 | RustOS::fuchsia => Err(format!("{} not supported by golang", value.as_ref())), 118 | RustOS::gnu => Err(format!("{} not supported by golang", value.as_ref())), 119 | RustOS::haiku => Err(format!("{} not supported by golang", value.as_ref())), 120 | RustOS::hermit => Err(format!("{} not supported by golang", value.as_ref())), 121 | RustOS::illumos => Ok(GoOS::illumos), 122 | RustOS::ios => Ok(GoOS::ios), 123 | RustOS::l4re => Err(format!("{} not supported by golang", value.as_ref())), 124 | RustOS::linux => Ok(GoOS::linux), 125 | RustOS::netbsd => Ok(GoOS::netbsd), 126 | RustOS::none => Err(format!("{} not supported by golang", value.as_ref())), 127 | RustOS::nto => Err(format!("{} not supported by golang", value.as_ref())), 128 | RustOS::openbsd => Ok(GoOS::openbsd), 129 | RustOS::psp => Err(format!("{} not supported by golang", value.as_ref())), 130 | RustOS::psx => Err(format!("{} not supported by golang", value.as_ref())), 131 | RustOS::redox => Err(format!("{} not supported by golang", value.as_ref())), 132 | RustOS::solaris => Ok(GoOS::solaris), 133 | RustOS::solid_asp3 => Err(format!("{} not supported by golang", value.as_ref())), 134 | RustOS::switch => Err(format!("{} not supported by golang", value.as_ref())), 135 | RustOS::tvos => Err(format!("{} not supported by golang", value.as_ref())), 136 | RustOS::uefi => Err(format!("{} not supported by golang", value.as_ref())), 137 | RustOS::unknown => Ok(GoOS::js), 138 | RustOS::vita => Err(format!("{} not supported by golang", value.as_ref())), 139 | RustOS::vxworks => Err(format!("{} not supported by golang", value.as_ref())), 140 | RustOS::watchos => Err(format!("{} not supported by golang", value.as_ref())), 141 | RustOS::windows => Ok(GoOS::windows), 142 | RustOS::xous => Err(format!("{} not supported by golang", value.as_ref())), 143 | } 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /rust/fcplug-build/src/rust_os_arch_gen.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_camel_case_types)] 2 | 3 | #[derive(strum::AsRefStr, strum::EnumString)] 4 | pub enum RustOS { 5 | #[strum(serialize = "3ds")]_3ds, 6 | aix, 7 | android, 8 | androideabi, 9 | cuda, 10 | darwin, 11 | dragonfly, 12 | eabi, 13 | eabihf, 14 | elf, 15 | emscripten, 16 | espidf, 17 | freebsd, 18 | fuchsia, 19 | gnu, 20 | haiku, 21 | hermit, 22 | illumos, 23 | ios, 24 | l4re, 25 | linux, 26 | netbsd, 27 | none, 28 | nto, 29 | openbsd, 30 | psp, 31 | psx, 32 | redox, 33 | solaris, 34 | solid_asp3, 35 | switch, 36 | tvos, 37 | uefi, 38 | unknown, 39 | vita, 40 | vxworks, 41 | watchos, 42 | windows, 43 | xous, 44 | } 45 | 46 | #[derive(strum::AsRefStr, strum::EnumString)] 47 | pub enum RustArch { 48 | aarch64, 49 | aarch64_be, 50 | arm, 51 | arm64_32, 52 | armeb, 53 | armebv7r, 54 | armv4t, 55 | armv5te, 56 | armv6, 57 | armv6k, 58 | armv7, 59 | armv7a, 60 | armv7k, 61 | armv7r, 62 | armv7s, 63 | asmjs, 64 | avr, 65 | bpfeb, 66 | bpfel, 67 | hexagon, 68 | i386, 69 | i586, 70 | i686, 71 | loongarch64, 72 | m68k, 73 | mips, 74 | mips64, 75 | mips64el, 76 | mipsel, 77 | mipsisa32r6, 78 | mipsisa32r6el, 79 | mipsisa64r6, 80 | mipsisa64r6el, 81 | msp430, 82 | nvptx64, 83 | powerpc, 84 | powerpc64, 85 | powerpc64le, 86 | riscv32gc, 87 | riscv32i, 88 | riscv32im, 89 | riscv32imac, 90 | riscv32imc, 91 | riscv64gc, 92 | riscv64imac, 93 | s390x, 94 | sparc, 95 | sparc64, 96 | sparcv9, 97 | thumbv4t, 98 | thumbv5te, 99 | thumbv6m, 100 | thumbv7a, 101 | thumbv7em, 102 | thumbv7m, 103 | thumbv7neon, 104 | thumbv8m_base, 105 | thumbv8m_main, 106 | wasm32, 107 | wasm64, 108 | x86_64, 109 | x86_64h, 110 | } 111 | -------------------------------------------------------------------------------- /rust/fcplug-build/src/with_codec/gen_go_codec.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use pilota_build::rir::Method; 4 | use pilota_build::ty::TyKind; 5 | use pilota_build::{rir::Service, DefId}; 6 | 7 | use crate::generator::{GoCodegenBackend, GoGeneratorBackend}; 8 | 9 | impl GoCodegenBackend for GoGeneratorBackend { 10 | // {lib}.go 11 | fn codegen_rustffi_iface_method( 12 | &self, 13 | service_def_id: DefId, 14 | method: &Arc, 15 | ) -> Option<(String, String)> { 16 | let iface_method_name = self.iface_method_name(method); 17 | let args_sign = method 18 | .args 19 | .iter() 20 | .map(|arg| { 21 | if arg.ty.is_scalar() { 22 | format!("{} {}", self.arg_name(arg), self.arg_type(arg, false)) 23 | } else { 24 | format!( 25 | "{} TBytes[*{}]", 26 | self.arg_name(arg), 27 | self.arg_type(arg, false) 28 | ) 29 | } 30 | }) 31 | .collect::>() 32 | .join(","); 33 | let ret_type = self.ret_type(method, false); 34 | let iface_method = format!("{iface_method_name}({args_sign}) RustFfiResult[{ret_type}]"); 35 | let ffi_func_name = self.ffi_func_name(service_def_id, method); 36 | let args_assign = method 37 | .args 38 | .iter() 39 | .map(|arg| { 40 | if arg.ty.is_scalar() { 41 | let name = self.arg_name(arg); 42 | if let TyKind::Bool = arg.ty.kind { 43 | format!("C._Bool({name})") 44 | } else { 45 | name 46 | } 47 | } else { 48 | format!("{}.asBuffer()", self.arg_name(arg)) 49 | } 50 | }) 51 | .collect::>() 52 | .join(","); 53 | Some(( 54 | iface_method, 55 | format!("return newRustFfiResult[{ret_type}](C.{ffi_func_name}({args_assign}))"), 56 | )) 57 | } 58 | // {lib}.go 59 | fn codegen_rustffi_service_impl(&self, _service_def_id: DefId, _s: &Service) -> String { 60 | r###" 61 | type ResultCode = int8 62 | 63 | const ( 64 | RcNoError ResultCode = 0 65 | RcDecode ResultCode = -1 66 | RcEncode ResultCode = -2 67 | RcUnknown ResultCode = -128 68 | ) 69 | 70 | // TBytes bytes with type marker 71 | type TBytes[T any] struct { 72 | bytes []byte 73 | _nil *T 74 | } 75 | 76 | // TBytesFromBytes new TBytes from bytes 77 | //go:inline 78 | func TBytesFromBytes[T any](bytes []byte) TBytes[T] { 79 | return TBytes[T]{bytes: bytes} 80 | } 81 | 82 | // TBytesFromString new TBytes from string 83 | //go:inline 84 | func TBytesFromString[T any](s string) TBytes[T] { 85 | return TBytes[T]{bytes: valconv.StringToReadonlyBytes[string](s)} 86 | } 87 | 88 | //go:inline 89 | func TBytesFromPbUnchecked[T proto.Message](obj T) TBytes[T] { 90 | tb, _ := TBytesFromPb[T](obj) 91 | return tb 92 | } 93 | 94 | //go:inline 95 | func TBytesFromPb[T proto.Message](obj T) (TBytes[T], error) { 96 | var tb TBytes[T] 97 | var err error 98 | tb.bytes, err = proto.Marshal(obj) 99 | if err != nil { 100 | return TBytes[T]{}, err 101 | } 102 | return tb, nil 103 | } 104 | 105 | //go:inline 106 | func TBytesFromJsonUnchecked[T proto.Message](obj T) TBytes[T] { 107 | tb, _ := TBytesFromJson[T](obj) 108 | return tb 109 | } 110 | 111 | //go:inline 112 | func TBytesFromJson[T any](obj T) (TBytes[T], error) { 113 | var tb TBytes[T] 114 | var err error 115 | tb.bytes, err = sonic.Marshal(obj) 116 | if err != nil { 117 | return TBytes[T]{}, err 118 | } 119 | return tb, nil 120 | } 121 | 122 | //go:inline 123 | func (b TBytes[T]) Len() int { 124 | return len(b.bytes) 125 | } 126 | 127 | // PbUnmarshal as protobuf to unmarshal 128 | // NOTE: maybe reference Rust memory buffer 129 | // 130 | //go:inline 131 | func (b TBytes[T]) PbUnmarshal() (*T, error) { 132 | var t T 133 | if b.Len() > 0 { 134 | err := proto.Unmarshal(b.bytes, any(&t).(proto.Message)) 135 | if err != nil { 136 | return nil, err 137 | } 138 | } 139 | return &t, nil 140 | } 141 | 142 | // PbUnmarshalUnchecked as protobuf to unmarshal 143 | // NOTE: maybe reference Rust memory buffer 144 | // 145 | //go:inline 146 | func (b TBytes[T]) PbUnmarshalUnchecked() (*T) { 147 | var t T 148 | if b.Len() > 0 { 149 | _= proto.Unmarshal(b.bytes, any(&t).(proto.Message)) 150 | } 151 | return &t 152 | } 153 | 154 | // JsonUnmarshal as json to unmarshal 155 | // NOTE: maybe reference Rust memory buffer 156 | // 157 | //go:inline 158 | func (b TBytes[T]) JsonUnmarshal() (*T, error) { 159 | var t T 160 | if b.Len() > 0 { 161 | err := sonic.Unmarshal(b.bytes, &t) 162 | if err != nil { 163 | return nil, err 164 | } 165 | } 166 | return &t, nil 167 | } 168 | 169 | // JsonUnmarshalUnchecked as json to unmarshal 170 | // NOTE: maybe reference Rust memory buffer 171 | // 172 | //go:inline 173 | func (b TBytes[T]) JsonUnmarshalUnchecked() *T { 174 | var t T 175 | if b.Len() > 0 { 176 | _ = sonic.Unmarshal(b.bytes, &t) 177 | } 178 | return &t 179 | } 180 | 181 | // Unmarshal unmarshal to object 182 | // NOTE: maybe reference Rust memory buffer 183 | // 184 | //go:inline 185 | func (b TBytes[T]) Unmarshal(unmarshal func([]byte, any) error) (*T, error) { 186 | var t T 187 | if b.Len() > 0 { 188 | err := unmarshal(b.bytes, &t) 189 | if err != nil { 190 | return nil, err 191 | } 192 | } 193 | return &t, nil 194 | } 195 | 196 | // UnmarshalUnchecked unmarshal to object 197 | // NOTE: maybe reference Rust memory buffer 198 | // 199 | //go:inline 200 | func (b TBytes[T]) UnmarshalUnchecked(unmarshal func([]byte, any) error) *T { 201 | var t T 202 | if b.Len() > 0 { 203 | _ = unmarshal(b.bytes, &t) 204 | } 205 | return &t 206 | } 207 | 208 | //go:inline 209 | func (b TBytes[T]) ForCBuffer() (unsafe.Pointer, int) { 210 | size := len(b.bytes) 211 | if size == 0 { 212 | return nil, 0 213 | } 214 | if cap(b.bytes) > size { 215 | b.bytes = b.bytes[0:size:size] 216 | } 217 | return unsafe.Pointer(&b.bytes[0]), size 218 | } 219 | 220 | //go:inline 221 | func (b TBytes[T]) asBuffer() C.struct_Buffer { 222 | p, size := b.ForCBuffer() 223 | if size == 0 { 224 | return C.struct_Buffer{} 225 | } 226 | return C.struct_Buffer{ 227 | ptr: (*C.uint8_t)(p), 228 | len: C.uintptr_t(size), 229 | cap: C.uintptr_t(size), 230 | } 231 | } 232 | 233 | // CBuffer Rust buffer for Go 234 | type CBuffer struct { 235 | buf C.struct_Buffer 236 | } 237 | 238 | // Free free rust memory buffer, must be called! 239 | // 240 | //go:inline 241 | func (b CBuffer) Free() { 242 | if b.buf.len > 0 { 243 | C.free_buffer(b.buf) 244 | } 245 | } 246 | 247 | //go:inline 248 | func (b CBuffer) Len() int { 249 | return int(b.buf.len) 250 | } 251 | 252 | //go:inline 253 | func (b CBuffer) AsBytes() []byte { 254 | if b.buf.len == 0 { 255 | return nil 256 | } 257 | return *(*[]byte)(unsafe.Pointer(&reflect.SliceHeader{ 258 | Data: uintptr(unsafe.Pointer(b.buf.ptr)), 259 | Len: int(b.buf.len), 260 | Cap: int(b.buf.cap), 261 | })) 262 | } 263 | 264 | //go:inline 265 | func (b CBuffer) AsString() string { 266 | if b.buf.len == 0 { 267 | return "" 268 | } 269 | return valconv.BytesToString[string](b.AsBytes()) 270 | } 271 | 272 | // RustFfiResult Rust FFI Result for Go 273 | // NOTE: must call Free method to free rust memory buffer! 274 | type RustFfiResult[T any] struct { 275 | CBuffer 276 | Code ResultCode 277 | _nil *T 278 | } 279 | 280 | //go:inline 281 | func newRustFfiResult[T any](ret C.struct_RustFfiResult) RustFfiResult[T] { 282 | return RustFfiResult[T]{ 283 | CBuffer: CBuffer{buf: ret.data}, 284 | Code: ResultCode(ret.code), 285 | _nil: nil, 286 | } 287 | } 288 | 289 | //go:inline 290 | func (r RustFfiResult[T]) String() string { 291 | return fmt.Sprintf("Code: %d, CBuffer: %s", r.Code, r.CBuffer.AsString()) 292 | } 293 | 294 | //go:inline 295 | func (r RustFfiResult[T]) IsOk() bool { 296 | return r.Code == RcNoError 297 | } 298 | 299 | // AsError as an error 300 | // NOTE: reference Rust memory buffer 301 | // 302 | //go:inline 303 | func (r RustFfiResult[T]) AsError() error { 304 | if r.Code != RcNoError { 305 | return errors.New(r.AsString()) 306 | } 307 | return nil 308 | } 309 | 310 | // PbUnmarshal as protobuf to unmarshal 311 | // NOTE: maybe reference Rust memory buffer 312 | // 313 | //go:inline 314 | func (r RustFfiResult[T]) PbUnmarshal() (*T, error) { 315 | if err := r.AsError(); err != nil { 316 | return nil, err 317 | } 318 | var t T 319 | if r.Len() > 0 { 320 | err := proto.Unmarshal(r.AsBytes(), any(&t).(proto.Message)) 321 | if err != nil { 322 | return nil, err 323 | } 324 | } 325 | return &t, nil 326 | } 327 | 328 | // PbUnmarshalUnchecked as protobuf to unmarshal 329 | // NOTE: maybe reference Rust memory buffer 330 | // 331 | //go:inline 332 | func (r RustFfiResult[T]) PbUnmarshalUnchecked() *T { 333 | if err := r.AsError(); err != nil { 334 | return nil 335 | } 336 | var t T 337 | if r.Len() > 0 { 338 | _ = proto.Unmarshal(r.AsBytes(), any(&t).(proto.Message)) 339 | } 340 | return &t 341 | } 342 | 343 | // JsonUnmarshal as json to unmarshal 344 | // NOTE: maybe reference Rust memory buffer 345 | // 346 | //go:inline 347 | func (r RustFfiResult[T]) JsonUnmarshal() (*T, error) { 348 | if err := r.AsError(); err != nil { 349 | return nil, err 350 | } 351 | var t T 352 | if r.Len() > 0 { 353 | err := sonic.Unmarshal(r.AsBytes(), &t) 354 | if err != nil { 355 | return nil, err 356 | } 357 | } 358 | return &t, nil 359 | } 360 | 361 | // JsonUnmarshalUnchecked as json to unmarshal 362 | // NOTE: maybe reference Rust memory buffer 363 | // 364 | //go:inline 365 | func (r RustFfiResult[T]) JsonUnmarshalUnchecked() *T { 366 | if err := r.AsError(); err != nil { 367 | return nil 368 | } 369 | var t T 370 | if r.Len() > 0 { 371 | _ = sonic.Unmarshal(r.AsBytes(), &t) 372 | } 373 | return &t 374 | } 375 | 376 | // Unmarshal unmarshal to object 377 | // NOTE: maybe reference Rust memory buffer 378 | // 379 | //go:inline 380 | func (r RustFfiResult[T]) Unmarshal(unmarshal func([]byte, any) error) (*T, error) { 381 | if err := r.AsError(); err != nil { 382 | return nil, err 383 | } 384 | var t T 385 | if r.Len() > 0 { 386 | err := unmarshal(r.AsBytes(), &t) 387 | if err != nil { 388 | return nil, err 389 | } 390 | } 391 | return &t, nil 392 | } 393 | 394 | // UnmarshalUnchecked unmarshal to object 395 | // NOTE: maybe reference Rust memory buffer 396 | // 397 | //go:inline 398 | func (r RustFfiResult[T]) UnmarshalUnchecked(unmarshal func([]byte, any) error) *T { 399 | if err := r.AsError(); err != nil { 400 | return nil 401 | } 402 | var t T 403 | if r.Len() > 0 { 404 | _ = unmarshal(r.AsBytes(), &t) 405 | } 406 | return &t 407 | } 408 | 409 | "### 410 | .to_string() 411 | } 412 | 413 | // main.go 414 | fn codegen_goffi_iface_method(&self, _def_id: DefId, method: &Arc) -> Option { 415 | let mod_name = self.config.gomod_name.clone(); 416 | let iface_method_name = self.iface_method_name(method); 417 | let args_sign = method 418 | .args 419 | .iter() 420 | .map(|arg| { 421 | if arg.ty.is_scalar() { 422 | format!("{} {}", self.arg_name(arg), self.arg_type(arg, true)) 423 | } else { 424 | format!( 425 | "{} {mod_name}.TBytes[{}]", 426 | self.arg_name(arg), 427 | self.arg_type(arg, true) 428 | ) 429 | } 430 | }) 431 | .collect::>() 432 | .join(","); 433 | let ret_type = self.ret_type(method, true); 434 | let is_empty_ret = self.context.is_empty_ty(&method.ret.kind); 435 | Some(if is_empty_ret { 436 | format!("{iface_method_name}({args_sign}) ResultMsg") 437 | } else { 438 | format!("{iface_method_name}({args_sign}) gust.EnumResult[{mod_name}.TBytes[*{ret_type}], ResultMsg]") 439 | }) 440 | } 441 | 442 | // main.go 443 | fn codegen_goffi_service_impl(&self, service_def_id: DefId, s: &Service) -> String { 444 | let mod_name = self.config.gomod_name.clone(); 445 | let mut ffi_functions = String::new(); 446 | 447 | for method in &s.methods { 448 | let is_empty_ret = self.context.is_empty_ty(&method.ret.kind); 449 | let iface_method_name = self.iface_method_name(method); 450 | let ffi_func_name = self.ffi_func_name(service_def_id, method); 451 | let ffi_args_assign = method 452 | .args 453 | .iter() 454 | .map(|arg| { 455 | if arg.ty.is_scalar() { 456 | let name = self.arg_name(arg); 457 | if let TyKind::Bool = arg.ty.kind { 458 | format!("bool({name})") 459 | } else { 460 | name 461 | } 462 | } else { 463 | format!( 464 | "asBytes[{}]({})", 465 | self.arg_type(arg, true), 466 | self.arg_name(arg) 467 | ) 468 | } 469 | }) 470 | .collect::>() 471 | .join(","); 472 | let ffi_args_sign = method 473 | .args 474 | .iter() 475 | .map(|arg| { 476 | if arg.ty.is_scalar() { 477 | format!("{} {}", self.arg_name(arg), self.arg_type(arg, true)) 478 | } else { 479 | format!("{} C.struct_Buffer", self.arg_name(arg)) 480 | } 481 | }) 482 | .collect::>() 483 | .join(","); 484 | 485 | if is_empty_ret { 486 | ffi_functions.push_str(&format!(r###" 487 | //go:inline 488 | //export {ffi_func_name} 489 | func {ffi_func_name}({ffi_args_sign}) C.struct_GoFfiResult {{ 490 | if _{iface_method_name}_Ret_Msg := GlobalGoFfi.{iface_method_name}({ffi_args_assign}); _{iface_method_name}_Ret_Msg.Code == {mod_name}.RcNoError {{ 491 | return C.struct_GoFfiResult{{}} 492 | }} else {{ 493 | return C.struct_GoFfiResult{{ 494 | code: C.int8_t(_{iface_method_name}_Ret_Msg.Code), 495 | data_ptr: C.leak_buffer(asBuffer({mod_name}.TBytesFromString[string](_{iface_method_name}_Ret_Msg.Msg))), 496 | }} 497 | }} 498 | }} 499 | "###)); 500 | } else { 501 | ffi_functions.push_str(&format!(r###" 502 | //go:inline 503 | //export {ffi_func_name} 504 | func {ffi_func_name}({ffi_args_sign}) C.struct_GoFfiResult {{ 505 | if _{iface_method_name}_Ret := GlobalGoFfi.{iface_method_name}({ffi_args_assign}); _{iface_method_name}_Ret.IsOk() {{ 506 | return C.{ffi_func_name}_set_result(asBuffer(_{iface_method_name}_Ret.Unwrap())) 507 | }} else {{ 508 | _{iface_method_name}_Ret_Msg := _{iface_method_name}_Ret.UnwrapErr() 509 | if _{iface_method_name}_Ret_Msg.Code == {mod_name}.RcNoError {{ 510 | _{iface_method_name}_Ret_Msg.Code = {mod_name}.RcUnknown 511 | }} 512 | return C.struct_GoFfiResult{{ 513 | code: C.int8_t(_{iface_method_name}_Ret_Msg.Code), 514 | data_ptr: C.leak_buffer(asBuffer({mod_name}.TBytesFromString[string](_{iface_method_name}_Ret_Msg.Msg))), 515 | }} 516 | }} 517 | }} 518 | "###)); 519 | } 520 | } 521 | 522 | format!( 523 | r###" 524 | type ResultMsg struct {{ 525 | Code {mod_name}.ResultCode 526 | Msg string 527 | }} 528 | 529 | //go:inline 530 | func asBuffer[T any](b {mod_name}.TBytes[T]) C.struct_Buffer {{ 531 | p, size := b.ForCBuffer() 532 | if size == 0 {{ 533 | return C.struct_Buffer{{}} 534 | }} 535 | return C.struct_Buffer{{ 536 | ptr: (*C.uint8_t)(p), 537 | len: C.uintptr_t(size), 538 | cap: C.uintptr_t(size), 539 | }} 540 | }} 541 | 542 | //go:inline 543 | func asBytes[T any](buf C.struct_Buffer) {mod_name}.TBytes[T] {{ 544 | if buf.len == 0 {{ 545 | return {mod_name}.TBytes[T]{{}} 546 | }} 547 | return {mod_name}.TBytesFromBytes[T](*(*[]byte)(unsafe.Pointer(&reflect.SliceHeader{{ 548 | Data: uintptr(unsafe.Pointer(buf.ptr)), 549 | Len: int(buf.len), 550 | Cap: int(buf.cap), 551 | }}))) 552 | }} 553 | 554 | {ffi_functions} 555 | "### 556 | ) 557 | } 558 | } 559 | -------------------------------------------------------------------------------- /rust/fcplug-build/src/with_codec/gen_rust_codec.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use pilota_build::rir::{Method, Service}; 4 | use pilota_build::{DefId, IdentName}; 5 | 6 | use crate::generator::{RustCodegenBackend, RustGeneratorBackend, ServiceType}; 7 | 8 | impl RustCodegenBackend for RustGeneratorBackend { 9 | fn codegen_rustffi_trait_method( 10 | &self, 11 | service_def_id: DefId, 12 | method: &Arc, 13 | ) -> Option { 14 | let method_name = (&**method.name).fn_ident(); 15 | let args = self.codegen_method_args(service_def_id, method); 16 | let ret = self.codegen_method_ret(service_def_id, method); 17 | Some(format!("fn {method_name}({args}) -> {ret}")) 18 | } 19 | fn codegen_rustffi_service_impl(&self, def_id: DefId, stream: &mut String, s: &Service) { 20 | let name = self.context.rust_name(def_id); 21 | let name_lower = name.to_lowercase(); 22 | let ust = &self.config.rust_mod_impl_name; 23 | stream.push_str( 24 | &s.methods 25 | .iter() 26 | .map(|method| { 27 | let fn_name = (&**method.name).fn_ident(); 28 | let args = self.codegen_ffi_args_param(def_id, method); 29 | let args_ident = self.codegen_ffi_args_ident(def_id, method); 30 | let ret = self.codegen_ffi_ret(def_id, method); 31 | format!( 32 | r###"#[no_mangle] 33 | #[inline] 34 | pub extern "C" fn {name_lower}_{fn_name}({args}) -> {ret} {{ 35 | {ret}::from(<{ust} as {name}>::{fn_name}({args_ident})) 36 | }} 37 | "### 38 | ) 39 | }) 40 | .collect::>() 41 | .join("\n"), 42 | ); 43 | } 44 | fn codegen_goffi_trait_method( 45 | &self, 46 | service_def_id: DefId, 47 | method: &Arc, 48 | ) -> Option { 49 | if self.context.is_empty_ty(&method.ret.kind) && !method.ret.is_scalar() { 50 | return None; 51 | } 52 | let method_name = (&**method.name).fn_ident(); 53 | let ffi_ret = self.codegen_ffi_ret(service_def_id, method); 54 | let ret_ty_name = self.rust_codegen_item_ty(&method.ret.kind); 55 | Some(format!("unsafe fn {method_name}_set_result(go_ret: ::fcplug::RustFfiArg<{ret_ty_name}>) -> {ffi_ret}")) 56 | } 57 | fn codegen_goffi_call_trait_method( 58 | &self, 59 | service_def_id: DefId, 60 | method: &Arc, 61 | ) -> Option { 62 | let name = self.context.rust_name(service_def_id); 63 | let name_lower = name.to_lowercase(); 64 | let method_name = (&**method.name).fn_ident(); 65 | let args = self.codegen_method_args(service_def_id, method); 66 | let ret = self.codegen_method_ret(service_def_id, method); 67 | let generic_signature = if self.context.is_empty_ty(&method.ret.kind) { 68 | String::new() 69 | } else { 70 | "".to_string() 71 | }; 72 | let args_ident = self.codegen_ffi_args_ident(service_def_id, method); 73 | Some(format!( 74 | r###"unsafe fn {method_name}{generic_signature}({args}) -> {ret} {{ 75 | ::fcplug::ABIResult::from({name_lower}_{method_name}({args_ident})) 76 | }} 77 | "### 78 | )) 79 | } 80 | fn codegen_goffi_service_impl(&self, def_id: DefId, stream: &mut String, s: &Service) { 81 | let name = self.context.rust_name(def_id); 82 | let name_lower = name.to_lowercase(); 83 | let ust = &self.config.rust_mod_impl_name; 84 | let ffi_fns = s 85 | .methods 86 | .iter() 87 | .map(|method| { 88 | let fn_name = (&**method.name).fn_ident(); 89 | let args = self.codegen_ffi_args_param(def_id, method); 90 | let ret = self.codegen_ffi_ret(def_id, method); 91 | format!("fn {name_lower}_{fn_name}({args}) -> {ret};") 92 | }) 93 | .collect::>() 94 | .join("\n"); 95 | stream.push_str(&format!( 96 | r###" 97 | #[link(name = "{}", kind = "{}")] 98 | extern "C" {{ 99 | {ffi_fns} 100 | }} 101 | "###, 102 | self.config.go_clib_name_base, self.config.rustc_link_kind_goffi, 103 | )); 104 | 105 | let store_to_rust_fns = s 106 | .methods 107 | .iter() 108 | .filter(|method| !method.ret.is_scalar()) 109 | .map(|method| { 110 | let fn_name = (&**method.name).fn_ident().to_string() + "_set_result"; 111 | let ret = self.codegen_ffi_ret(def_id, method); 112 | format!( 113 | r###"#[no_mangle] 114 | #[inline] 115 | pub extern "C" fn {name_lower}_{fn_name}(buf: ::fcplug::Buffer) -> {ret} {{ 116 | unsafe{{<{ust} as {name}>::{fn_name}(::fcplug::RustFfiArg::from(buf))}} 117 | }} 118 | "### 119 | ) 120 | }) 121 | .collect::>() 122 | .join("\n"); 123 | stream.push_str(&store_to_rust_fns); 124 | } 125 | } 126 | 127 | impl RustGeneratorBackend { 128 | fn codegen_ffi_args_param(&self, service_def_id: DefId, method: &Method) -> String { 129 | match self.context.service_type(service_def_id) { 130 | ServiceType::RustFfi => method 131 | .args 132 | .iter() 133 | .map(|arg| { 134 | let ident = (&**arg.name).snake_ident(); 135 | let ty_name = self.rust_codegen_item_ty(&arg.ty.kind); 136 | if arg.ty.is_scalar() { 137 | format!("{ident}: {ty_name}") 138 | } else { 139 | format!("{ident}: ::fcplug::Buffer") 140 | } 141 | }) 142 | .collect::>() 143 | .join(", "), 144 | ServiceType::GoFfi => method 145 | .args 146 | .iter() 147 | .map(|arg| { 148 | let ident = (&**arg.name).snake_ident(); 149 | let ty_name = self.rust_codegen_item_ty(&arg.ty.kind); 150 | if arg.ty.is_scalar() { 151 | format!("{ident}: {ty_name}") 152 | } else { 153 | format!("{ident}: ::fcplug::Buffer") 154 | } 155 | }) 156 | .collect::>() 157 | .join(", "), 158 | } 159 | } 160 | fn codegen_ffi_args_ident(&self, service_def_id: DefId, method: &Method) -> String { 161 | match self.context.service_type(service_def_id) { 162 | ServiceType::RustFfi => method 163 | .args 164 | .iter() 165 | .map(|arg| { 166 | let ident = (&**arg.name).snake_ident(); 167 | if arg.ty.is_scalar() { 168 | format!("{ident}") 169 | } else { 170 | format!("::fcplug::RustFfiArg::from({ident})") 171 | } 172 | }) 173 | .collect::>() 174 | .join(", "), 175 | ServiceType::GoFfi => method 176 | .args 177 | .iter() 178 | .map(|arg| { 179 | let ident = (&**arg.name).snake_ident(); 180 | if arg.ty.is_scalar() { 181 | format!("{ident}") 182 | } else { 183 | format!("::fcplug::Buffer::from_vec_mut(&mut {ident}.bytes)") 184 | } 185 | }) 186 | .collect::>() 187 | .join(", "), 188 | } 189 | } 190 | fn codegen_method_args(&self, service_def_id: DefId, method: &Method) -> String { 191 | match self.context.service_type(service_def_id) { 192 | ServiceType::RustFfi => method 193 | .args 194 | .iter() 195 | .map(|arg| { 196 | let ident = (&**arg.name).snake_ident(); 197 | let ty_name = self.rust_codegen_item_ty(&arg.ty.kind); 198 | if arg.ty.is_scalar() { 199 | format!("{ident}: {ty_name}") 200 | } else { 201 | format!("{ident}: ::fcplug::RustFfiArg<{ty_name}>") 202 | } 203 | }) 204 | .collect::>() 205 | .join(", "), 206 | ServiceType::GoFfi => method 207 | .args 208 | .iter() 209 | .map(|arg| { 210 | let ident = (&**arg.name).snake_ident(); 211 | let ty_name = self.rust_codegen_item_ty(&arg.ty.kind); 212 | if arg.ty.is_scalar() { 213 | format!("{ident}: {ty_name}") 214 | } else { 215 | format!("mut {ident}: ::fcplug::TBytes<{ty_name}>") 216 | } 217 | }) 218 | .collect::>() 219 | .join(", "), 220 | } 221 | } 222 | fn codegen_method_ret(&self, service_def_id: DefId, method: &Method) -> String { 223 | let ty_name = self.rust_codegen_item_ty(&method.ret.kind); 224 | match self.context.service_type(service_def_id) { 225 | ServiceType::RustFfi => { 226 | if self.context.is_empty_ty(&method.ret.kind) { 227 | format!("::fcplug::ABIResult<()>") 228 | } else if method.ret.is_scalar() { 229 | format!("{ty_name}") 230 | } else { 231 | format!("::fcplug::ABIResult<::fcplug::TBytes<{ty_name}>>") 232 | } 233 | } 234 | ServiceType::GoFfi => { 235 | if self.context.is_empty_ty(&method.ret.kind) { 236 | format!("::fcplug::ABIResult<()>") 237 | } else if method.ret.is_scalar() { 238 | format!("{ty_name}") 239 | } else { 240 | format!("::fcplug::ABIResult") 241 | } 242 | } 243 | } 244 | } 245 | fn codegen_ffi_ret(&self, service_def_id: DefId, method: &Method) -> String { 246 | let ty_name = self.rust_codegen_item_ty(&method.ret.kind); 247 | match self.context.service_type(service_def_id) { 248 | ServiceType::RustFfi => { 249 | if self.context.is_empty_ty(&method.ret.kind) { 250 | format!("::fcplug::RustFfiResult") 251 | } else if method.ret.is_scalar() { 252 | format!("{ty_name}") 253 | } else { 254 | format!("::fcplug::RustFfiResult") 255 | } 256 | } 257 | ServiceType::GoFfi => { 258 | if self.context.is_empty_ty(&method.ret.kind) { 259 | format!("::fcplug::GoFfiResult") 260 | } else if method.ret.is_scalar() { 261 | format!("{ty_name}") 262 | } else { 263 | format!("::fcplug::GoFfiResult") 264 | } 265 | } 266 | } 267 | } 268 | } 269 | -------------------------------------------------------------------------------- /rust/fcplug-build/src/with_codec/mod.rs: -------------------------------------------------------------------------------- 1 | use std::fs; 2 | use std::process::Command; 3 | 4 | use crate::config::IdlType; 5 | use crate::generator::{Generator, ImportPkg, MidOutput}; 6 | use crate::{deal_output, deal_result, CODE_IO}; 7 | 8 | mod gen_go_codec; 9 | mod gen_rust_codec; 10 | 11 | impl Generator { 12 | pub(crate) fn _gen_code(self) -> MidOutput { 13 | self.gen_go_codec_code(); 14 | MidOutput { 15 | rust_clib_includes: r###" 16 | typedef int8_t ResultCode; 17 | 18 | typedef struct Buffer { 19 | uint8_t *ptr; 20 | uintptr_t len; 21 | uintptr_t cap; 22 | } Buffer; 23 | 24 | typedef struct RustFfiResult { 25 | ResultCode code; 26 | struct Buffer data; 27 | } RustFfiResult; 28 | 29 | typedef struct GoFfiResult { 30 | ResultCode code; 31 | uintptr_t data_ptr; 32 | } GoFfiResult; 33 | 34 | void free_buffer(struct Buffer buf); 35 | uintptr_t leak_buffer(struct Buffer buf); 36 | 37 | "### 38 | .to_string(), 39 | mod_requires: vec![ 40 | "github.com/andeya/gust@v1.5.2".to_string(), 41 | "github.com/bytedance/sonic@latest".to_string(), 42 | match self.config.idl_type { 43 | IdlType::Proto | IdlType::ProtoNoCodec => "google.golang.org/protobuf@v1.26.0", 44 | IdlType::Thrift | IdlType::ThriftNoCodec => "github.com/apache/thrift@v0.13.0", 45 | } 46 | .to_string(), 47 | ], 48 | imports: vec![ 49 | ImportPkg { 50 | in_main: true, 51 | in_lib: false, 52 | import_path: "github.com/andeya/gust".to_string(), 53 | use_code: "var _ gust.EnumResult[any, any]".to_string(), 54 | }, 55 | ImportPkg { 56 | in_main: false, 57 | in_lib: true, 58 | import_path: "github.com/andeya/gust/valconv".to_string(), 59 | use_code: "var _ valconv.ReadonlyBytes".to_string(), 60 | }, 61 | ImportPkg { 62 | in_main: false, 63 | in_lib: true, 64 | import_path: "github.com/bytedance/sonic".to_string(), 65 | use_code: "var _ = sonic.Marshal".to_string(), 66 | }, 67 | match self.config.idl_type { 68 | IdlType::Proto | IdlType::ProtoNoCodec => ImportPkg { 69 | in_main: false, 70 | in_lib: true, 71 | import_path: "google.golang.org/protobuf/proto".to_string(), 72 | use_code: "var _ = proto.Marshal".to_string(), 73 | }, 74 | IdlType::Thrift | IdlType::ThriftNoCodec => ImportPkg::default(), 75 | }, 76 | ImportPkg { 77 | in_main: true, 78 | in_lib: false, 79 | import_path: self.config.gomod_path, 80 | use_code: format!("var _ {}.ResultCode", self.config.gomod_name), 81 | }, 82 | ], 83 | } 84 | } 85 | fn gen_go_codec_code(&self) { 86 | match self.config.idl_type { 87 | IdlType::Proto | IdlType::ProtoNoCodec => { 88 | deal_output( 89 | Command::new("protoc") 90 | .arg(format!( 91 | "--proto_path={}", 92 | self.config.target_out_dir.to_str().unwrap() 93 | )) 94 | .arg(format!( 95 | "--go_out={}", 96 | self.config.pkg_dir.to_str().unwrap() 97 | )) 98 | .arg(self.config.idl_file.as_os_str()) 99 | .output(), 100 | ); 101 | } 102 | IdlType::Thrift | IdlType::ThriftNoCodec => { 103 | deal_output( 104 | Command::new("thriftgo") 105 | .arg(format!("-g=go")) 106 | .arg(format!( 107 | "-o={}", 108 | self.config 109 | .target_out_dir 110 | .join("gen-thrift") 111 | .to_str() 112 | .unwrap() 113 | )) 114 | .arg(self.config.idl_file.as_os_str()) 115 | .output(), 116 | ); 117 | let go_mod_name = &self.config.gomod_name; 118 | deal_result( 119 | CODE_IO, 120 | fs::rename( 121 | self.config 122 | .target_out_dir 123 | .join("gen-thrift") 124 | .join(&go_mod_name) 125 | .join(&format!("{go_mod_name}.go")), 126 | self.config 127 | .pkg_dir 128 | .join(&format!("{go_mod_name}.thrift.go")), 129 | ), 130 | ); 131 | } 132 | }; 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /rust/fcplug-build/src/without_codec/gen_go_no_codec.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | #![allow(unused_variables)] 3 | 4 | use std::sync::Arc; 5 | 6 | use pilota_build::DefId; 7 | use pilota_build::rir::{Message, Method, Service}; 8 | 9 | use crate::generator::{GoCodegenBackend, GoGeneratorBackend}; 10 | 11 | impl GoCodegenBackend for GoGeneratorBackend { 12 | fn codegen_struct_type(&self, def_id: DefId, s: &Message) -> String { 13 | let files = s 14 | .fields 15 | .iter() 16 | .map(|field| { 17 | let field_name = self.field_name(field); 18 | let field_type = self.field_type(field); 19 | let field_tag = self.field_tag(field); 20 | format!("{field_name} {field_type} {field_tag}") 21 | }) 22 | .collect::>() 23 | .join(" \n"); 24 | let struct_name = self.struct_name(def_id); 25 | format!(r###" 26 | type {struct_name} struct {{ 27 | {files} 28 | }} 29 | "###) 30 | } 31 | 32 | fn codegen_rustffi_iface_method(&self, service_def_id: DefId, method: &Arc) -> Option<(String, String)> { 33 | // TODO 34 | None 35 | } 36 | 37 | fn codegen_rustffi_service_impl(&self, service_def_id: DefId, s: &Service) -> String { 38 | // TODO 39 | String::new() 40 | } 41 | 42 | fn codegen_goffi_iface_method(&self, service_def_id: DefId, method: &Arc) -> Option { 43 | // TODO 44 | None 45 | } 46 | 47 | fn codegen_goffi_service_impl(&self, service_def_id: DefId, s: &Service) -> String { 48 | // TODO 49 | String::new() 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /rust/fcplug-build/src/without_codec/gen_rust_no_codec.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | #![allow(unused_variables)] 3 | 4 | use std::sync::Arc; 5 | 6 | use pilota_build::DefId; 7 | use pilota_build::rir::{Method, Service}; 8 | 9 | use crate::generator::{RustCodegenBackend, RustGeneratorBackend}; 10 | 11 | impl RustCodegenBackend for RustGeneratorBackend { 12 | fn codegen_rustffi_trait_method(&self, service_def_id: DefId, method: &Arc) -> Option { 13 | // TODO 14 | None 15 | } 16 | 17 | 18 | fn codegen_rustffi_service_impl(&self, def_id: DefId, stream: &mut String, s: &Service) { 19 | // TODO 20 | } 21 | 22 | fn codegen_goffi_trait_method(&self, service_def_id: DefId, method: &Arc) -> Option { 23 | // TODO 24 | None 25 | } 26 | 27 | fn codegen_goffi_call_trait_method(&self, service_def_id: DefId, method: &Arc) -> Option { 28 | // TODO 29 | None 30 | } 31 | 32 | 33 | fn codegen_goffi_service_impl(&self, def_id: DefId, stream: &mut String, s: &Service) { 34 | // TODO 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /rust/fcplug-build/src/without_codec/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::generator::{Generator, MidOutput}; 2 | 3 | mod gen_go_no_codec; 4 | mod gen_rust_no_codec; 5 | 6 | impl Generator { 7 | pub(crate) fn _gen_code(self) -> MidOutput { 8 | MidOutput { 9 | rust_clib_includes: "".to_string(), 10 | mod_requires: vec![], 11 | imports: vec![], 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /rust/fcplug/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "fcplug" 3 | edition.workspace = true 4 | version.workspace = true 5 | authors.workspace = true 6 | description.workspace = true 7 | repository.workspace = true 8 | license.workspace = true 9 | readme.workspace = true 10 | documentation.workspace = true 11 | keywords.workspace = true 12 | categories.workspace = true 13 | 14 | [lib] 15 | crate-type = ["rlib", "staticlib"] 16 | 17 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 18 | 19 | [dependencies] 20 | defer-lite = { workspace = true } 21 | tracing = { workspace = true } 22 | tracing-appender = { workspace = true } 23 | tracing-subscriber = { workspace = true } 24 | anyhow = { workspace = true } 25 | pilota = { workspace = true } 26 | serde = { workspace = true } 27 | serde_json = { workspace = true } 28 | -------------------------------------------------------------------------------- /rust/fcplug/src/basic.rs: -------------------------------------------------------------------------------- 1 | // ---------------------------------TryFromBytes implement------------------------------- 2 | 3 | use crate::{ABIResult, TryFromBytes, TryIntoBytes}; 4 | 5 | impl TryFromBytes<'_> for Vec { 6 | #[inline(always)] 7 | fn try_from_bytes(buf: &mut [u8]) -> ABIResult 8 | where 9 | Self: Sized, 10 | { 11 | Ok(buf.to_owned()) 12 | } 13 | } 14 | 15 | impl<'a> TryFromBytes<'a> for &'a [u8] { 16 | #[inline(always)] 17 | fn try_from_bytes(buf: &'a mut [u8]) -> ABIResult 18 | where 19 | Self: Sized, 20 | { 21 | Ok(buf) 22 | } 23 | } 24 | 25 | impl<'a> TryFromBytes<'a> for &'a str { 26 | #[inline(always)] 27 | fn try_from_bytes(buf: &'a mut [u8]) -> ABIResult 28 | where 29 | Self: Sized, 30 | { 31 | Ok(unsafe { std::str::from_utf8_unchecked(buf) }) 32 | } 33 | } 34 | 35 | impl<'a> TryFromBytes<'a> for &'a mut [u8] { 36 | #[inline(always)] 37 | fn try_from_bytes(buf: &'a mut [u8]) -> ABIResult 38 | where 39 | Self: Sized, 40 | { 41 | Ok(buf) 42 | } 43 | } 44 | 45 | impl<'a> TryFromBytes<'a> for &'a mut str { 46 | #[inline(always)] 47 | fn try_from_bytes(buf: &'a mut [u8]) -> ABIResult 48 | where 49 | Self: Sized, 50 | { 51 | Ok(unsafe { std::str::from_utf8_unchecked_mut(buf) }) 52 | } 53 | } 54 | 55 | impl TryFromBytes<'_> for () { 56 | #[inline(always)] 57 | fn try_from_bytes(_: &mut [u8]) -> ABIResult 58 | where 59 | Self: Sized, 60 | { 61 | Ok(()) 62 | } 63 | } 64 | 65 | // ---------------------------------TryIntoBytes implement------------------------------- 66 | 67 | impl TryIntoBytes for Vec { 68 | #[inline(always)] 69 | fn try_into_bytes(self) -> ABIResult> 70 | where 71 | Self: Sized, 72 | { 73 | Ok(self) 74 | } 75 | } 76 | 77 | impl TryIntoBytes for String { 78 | #[inline(always)] 79 | fn try_into_bytes(self) -> ABIResult> 80 | where 81 | Self: Sized, 82 | { 83 | Ok(self.into_bytes()) 84 | } 85 | } 86 | 87 | const EMPTY_BYTES: Vec = Vec::new(); 88 | 89 | impl TryIntoBytes for () { 90 | #[inline(always)] 91 | fn try_into_bytes(self) -> ABIResult> 92 | where 93 | Self: Sized, 94 | { 95 | Ok(EMPTY_BYTES) 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /rust/fcplug/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(downcast_unchecked)] 2 | #![feature(try_trait_v2)] 3 | #![feature(new_uninit)] 4 | #![feature(const_trait_impl)] 5 | 6 | use std::convert::Infallible; 7 | use std::fmt::{Debug, Display, Formatter}; 8 | use std::ops::FromResidual; 9 | 10 | #[cfg(debug_assertions)] 11 | use tracing::error; 12 | 13 | mod basic; 14 | pub mod protobuf; 15 | pub mod serde; 16 | 17 | #[inline] 18 | #[no_mangle] 19 | pub extern "C" fn no_mangle_types(_: Buffer, _: RustFfiResult, _: GoFfiResult) { 20 | unimplemented!() 21 | } 22 | 23 | #[inline] 24 | #[no_mangle] 25 | pub extern "C" fn free_buffer(buf: Buffer) { 26 | unsafe { buf.mem_free() } 27 | } 28 | 29 | #[inline] 30 | #[no_mangle] 31 | pub extern "C" fn leak_buffer(buf: Buffer) -> usize { 32 | if let Some(v) = buf.read() { 33 | Box::leak(Box::new(v.to_vec())) as *mut Vec as usize 34 | } else { 35 | 0 36 | } 37 | } 38 | 39 | pub trait FromMessage { 40 | fn from_message(value: M) -> Self; 41 | fn try_from_bytes(buf: &mut [u8]) -> ABIResult 42 | where 43 | Self: Sized, 44 | M: for<'a> TryFromBytes<'a>, 45 | { 46 | Ok(Self::from_message(M::try_from_bytes(buf)?)) 47 | } 48 | } 49 | 50 | pub trait IntoMessage { 51 | fn into_message(self) -> M; 52 | fn try_into_bytes(self) -> ABIResult> 53 | where 54 | Self: Sized, 55 | M: TryIntoBytes, 56 | { 57 | self.into_message().try_into_bytes() 58 | } 59 | } 60 | 61 | pub trait ABIMessage<'a>: TryFromBytes<'a> + TryIntoBytes {} 62 | 63 | pub trait TryFromBytes<'b>: Debug { 64 | fn try_from_bytes(buf: &'b mut [u8]) -> ABIResult 65 | where 66 | Self: Sized; 67 | } 68 | 69 | pub trait TryIntoBytes: Debug { 70 | fn try_into_bytes(self) -> ABIResult> 71 | where 72 | Self: Sized; 73 | } 74 | 75 | #[derive(Copy, Clone, Debug)] 76 | #[repr(C)] 77 | pub struct Buffer { 78 | pub ptr: *mut u8, 79 | pub len: usize, 80 | pub cap: usize, 81 | } 82 | 83 | impl Default for Buffer { 84 | #[inline] 85 | fn default() -> Self { 86 | Self { 87 | ptr: std::ptr::null_mut(), 88 | len: 0, 89 | cap: 0, 90 | } 91 | } 92 | } 93 | 94 | impl Buffer { 95 | #[inline] 96 | pub fn null() -> Self { 97 | Self::default() 98 | } 99 | 100 | /// read provides a reference to the included data to be parsed or copied elsewhere 101 | /// data is only guaranteed to live as long as the Buffer 102 | /// (or the scope of the extern "C" call it came from) 103 | #[inline] 104 | pub fn read(&self) -> Option<&[u8]> { 105 | if self.is_empty() { 106 | None 107 | } else { 108 | unsafe { Some(std::slice::from_raw_parts(self.ptr, self.len)) } 109 | } 110 | } 111 | 112 | /// read_mut provides a reference to the included data to be parsed or copied elsewhere 113 | /// data is only guaranteed to live as long as the Buffer 114 | /// (or the scope of the extern "C" call it came from) 115 | #[inline] 116 | pub fn read_mut(&mut self) -> Option<&mut [u8]> { 117 | if self.is_empty() { 118 | None 119 | } else { 120 | unsafe { Some(std::slice::from_raw_parts_mut(self.ptr, self.len)) } 121 | } 122 | } 123 | 124 | #[inline] 125 | pub(crate) fn is_empty(&self) -> bool { 126 | self.ptr.is_null() || self.len == 0 || self.cap == 0 127 | } 128 | 129 | #[inline] 130 | pub(crate) unsafe fn mem_free(self) { 131 | if !self.ptr.is_null() { 132 | let _ = unsafe { Vec::from_raw_parts(self.ptr, self.len, self.cap) }; 133 | } 134 | } 135 | 136 | /// this releases our memory to the caller 137 | #[inline] 138 | pub fn from_vec(mut v: Vec) -> Self { 139 | if v.is_empty() { 140 | Self::null() 141 | } else { 142 | v.shrink_to_fit(); 143 | Self { 144 | len: v.len(), 145 | cap: v.capacity(), 146 | ptr: v.leak().as_mut_ptr(), 147 | } 148 | } 149 | } 150 | 151 | /// this share our memory to the caller 152 | #[inline] 153 | pub fn from_vec_mut(v: &mut Vec) -> Self { 154 | if v.is_empty() { 155 | Self::null() 156 | } else { 157 | v.shrink_to_fit(); 158 | Self { 159 | len: v.len(), 160 | cap: v.capacity(), 161 | ptr: v.as_mut_ptr(), 162 | } 163 | } 164 | } 165 | 166 | // pub(crate) fn consume_vec(self) -> Vec { 167 | // if !self.ptr.is_null() { 168 | // unsafe { Vec::from_raw_parts(self.ptr, self.len, self.cap) } 169 | // } else { 170 | // Vec::new() 171 | // } 172 | // } 173 | } 174 | 175 | impl Display for Buffer { 176 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 177 | write!(f, "{:?}", self.read()) 178 | } 179 | } 180 | 181 | pub type ResultCode = i8; 182 | 183 | #[derive(Debug)] 184 | pub struct ResultMsg { 185 | pub code: ResultCode, 186 | pub msg: String, 187 | } 188 | 189 | const RC_NO_ERROR: ResultCode = 0; 190 | const RC_DECODE: ResultCode = -1; 191 | const RC_ENCODE: ResultCode = -2; 192 | const RC_UNKNOWN: ResultCode = -128; 193 | 194 | pub type ABIResult = Result; 195 | 196 | #[derive(Debug, Clone)] 197 | pub struct TBytes { 198 | pub bytes: Vec, 199 | _p: std::marker::PhantomData, 200 | } 201 | 202 | impl TBytes { 203 | #[inline(always)] 204 | pub fn new(bytes: Vec) -> Self { 205 | Self { 206 | bytes, 207 | _p: Default::default(), 208 | } 209 | } 210 | } 211 | 212 | impl TBytes { 213 | #[inline] 214 | pub fn try_from(value: T) -> ABIResult 215 | where 216 | T: IntoMessage, 217 | M: TryIntoBytes, 218 | { 219 | Ok(TBytes::::new(T::into_message(value).try_into_bytes()?)) 220 | } 221 | } 222 | 223 | pub trait TryIntoTBytes { 224 | #[inline] 225 | fn try_into_tbytes(self) -> ABIResult> 226 | where 227 | Self: IntoMessage + Sized, 228 | for<'a> M: TryIntoBytes, 229 | { 230 | Ok(TBytes::::new(self.into_message().try_into_bytes()?)) 231 | } 232 | } 233 | 234 | impl TryIntoTBytes for T {} 235 | 236 | impl TryIntoBytes for TBytes { 237 | fn try_into_bytes(self) -> ABIResult> 238 | where 239 | Self: Sized, 240 | { 241 | Ok(self.bytes) 242 | } 243 | } 244 | 245 | #[derive(Debug)] 246 | pub struct RustFfiArg { 247 | buf: Buffer, 248 | _p: std::marker::PhantomData, 249 | } 250 | 251 | impl RustFfiArg { 252 | #[inline(always)] 253 | pub fn from(buf: Buffer) -> Self { 254 | Self { 255 | buf, 256 | _p: Default::default(), 257 | } 258 | } 259 | pub fn bytes(&self) -> &[u8] { 260 | self.buf.read().unwrap_or_default() 261 | } 262 | pub fn bytes_mut(&mut self) -> &mut [u8] { 263 | self.buf.read_mut().unwrap_or_default() 264 | } 265 | pub fn try_to_object(&mut self) -> ABIResult 266 | where 267 | Self: Sized, 268 | for<'a> U: TryFromBytes<'a>, 269 | T: FromMessage, 270 | { 271 | Ok(T::from_message(U::try_from_bytes(self.bytes_mut())?)) 272 | } 273 | } 274 | 275 | #[derive(Debug)] 276 | #[repr(C)] 277 | pub struct GoFfiResult { 278 | pub code: ResultCode, 279 | pub data_ptr: usize, 280 | } 281 | 282 | impl GoFfiResult { 283 | #[inline] 284 | pub fn from_ok(data: T) -> Self { 285 | Self { 286 | code: RC_NO_ERROR, 287 | data_ptr: Box::leak(Box::new(data)) as *mut T as usize, 288 | } 289 | } 290 | #[inline] 291 | pub(crate) fn from_err(mut ret_msg: ResultMsg) -> Self { 292 | let err = ret_msg.msg.to_string(); 293 | #[cfg(debug_assertions)] 294 | { 295 | error!("{}", err); 296 | } 297 | if ret_msg.code == 0 { 298 | ret_msg.code = RC_UNKNOWN 299 | } 300 | Self { 301 | code: ret_msg.code, 302 | data_ptr: Box::leak(Box::new(err)) as *mut String as usize, 303 | } 304 | } 305 | #[inline] 306 | pub unsafe fn consume_data(&mut self) -> Option { 307 | let data_ptr = self.data_ptr as *mut T; 308 | if data_ptr.is_null() { 309 | None 310 | } else { 311 | self.data_ptr = std::ptr::null_mut::() as usize; 312 | Some(*Box::from_raw(data_ptr)) 313 | } 314 | } 315 | } 316 | 317 | impl FromResidual> for GoFfiResult { 318 | #[inline] 319 | fn from_residual(residual: Result) -> Self { 320 | let ResultMsg { code, msg } = residual.unwrap_err(); 321 | Self { 322 | code, 323 | data_ptr: Box::leak(Box::new(msg)) as *mut String as usize, 324 | } 325 | } 326 | } 327 | 328 | impl From> for GoFfiResult { 329 | #[inline] 330 | fn from(value: ABIResult) -> Self { 331 | match value { 332 | Ok(v) => match v.try_into_bytes() { 333 | Ok(v) => Self::from_ok(Buffer::from_vec(v)), 334 | Err(e) => Self::from_err(e), 335 | }, 336 | Err(e) => Self::from_err(e), 337 | } 338 | } 339 | } 340 | 341 | impl From for ABIResult { 342 | #[inline] 343 | fn from(mut value: GoFfiResult) -> Self { 344 | match value.code { 345 | RC_NO_ERROR => { 346 | if let Some(v) = unsafe { value.consume_data::() } { 347 | Ok(v) 348 | } else { 349 | Ok(T::default()) 350 | } 351 | } 352 | code => { 353 | let msg = if let Some(v) = unsafe { value.consume_data::() } { 354 | v 355 | } else { 356 | String::default() 357 | }; 358 | Err(ResultMsg { code, msg }) 359 | } 360 | } 361 | } 362 | } 363 | 364 | #[derive(Debug)] 365 | #[repr(C)] 366 | pub struct RustFfiResult { 367 | pub code: ResultCode, 368 | pub data: Buffer, 369 | } 370 | 371 | impl RustFfiResult { 372 | #[inline] 373 | pub fn from_ok(data: Buffer) -> Self { 374 | Self { 375 | code: RC_NO_ERROR, 376 | data, 377 | } 378 | } 379 | #[inline] 380 | pub(crate) fn from_err(mut ret_msg: ResultMsg) -> Self { 381 | #[cfg(debug_assertions)] 382 | { 383 | let err = ret_msg.msg.to_string(); 384 | error!("{}", err); 385 | } 386 | if ret_msg.code == 0 { 387 | ret_msg.code = RC_UNKNOWN 388 | } 389 | Self { 390 | code: ret_msg.code, 391 | data: Buffer::from_vec(ret_msg.msg.into_bytes()), 392 | } 393 | } 394 | } 395 | 396 | impl FromResidual> for RustFfiResult { 397 | fn from_residual(residual: Result) -> Self { 398 | Self { 399 | code: residual.unwrap_err(), 400 | data: Buffer::null(), 401 | } 402 | } 403 | } 404 | 405 | impl From> for RustFfiResult { 406 | #[inline] 407 | fn from(value: ABIResult) -> Self { 408 | match value { 409 | Ok(v) => match v.try_into_bytes() { 410 | Ok(v) => Self::from_ok(Buffer::from_vec(v)), 411 | Err(e) => Self::from_err(e), 412 | }, 413 | Err(e) => Self::from_err(e), 414 | } 415 | } 416 | } 417 | 418 | impl<'a, T: TryFromBytes<'a>> From<&'a mut RustFfiResult> for ABIResult { 419 | #[inline] 420 | fn from(value: &'a mut RustFfiResult) -> Self { 421 | match value.code { 422 | RC_NO_ERROR => TryFromBytes::try_from_bytes(value.data.read_mut().unwrap_or_default()) 423 | .map_err(|err| { 424 | let msg = format!("{:?}", err); 425 | #[cfg(debug_assertions)] 426 | { 427 | error!("{}", msg); 428 | } 429 | ResultMsg { 430 | code: RC_DECODE, 431 | msg, 432 | } 433 | }), 434 | code => Err(ResultMsg { 435 | code, 436 | msg: value.data.to_string(), 437 | }), 438 | } 439 | } 440 | } 441 | 442 | #[cfg(test)] 443 | mod tests { 444 | use crate::Buffer; 445 | 446 | #[test] 447 | fn it_works() { 448 | assert_eq!(4, 2 + 2); 449 | } 450 | 451 | #[test] 452 | fn test_free() { 453 | let buf = Buffer::from_vec(vec![0, 1, 2, 3, 4, 5, 6, 7, 8]); 454 | println!("{:?}", buf.read()); 455 | { 456 | println!("{:?}", unsafe { 457 | Vec::from_raw_parts(buf.ptr, buf.len, buf.cap) 458 | }); 459 | } 460 | println!("{:?}", buf.read()); 461 | } 462 | } 463 | -------------------------------------------------------------------------------- /rust/fcplug/src/log.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | 3 | use std::{env, io, str::FromStr, sync::Once}; 4 | 5 | use defer_lite::defer; 6 | pub use tracing::*; 7 | use tracing_appender::non_blocking::WorkerGuard; 8 | 9 | #[cfg(debug_assertions)] 10 | const DEBUG_ASSERTIONS: bool = true; 11 | 12 | #[cfg(not(debug_assertions))] 13 | const DEBUG_ASSERTIONS: bool = false; 14 | 15 | pub type FileLogGuard = WorkerGuard; 16 | 17 | static ONCE: Once = Once::new(); 18 | 19 | pub fn init_log(filelog: Option) -> Option { 20 | if ONCE.is_completed() { 21 | return None; 22 | } 23 | ONCE.call_once(|| {}); 24 | defer! {info!("logger initialized")} 25 | 26 | // Set the format of the log output, 27 | // for example, whether to include the log level, 28 | // whether to include the location of the log source, 29 | // and set the time format of the log. 30 | // via: https://docs.rs/tracing-subscriber/0.3.3/tracing_subscriber/fmt/struct.SubscriberBuilder.html#method.with_timer 31 | let format = tracing_subscriber::fmt::format().with_level(true).with_target(true); 32 | 33 | // max log level 34 | let max_level = env::var("RUST_LOG") 35 | .map_or_else(|_| Level::INFO, |s| Level::from_str(&s).unwrap_or(Level::INFO)); 36 | 37 | // Initialize and set the log format (customize and filter logs) 38 | let subscriber_builder = 39 | tracing_subscriber::fmt().with_max_level(max_level).event_format(format); 40 | 41 | if !filelog.unwrap_or(!DEBUG_ASSERTIONS) { 42 | subscriber_builder 43 | .with_writer(io::stdout) 44 | .init(); 45 | return None; 46 | } 47 | 48 | // Use tracing_appender to specify the output destination of the log 49 | // via: https://docs.rs/tracing-appender/0.2.0/tracing_appender/ 50 | let file_appender = tracing_appender::rolling::daily("./.out/log/", "anfs.log"); 51 | let (non_blocking, guard) = tracing_appender::non_blocking(file_appender); 52 | subscriber_builder 53 | .with_writer(non_blocking)// write to file, will overwrite stdout above 54 | .with_ansi(false)// If the log is written to a file, the ansi color output function should be turned off 55 | .init(); 56 | Some(guard) 57 | } 58 | 59 | #[test] 60 | fn test() { 61 | use tracing::*; 62 | 63 | #[allow(unused_variables)] 64 | //let x = init_log(None); 65 | #[allow(unused_variables)] 66 | let x = init_log(None); 67 | 68 | let warn_description = "Invalid Input"; 69 | let input = &[0x27, 0x45]; 70 | 71 | warn!(?input, warning = warn_description); 72 | warn!(target: "evmmmmmm",warning2 = warn_description, "Received warning for input: {:?}", input,); 73 | } 74 | -------------------------------------------------------------------------------- /rust/fcplug/src/protobuf.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Debug; 2 | 3 | pub use pilota::prost::Message; 4 | 5 | use crate::{ 6 | ABIResult, FromMessage, IntoMessage, ResultMsg, TryFromBytes, TryIntoBytes, RC_DECODE, 7 | RC_ENCODE, 8 | }; 9 | 10 | #[derive(Debug)] 11 | pub struct PbMessage(pub T); 12 | 13 | impl FromMessage> for T { 14 | #[inline] 15 | fn from_message(value: PbMessage) -> Self { 16 | value.0 17 | } 18 | } 19 | 20 | impl IntoMessage> for T { 21 | #[inline] 22 | fn into_message(self) -> PbMessage { 23 | PbMessage(self) 24 | } 25 | } 26 | 27 | impl TryFromBytes<'_> for PbMessage { 28 | #[inline] 29 | fn try_from_bytes(buf: &mut [u8]) -> ABIResult 30 | where 31 | Self: Sized, 32 | { 33 | Ok(T::decode(buf as &[u8]) 34 | .map(PbMessage) 35 | .map_err(decode_map_err)?) 36 | } 37 | } 38 | 39 | impl TryIntoBytes for PbMessage { 40 | #[inline] 41 | fn try_into_bytes(self) -> ABIResult> { 42 | Ok(self.0.encode_to_vec()) 43 | } 44 | } 45 | 46 | #[inline] 47 | fn decode_map_err(e: pilota::prost::DecodeError) -> ResultMsg { 48 | ResultMsg { 49 | code: RC_DECODE, 50 | msg: e.to_string(), 51 | } 52 | } 53 | 54 | #[inline] 55 | #[allow(dead_code)] 56 | fn encode_map_err(e: pilota::prost::EncodeError) -> ResultMsg { 57 | ResultMsg { 58 | code: RC_ENCODE, 59 | msg: e.to_string(), 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /rust/fcplug/src/serde.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Debug; 2 | 3 | use serde::{Deserialize, Serialize}; 4 | 5 | use crate::{ 6 | ABIResult, FromMessage, IntoMessage, ResultMsg, TryFromBytes, TryIntoBytes, RC_DECODE, 7 | }; 8 | 9 | #[derive(Debug)] 10 | pub struct JsonMessage Deserialize<'a> + Serialize + Debug>(pub T); 11 | 12 | impl FromMessage> for T 13 | where 14 | T: for<'a> Deserialize<'a> + Serialize + Debug, 15 | { 16 | #[inline] 17 | fn from_message(value: JsonMessage) -> Self { 18 | value.0 19 | } 20 | } 21 | 22 | impl IntoMessage> for T 23 | where 24 | T: for<'a> Deserialize<'a> + Serialize + Debug, 25 | { 26 | #[inline] 27 | fn into_message(self) -> JsonMessage { 28 | JsonMessage(self) 29 | } 30 | } 31 | 32 | impl TryFromBytes<'_> for JsonMessage 33 | where 34 | T: for<'a> Deserialize<'a> + Serialize + Debug, 35 | { 36 | #[inline] 37 | fn try_from_bytes(buf: &mut [u8]) -> ABIResult 38 | where 39 | Self: Sized, 40 | { 41 | Ok(serde_json::from_slice::(buf as &[u8]) 42 | .map(JsonMessage) 43 | .map_err(decode_map_err)?) 44 | } 45 | } 46 | 47 | impl TryIntoBytes for JsonMessage 48 | where 49 | T: for<'a> Deserialize<'a> + Serialize + Debug, 50 | { 51 | #[inline] 52 | fn try_into_bytes(self) -> ABIResult> { 53 | Ok(serde_json::to_vec(&self.0).map_err(encode_map_err)?) 54 | } 55 | } 56 | 57 | #[inline] 58 | fn decode_map_err(e: serde_json::Error) -> ResultMsg { 59 | ResultMsg { 60 | code: RC_DECODE, 61 | msg: e.to_string(), 62 | } 63 | } 64 | 65 | #[inline] 66 | fn encode_map_err(e: serde_json::Error) -> ResultMsg { 67 | ResultMsg { 68 | code: RC_DECODE, 69 | msg: e.to_string(), 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /samples/echo/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "echo" 3 | edition.workspace = true 4 | version.workspace = true 5 | authors.workspace = true 6 | description.workspace = true 7 | repository.workspace = true 8 | license.workspace = true 9 | readme.workspace = true 10 | documentation.workspace = true 11 | keywords.workspace = true 12 | categories.workspace = true 13 | 14 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 15 | 16 | [lib] 17 | crate-type = ["rlib", "staticlib"] 18 | 19 | [dependencies] 20 | fcplug = { workspace = true } 21 | pilota = { workspace = true } 22 | serde = { workspace = true } 23 | serde_json = { workspace = true } 24 | 25 | [build-dependencies] 26 | fcplug-build = { workspace = true, features = ["no-codec"] } 27 | -------------------------------------------------------------------------------- /samples/echo/README.md: -------------------------------------------------------------------------------- 1 | # echo 2 | 3 | No codec sample. 4 | 5 | NOTE: In development! 6 | -------------------------------------------------------------------------------- /samples/echo/build.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_imports)] 2 | 3 | use fcplug_build::{generate_code, Config, UnitLikeStructPath}; 4 | 5 | fn main() { 6 | generate_code(Config { 7 | idl_file: "./echo.thrift".into(), 8 | go_root_path: None, 9 | go_mod_parent: "github.com/andeya/fcplug/samples", 10 | target_crate_dir: None, 11 | use_goffi_cdylib: false, 12 | add_clib_to_git: false, 13 | }); 14 | } 15 | -------------------------------------------------------------------------------- /samples/echo/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cargo build --release 4 | cargo build --release 5 | cargo build --release 6 | -------------------------------------------------------------------------------- /samples/echo/cgobin/clib_goffi_gen.go: -------------------------------------------------------------------------------- 1 | // Code generated by fcplug. DO NOT EDIT. 2 | 3 | package main 4 | 5 | /* 6 | #cgo CFLAGS: -I/Users/henrylee2cn/rust/fcplug/target/debug 7 | #cgo LDFLAGS: -L/Users/henrylee2cn/rust/fcplug/target/debug -lecho -ldl -lm 8 | 9 | #include "echo.h" 10 | */ 11 | import "C" 12 | import ( 13 | "reflect" 14 | "unsafe" 15 | ) 16 | 17 | // main function is never called by C to. 18 | func main() {} 19 | 20 | var ( 21 | _ reflect.SliceHeader 22 | _ unsafe.Pointer 23 | ) 24 | 25 | var GlobalGoFfi GoFfi = _UnimplementedGoFfi{} 26 | 27 | type GoFfi interface { 28 | } 29 | type _UnimplementedGoFfi struct{} 30 | -------------------------------------------------------------------------------- /samples/echo/cgobin/clib_goffi_impl.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | func init() { 4 | // TODO: Replace with your own implementation, then re-execute `cargo build` 5 | GlobalGoFfi = _UnimplementedGoFfi{} 6 | } 7 | -------------------------------------------------------------------------------- /samples/echo/echo.thrift: -------------------------------------------------------------------------------- 1 | struct A { 2 | 1: i32 number, 3 | } 4 | 5 | struct Ping { 6 | 1: string msg, 7 | 2: list number_list, 8 | 3: list number_set, 9 | } 10 | 11 | struct Pong { 12 | 1: string msg, 13 | 2: map number_map, 14 | } 15 | 16 | service rustFFI { 17 | Pong echo_rs (1: Ping req), 18 | } 19 | 20 | service goFFI { 21 | Pong echo_go (1: Ping req), 22 | } 23 | -------------------------------------------------------------------------------- /samples/echo/echo_gen.go: -------------------------------------------------------------------------------- 1 | // Code generated by fcplug. DO NOT EDIT. 2 | 3 | package echo 4 | 5 | /* 6 | #cgo CFLAGS: -I/Users/henrylee2cn/rust/fcplug/target/debug 7 | #cgo LDFLAGS: -L/Users/henrylee2cn/rust/fcplug/target/debug -lecho -ldl -lm 8 | 9 | #include "echo.h" 10 | */ 11 | import "C" 12 | 13 | import ( 14 | "errors" 15 | "fmt" 16 | "reflect" 17 | "unsafe" 18 | ) 19 | 20 | var ( 21 | _ = errors.New 22 | _ = fmt.Sprintf 23 | _ reflect.SliceHeader 24 | _ unsafe.Pointer 25 | ) 26 | 27 | type A struct { 28 | Number int32 `json:"number"` 29 | } 30 | 31 | type Pong struct { 32 | Msg string `json:"msg"` 33 | NumberMap map[int16]A `json:"number_map"` 34 | } 35 | 36 | type Ping struct { 37 | Msg string `json:"msg"` 38 | NumberList []A `json:"number_list"` 39 | NumberSet []A `json:"number_set"` 40 | } 41 | 42 | var GlobalRustFfi RustFfi = RustFfiImpl{} 43 | 44 | type RustFfi interface { 45 | } 46 | type RustFfiImpl struct{} 47 | -------------------------------------------------------------------------------- /samples/echo/echo_test.go: -------------------------------------------------------------------------------- 1 | package echo 2 | 3 | import "testing" 4 | 5 | func TestEcho(t *testing.T) { 6 | 7 | } 8 | -------------------------------------------------------------------------------- /samples/echo/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/andeya/fcplug/samples/echo 2 | 3 | go 1.18 4 | -------------------------------------------------------------------------------- /samples/echo/src/echo_ffi/echo_gen.rs: -------------------------------------------------------------------------------- 1 | // Code generated by fcplug. DO NOT EDIT. 2 | #![allow(warnings, clippy::all)] 3 | #[derive(PartialOrd, Hash, Eq, Ord, Debug, Default, ::serde::Serialize, ::serde::Deserialize)] 4 | #[repr(C)] 5 | #[derive(Clone, PartialEq)] 6 | pub struct A { 7 | pub number: i32, 8 | } 9 | #[derive(Debug, Default, ::serde::Serialize, ::serde::Deserialize, Clone, PartialEq)] 10 | pub struct Pong { 11 | pub msg: ::std::string::String, 12 | 13 | pub number_map: ::std::collections::HashMap, 14 | } 15 | pub(super) trait GoFfi {} 16 | 17 | pub trait GoFfiCall {} 18 | #[derive( 19 | PartialOrd, 20 | Hash, 21 | Eq, 22 | Ord, 23 | Debug, 24 | Default, 25 | ::serde::Serialize, 26 | ::serde::Deserialize, 27 | Clone, 28 | PartialEq, 29 | )] 30 | pub struct Ping { 31 | pub msg: ::std::string::String, 32 | 33 | pub number_list: ::std::vec::Vec, 34 | 35 | pub number_set: ::std::vec::Vec, 36 | } 37 | pub(super) trait RustFfi {} 38 | trait Ffi: RustFfi + GoFfi + GoFfiCall {} 39 | 40 | pub struct FfiImpl; 41 | 42 | impl GoFfiCall for FfiImpl {} 43 | impl Ffi for FfiImpl {} 44 | -------------------------------------------------------------------------------- /samples/echo/src/echo_ffi/mod.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_variables)] 2 | 3 | pub use echo_gen::*; 4 | 5 | mod echo_gen; 6 | 7 | impl GoFfi for FfiImpl {} 8 | 9 | impl RustFfi for FfiImpl {} 10 | -------------------------------------------------------------------------------- /samples/echo/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub fn add(left: usize, right: usize) -> usize { 2 | left + right 3 | } 4 | 5 | mod echo_ffi; 6 | 7 | #[cfg(test)] 8 | mod tests { 9 | use super::*; 10 | 11 | #[test] 12 | fn it_works() { 13 | let result = add(2, 2); 14 | assert_eq!(result, 4); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /samples/echo_pb/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "echo_pb" 3 | edition.workspace = true 4 | version.workspace = true 5 | authors.workspace = true 6 | description.workspace = true 7 | repository.workspace = true 8 | license.workspace = true 9 | readme.workspace = true 10 | documentation.workspace = true 11 | keywords.workspace = true 12 | categories.workspace = true 13 | 14 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 15 | 16 | [lib] 17 | crate-type = ["rlib", "staticlib"] 18 | 19 | [dependencies] 20 | fcplug = { workspace = true } 21 | pilota = { workspace = true } 22 | serde = { workspace = true } 23 | serde_json = { workspace = true } 24 | 25 | [build-dependencies] 26 | fcplug-build = { workspace = true, features = ["default"] } 27 | -------------------------------------------------------------------------------- /samples/echo_pb/README.md: -------------------------------------------------------------------------------- 1 | # echo_pb 2 | 3 | Protobuf IDL codec sample. 4 | 5 | ## Compile go into a C dynamic library 6 | 7 | Set build parameters `Config::use_goffi_cdylib` to `true` 8 | 9 | ```shell 10 | cargo build -Z unstable-options --out-dir . 11 | DYLD_LIBRARY_PATH=$DYLD_LIBRARY_PATH:{DIR}/fcplug/target/debug ./echo_pb 12 | ``` 13 | -------------------------------------------------------------------------------- /samples/echo_pb/build.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_imports)] 2 | 3 | fn main() { 4 | use fcplug_build::{generate_code, Config, UnitLikeStructPath}; 5 | generate_code(Config { 6 | idl_file: "./echo.proto".into(), 7 | target_crate_dir: None, 8 | go_root_path: None, 9 | // go_root_path: Some("/Users/henrylee2cn/.gvm/gos/go1.18.10".into()), 10 | go_mod_parent: "github.com/andeya/fcplug/samples", 11 | use_goffi_cdylib: false, 12 | add_clib_to_git: false, 13 | }); 14 | } 15 | -------------------------------------------------------------------------------- /samples/echo_pb/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cargo build --release 4 | cargo build --release 5 | cargo build --release 6 | -------------------------------------------------------------------------------- /samples/echo_pb/cgobin/clib_goffi_gen_darwin_amd64.go: -------------------------------------------------------------------------------- 1 | // Code generated by fcplug. DO NOT EDIT. 2 | 3 | package main 4 | 5 | /* 6 | #cgo CFLAGS: -I../../../target/debug 7 | #cgo LDFLAGS: -L../../../target/debug -lecho_pb -ldl -lm 8 | 9 | #include "echo_pb.h" 10 | */ 11 | import "C" 12 | import ( 13 | "reflect" 14 | "unsafe" 15 | 16 | "github.com/andeya/fcplug/samples/echo_pb" 17 | "github.com/andeya/gust" 18 | ) 19 | 20 | // main function is never called by C to. 21 | func main() {} 22 | 23 | var ( 24 | _ reflect.SliceHeader 25 | _ unsafe.Pointer 26 | ) 27 | var _ gust.EnumResult[any, any] 28 | var _ echo_pb.ResultCode 29 | 30 | var GlobalGoFfi GoFfi = _UnimplementedGoFfi{} 31 | 32 | type GoFfi interface { 33 | EchoGo(req echo_pb.TBytes[echo_pb.Ping]) gust.EnumResult[echo_pb.TBytes[*echo_pb.Pong], ResultMsg] 34 | } 35 | type _UnimplementedGoFfi struct{} 36 | 37 | func (_UnimplementedGoFfi) EchoGo(req echo_pb.TBytes[echo_pb.Ping]) gust.EnumResult[echo_pb.TBytes[*echo_pb.Pong], ResultMsg] { 38 | panic("unimplemented") 39 | } 40 | 41 | type ResultMsg struct { 42 | Code echo_pb.ResultCode 43 | Msg string 44 | } 45 | 46 | //go:inline 47 | func asBuffer[T any](b echo_pb.TBytes[T]) C.struct_Buffer { 48 | p, size := b.ForCBuffer() 49 | if size == 0 { 50 | return C.struct_Buffer{} 51 | } 52 | return C.struct_Buffer{ 53 | ptr: (*C.uint8_t)(p), 54 | len: C.uintptr_t(size), 55 | cap: C.uintptr_t(size), 56 | } 57 | } 58 | 59 | //go:inline 60 | func asBytes[T any](buf C.struct_Buffer) echo_pb.TBytes[T] { 61 | if buf.len == 0 { 62 | return echo_pb.TBytes[T]{} 63 | } 64 | return echo_pb.TBytesFromBytes[T](*(*[]byte)(unsafe.Pointer(&reflect.SliceHeader{ 65 | Data: uintptr(unsafe.Pointer(buf.ptr)), 66 | Len: int(buf.len), 67 | Cap: int(buf.cap), 68 | }))) 69 | } 70 | 71 | //go:inline 72 | //export goffi_echo_go 73 | func goffi_echo_go(req C.struct_Buffer) C.struct_GoFfiResult { 74 | if _EchoGo_Ret := GlobalGoFfi.EchoGo(asBytes[echo_pb.Ping](req)); _EchoGo_Ret.IsOk() { 75 | return C.goffi_echo_go_set_result(asBuffer(_EchoGo_Ret.Unwrap())) 76 | } else { 77 | _EchoGo_Ret_Msg := _EchoGo_Ret.UnwrapErr() 78 | if _EchoGo_Ret_Msg.Code == echo_pb.RcNoError { 79 | _EchoGo_Ret_Msg.Code = echo_pb.RcUnknown 80 | } 81 | return C.struct_GoFfiResult{ 82 | code: C.int8_t(_EchoGo_Ret_Msg.Code), 83 | data_ptr: C.leak_buffer(asBuffer(echo_pb.TBytesFromString[string](_EchoGo_Ret_Msg.Msg))), 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /samples/echo_pb/cgobin/clib_goffi_gen_linux_amd64.go: -------------------------------------------------------------------------------- 1 | // Code generated by fcplug. DO NOT EDIT. 2 | 3 | package main 4 | 5 | /* 6 | #cgo CFLAGS: -I../../../target/debug 7 | #cgo LDFLAGS: -L../../../target/debug -lecho_pb -ldl -lm 8 | 9 | #include "echo_pb.h" 10 | */ 11 | import "C" 12 | import ( 13 | "reflect" 14 | "unsafe" 15 | 16 | "github.com/andeya/fcplug/samples/echo_pb" 17 | "github.com/andeya/gust" 18 | ) 19 | 20 | // main function is never called by C to. 21 | func main() {} 22 | 23 | var ( 24 | _ reflect.SliceHeader 25 | _ unsafe.Pointer 26 | ) 27 | var _ gust.EnumResult[any, any] 28 | var _ echo_pb.ResultCode 29 | 30 | var GlobalGoFfi GoFfi = _UnimplementedGoFfi{} 31 | 32 | type GoFfi interface { 33 | EchoGo(req echo_pb.TBytes[echo_pb.Ping]) gust.EnumResult[echo_pb.TBytes[*echo_pb.Pong], ResultMsg] 34 | } 35 | type _UnimplementedGoFfi struct{} 36 | 37 | func (_UnimplementedGoFfi) EchoGo(req echo_pb.TBytes[echo_pb.Ping]) gust.EnumResult[echo_pb.TBytes[*echo_pb.Pong], ResultMsg] { 38 | panic("unimplemented") 39 | } 40 | 41 | type ResultMsg struct { 42 | Code echo_pb.ResultCode 43 | Msg string 44 | } 45 | 46 | //go:inline 47 | func asBuffer[T any](b echo_pb.TBytes[T]) C.struct_Buffer { 48 | p, size := b.ForCBuffer() 49 | if size == 0 { 50 | return C.struct_Buffer{} 51 | } 52 | return C.struct_Buffer{ 53 | ptr: (*C.uint8_t)(p), 54 | len: C.uintptr_t(size), 55 | cap: C.uintptr_t(size), 56 | } 57 | } 58 | 59 | //go:inline 60 | func asBytes[T any](buf C.struct_Buffer) echo_pb.TBytes[T] { 61 | if buf.len == 0 { 62 | return echo_pb.TBytes[T]{} 63 | } 64 | return echo_pb.TBytesFromBytes[T](*(*[]byte)(unsafe.Pointer(&reflect.SliceHeader{ 65 | Data: uintptr(unsafe.Pointer(buf.ptr)), 66 | Len: int(buf.len), 67 | Cap: int(buf.cap), 68 | }))) 69 | } 70 | 71 | //go:inline 72 | //export goffi_echo_go 73 | func goffi_echo_go(req C.struct_Buffer) C.struct_GoFfiResult { 74 | if _EchoGo_Ret := GlobalGoFfi.EchoGo(asBytes[echo_pb.Ping](req)); _EchoGo_Ret.IsOk() { 75 | return C.goffi_echo_go_set_result(asBuffer(_EchoGo_Ret.Unwrap())) 76 | } else { 77 | _EchoGo_Ret_Msg := _EchoGo_Ret.UnwrapErr() 78 | if _EchoGo_Ret_Msg.Code == echo_pb.RcNoError { 79 | _EchoGo_Ret_Msg.Code = echo_pb.RcUnknown 80 | } 81 | return C.struct_GoFfiResult{ 82 | code: C.int8_t(_EchoGo_Ret_Msg.Code), 83 | data_ptr: C.leak_buffer(asBuffer(echo_pb.TBytesFromString[string](_EchoGo_Ret_Msg.Msg))), 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /samples/echo_pb/cgobin/clib_goffi_impl.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/andeya/fcplug/samples/echo_pb" 5 | "github.com/andeya/gust" 6 | ) 7 | 8 | func init() { 9 | // TODO: Replace with your own implementation, then re-execute `cargo build` 10 | GlobalGoFfi = GoFfiImpl{} 11 | } 12 | 13 | type GoFfiImpl struct{} 14 | 15 | func (g GoFfiImpl) EchoGo(req echo_pb.TBytes[echo_pb.Ping]) gust.EnumResult[echo_pb.TBytes[*echo_pb.Pong], ResultMsg] { 16 | ping := req.PbUnmarshalUnchecked() 17 | if ping.Msg != "this is ping from rust" { 18 | panic("ping==============:" + ping.Msg) 19 | } 20 | // fmt.Printf("go receive req: %v\n", req.PbUnmarshalUnchecked()) 21 | return gust.EnumOk[echo_pb.TBytes[*echo_pb.Pong], ResultMsg](echo_pb.TBytesFromPbUnchecked(&echo_pb.Pong{ 22 | Msg: "this is pong from go", 23 | })) 24 | } 25 | -------------------------------------------------------------------------------- /samples/echo_pb/echo.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | message Ping { 4 | string msg = 1; 5 | } 6 | 7 | message Pong { 8 | string msg = 1; 9 | } 10 | 11 | // go call rust 12 | service RustFFI { 13 | rpc echo_rs (Ping) returns (Pong) {} 14 | } 15 | 16 | // rust call go 17 | service GoFFI { 18 | rpc echo_go (Ping) returns (Pong) {} 19 | } 20 | -------------------------------------------------------------------------------- /samples/echo_pb/echo_pb.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // versions: 3 | // protoc-gen-go v1.26.0 4 | // protoc v4.23.2 5 | // source: echo_pb.proto 6 | 7 | package echo_pb 8 | 9 | import ( 10 | protoreflect "google.golang.org/protobuf/reflect/protoreflect" 11 | protoimpl "google.golang.org/protobuf/runtime/protoimpl" 12 | reflect "reflect" 13 | sync "sync" 14 | ) 15 | 16 | const ( 17 | // Verify that this generated code is sufficiently up-to-date. 18 | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) 19 | // Verify that runtime/protoimpl is sufficiently up-to-date. 20 | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) 21 | ) 22 | 23 | type Ping struct { 24 | state protoimpl.MessageState 25 | sizeCache protoimpl.SizeCache 26 | unknownFields protoimpl.UnknownFields 27 | 28 | Msg string `protobuf:"bytes,1,opt,name=msg,proto3" json:"msg,omitempty"` 29 | } 30 | 31 | func (x *Ping) Reset() { 32 | *x = Ping{} 33 | if protoimpl.UnsafeEnabled { 34 | mi := &file_echo_pb_proto_msgTypes[0] 35 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 36 | ms.StoreMessageInfo(mi) 37 | } 38 | } 39 | 40 | func (x *Ping) String() string { 41 | return protoimpl.X.MessageStringOf(x) 42 | } 43 | 44 | func (*Ping) ProtoMessage() {} 45 | 46 | func (x *Ping) ProtoReflect() protoreflect.Message { 47 | mi := &file_echo_pb_proto_msgTypes[0] 48 | if protoimpl.UnsafeEnabled && x != nil { 49 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 50 | if ms.LoadMessageInfo() == nil { 51 | ms.StoreMessageInfo(mi) 52 | } 53 | return ms 54 | } 55 | return mi.MessageOf(x) 56 | } 57 | 58 | // Deprecated: Use Ping.ProtoReflect.Descriptor instead. 59 | func (*Ping) Descriptor() ([]byte, []int) { 60 | return file_echo_pb_proto_rawDescGZIP(), []int{0} 61 | } 62 | 63 | func (x *Ping) GetMsg() string { 64 | if x != nil { 65 | return x.Msg 66 | } 67 | return "" 68 | } 69 | 70 | type Pong struct { 71 | state protoimpl.MessageState 72 | sizeCache protoimpl.SizeCache 73 | unknownFields protoimpl.UnknownFields 74 | 75 | Msg string `protobuf:"bytes,1,opt,name=msg,proto3" json:"msg,omitempty"` 76 | } 77 | 78 | func (x *Pong) Reset() { 79 | *x = Pong{} 80 | if protoimpl.UnsafeEnabled { 81 | mi := &file_echo_pb_proto_msgTypes[1] 82 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 83 | ms.StoreMessageInfo(mi) 84 | } 85 | } 86 | 87 | func (x *Pong) String() string { 88 | return protoimpl.X.MessageStringOf(x) 89 | } 90 | 91 | func (*Pong) ProtoMessage() {} 92 | 93 | func (x *Pong) ProtoReflect() protoreflect.Message { 94 | mi := &file_echo_pb_proto_msgTypes[1] 95 | if protoimpl.UnsafeEnabled && x != nil { 96 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 97 | if ms.LoadMessageInfo() == nil { 98 | ms.StoreMessageInfo(mi) 99 | } 100 | return ms 101 | } 102 | return mi.MessageOf(x) 103 | } 104 | 105 | // Deprecated: Use Pong.ProtoReflect.Descriptor instead. 106 | func (*Pong) Descriptor() ([]byte, []int) { 107 | return file_echo_pb_proto_rawDescGZIP(), []int{1} 108 | } 109 | 110 | func (x *Pong) GetMsg() string { 111 | if x != nil { 112 | return x.Msg 113 | } 114 | return "" 115 | } 116 | 117 | var File_echo_pb_proto protoreflect.FileDescriptor 118 | 119 | var file_echo_pb_proto_rawDesc = []byte{ 120 | 0x0a, 0x0d, 0x65, 0x63, 0x68, 0x6f, 0x5f, 0x70, 0x62, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 121 | 0x07, 0x65, 0x63, 0x68, 0x6f, 0x5f, 0x70, 0x62, 0x22, 0x18, 0x0a, 0x04, 0x50, 0x69, 0x6e, 0x67, 122 | 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x73, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6d, 123 | 0x73, 0x67, 0x22, 0x18, 0x0a, 0x04, 0x50, 0x6f, 0x6e, 0x67, 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x73, 124 | 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6d, 0x73, 0x67, 0x32, 0x34, 0x0a, 0x07, 125 | 0x52, 0x75, 0x73, 0x74, 0x46, 0x46, 0x49, 0x12, 0x29, 0x0a, 0x07, 0x65, 0x63, 0x68, 0x6f, 0x5f, 126 | 0x72, 0x73, 0x12, 0x0d, 0x2e, 0x65, 0x63, 0x68, 0x6f, 0x5f, 0x70, 0x62, 0x2e, 0x50, 0x69, 0x6e, 127 | 0x67, 0x1a, 0x0d, 0x2e, 0x65, 0x63, 0x68, 0x6f, 0x5f, 0x70, 0x62, 0x2e, 0x50, 0x6f, 0x6e, 0x67, 128 | 0x22, 0x00, 0x32, 0x32, 0x0a, 0x05, 0x47, 0x6f, 0x46, 0x46, 0x49, 0x12, 0x29, 0x0a, 0x07, 0x65, 129 | 0x63, 0x68, 0x6f, 0x5f, 0x67, 0x6f, 0x12, 0x0d, 0x2e, 0x65, 0x63, 0x68, 0x6f, 0x5f, 0x70, 0x62, 130 | 0x2e, 0x50, 0x69, 0x6e, 0x67, 0x1a, 0x0d, 0x2e, 0x65, 0x63, 0x68, 0x6f, 0x5f, 0x70, 0x62, 0x2e, 131 | 0x50, 0x6f, 0x6e, 0x67, 0x22, 0x00, 0x42, 0x0c, 0x5a, 0x0a, 0x2e, 0x2f, 0x3b, 0x65, 0x63, 0x68, 132 | 0x6f, 0x5f, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 133 | } 134 | 135 | var ( 136 | file_echo_pb_proto_rawDescOnce sync.Once 137 | file_echo_pb_proto_rawDescData = file_echo_pb_proto_rawDesc 138 | ) 139 | 140 | func file_echo_pb_proto_rawDescGZIP() []byte { 141 | file_echo_pb_proto_rawDescOnce.Do(func() { 142 | file_echo_pb_proto_rawDescData = protoimpl.X.CompressGZIP(file_echo_pb_proto_rawDescData) 143 | }) 144 | return file_echo_pb_proto_rawDescData 145 | } 146 | 147 | var file_echo_pb_proto_msgTypes = make([]protoimpl.MessageInfo, 2) 148 | var file_echo_pb_proto_goTypes = []interface{}{ 149 | (*Ping)(nil), // 0: echo_pb.Ping 150 | (*Pong)(nil), // 1: echo_pb.Pong 151 | } 152 | var file_echo_pb_proto_depIdxs = []int32{ 153 | 0, // 0: echo_pb.RustFFI.echo_rs:input_type -> echo_pb.Ping 154 | 0, // 1: echo_pb.GoFFI.echo_go:input_type -> echo_pb.Ping 155 | 1, // 2: echo_pb.RustFFI.echo_rs:output_type -> echo_pb.Pong 156 | 1, // 3: echo_pb.GoFFI.echo_go:output_type -> echo_pb.Pong 157 | 2, // [2:4] is the sub-list for method output_type 158 | 0, // [0:2] is the sub-list for method input_type 159 | 0, // [0:0] is the sub-list for extension type_name 160 | 0, // [0:0] is the sub-list for extension extendee 161 | 0, // [0:0] is the sub-list for field type_name 162 | } 163 | 164 | func init() { file_echo_pb_proto_init() } 165 | func file_echo_pb_proto_init() { 166 | if File_echo_pb_proto != nil { 167 | return 168 | } 169 | if !protoimpl.UnsafeEnabled { 170 | file_echo_pb_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { 171 | switch v := v.(*Ping); i { 172 | case 0: 173 | return &v.state 174 | case 1: 175 | return &v.sizeCache 176 | case 2: 177 | return &v.unknownFields 178 | default: 179 | return nil 180 | } 181 | } 182 | file_echo_pb_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { 183 | switch v := v.(*Pong); i { 184 | case 0: 185 | return &v.state 186 | case 1: 187 | return &v.sizeCache 188 | case 2: 189 | return &v.unknownFields 190 | default: 191 | return nil 192 | } 193 | } 194 | } 195 | type x struct{} 196 | out := protoimpl.TypeBuilder{ 197 | File: protoimpl.DescBuilder{ 198 | GoPackagePath: reflect.TypeOf(x{}).PkgPath(), 199 | RawDescriptor: file_echo_pb_proto_rawDesc, 200 | NumEnums: 0, 201 | NumMessages: 2, 202 | NumExtensions: 0, 203 | NumServices: 2, 204 | }, 205 | GoTypes: file_echo_pb_proto_goTypes, 206 | DependencyIndexes: file_echo_pb_proto_depIdxs, 207 | MessageInfos: file_echo_pb_proto_msgTypes, 208 | }.Build() 209 | File_echo_pb_proto = out.File 210 | file_echo_pb_proto_rawDesc = nil 211 | file_echo_pb_proto_goTypes = nil 212 | file_echo_pb_proto_depIdxs = nil 213 | } 214 | -------------------------------------------------------------------------------- /samples/echo_pb/echo_pb_gen_darwin_amd64.go: -------------------------------------------------------------------------------- 1 | // Code generated by fcplug. DO NOT EDIT. 2 | 3 | package echo_pb 4 | 5 | /* 6 | #cgo CFLAGS: -I../../target/debug 7 | #cgo LDFLAGS: -L../../target/debug -lecho_pb -ldl -lm 8 | 9 | #include "echo_pb.h" 10 | */ 11 | import "C" 12 | 13 | import ( 14 | "errors" 15 | "fmt" 16 | "reflect" 17 | "unsafe" 18 | 19 | "github.com/andeya/gust/valconv" 20 | "github.com/bytedance/sonic" 21 | "google.golang.org/protobuf/proto" 22 | ) 23 | 24 | var ( 25 | _ = errors.New 26 | _ = fmt.Sprintf 27 | _ reflect.SliceHeader 28 | _ unsafe.Pointer 29 | ) 30 | var _ valconv.ReadonlyBytes 31 | var _ = sonic.Marshal 32 | var _ = proto.Marshal 33 | 34 | var GlobalRustFfi RustFfi = RustFfiImpl{} 35 | 36 | type RustFfi interface { 37 | EchoRs(req TBytes[*Ping]) RustFfiResult[Pong] 38 | } 39 | type RustFfiImpl struct{} 40 | 41 | //go:inline 42 | func (RustFfiImpl) EchoRs(req TBytes[*Ping]) RustFfiResult[Pong] { 43 | return newRustFfiResult[Pong](C.rustffi_echo_rs(req.asBuffer())) 44 | } 45 | 46 | type ResultCode = int8 47 | 48 | const ( 49 | RcNoError ResultCode = 0 50 | RcDecode ResultCode = -1 51 | RcEncode ResultCode = -2 52 | RcUnknown ResultCode = -128 53 | ) 54 | 55 | // TBytes bytes with type marker 56 | type TBytes[T any] struct { 57 | bytes []byte 58 | _nil *T 59 | } 60 | 61 | // TBytesFromBytes new TBytes from bytes 62 | // 63 | //go:inline 64 | func TBytesFromBytes[T any](bytes []byte) TBytes[T] { 65 | return TBytes[T]{bytes: bytes} 66 | } 67 | 68 | // TBytesFromString new TBytes from string 69 | // 70 | //go:inline 71 | func TBytesFromString[T any](s string) TBytes[T] { 72 | return TBytes[T]{bytes: valconv.StringToReadonlyBytes[string](s)} 73 | } 74 | 75 | //go:inline 76 | func TBytesFromPbUnchecked[T proto.Message](obj T) TBytes[T] { 77 | tb, _ := TBytesFromPb[T](obj) 78 | return tb 79 | } 80 | 81 | //go:inline 82 | func TBytesFromPb[T proto.Message](obj T) (TBytes[T], error) { 83 | var tb TBytes[T] 84 | var err error 85 | tb.bytes, err = proto.Marshal(obj) 86 | if err != nil { 87 | return TBytes[T]{}, err 88 | } 89 | return tb, nil 90 | } 91 | 92 | //go:inline 93 | func TBytesFromJsonUnchecked[T proto.Message](obj T) TBytes[T] { 94 | tb, _ := TBytesFromJson[T](obj) 95 | return tb 96 | } 97 | 98 | //go:inline 99 | func TBytesFromJson[T any](obj T) (TBytes[T], error) { 100 | var tb TBytes[T] 101 | var err error 102 | tb.bytes, err = sonic.Marshal(obj) 103 | if err != nil { 104 | return TBytes[T]{}, err 105 | } 106 | return tb, nil 107 | } 108 | 109 | //go:inline 110 | func (b TBytes[T]) Len() int { 111 | return len(b.bytes) 112 | } 113 | 114 | // PbUnmarshal as protobuf to unmarshal 115 | // NOTE: maybe reference Rust memory buffer 116 | // 117 | //go:inline 118 | func (b TBytes[T]) PbUnmarshal() (*T, error) { 119 | var t T 120 | if b.Len() > 0 { 121 | err := proto.Unmarshal(b.bytes, any(&t).(proto.Message)) 122 | if err != nil { 123 | return nil, err 124 | } 125 | } 126 | return &t, nil 127 | } 128 | 129 | // PbUnmarshalUnchecked as protobuf to unmarshal 130 | // NOTE: maybe reference Rust memory buffer 131 | // 132 | //go:inline 133 | func (b TBytes[T]) PbUnmarshalUnchecked() *T { 134 | var t T 135 | if b.Len() > 0 { 136 | _ = proto.Unmarshal(b.bytes, any(&t).(proto.Message)) 137 | } 138 | return &t 139 | } 140 | 141 | // JsonUnmarshal as json to unmarshal 142 | // NOTE: maybe reference Rust memory buffer 143 | // 144 | //go:inline 145 | func (b TBytes[T]) JsonUnmarshal() (*T, error) { 146 | var t T 147 | if b.Len() > 0 { 148 | err := sonic.Unmarshal(b.bytes, &t) 149 | if err != nil { 150 | return nil, err 151 | } 152 | } 153 | return &t, nil 154 | } 155 | 156 | // JsonUnmarshalUnchecked as json to unmarshal 157 | // NOTE: maybe reference Rust memory buffer 158 | // 159 | //go:inline 160 | func (b TBytes[T]) JsonUnmarshalUnchecked() *T { 161 | var t T 162 | if b.Len() > 0 { 163 | _ = sonic.Unmarshal(b.bytes, &t) 164 | } 165 | return &t 166 | } 167 | 168 | // Unmarshal unmarshal to object 169 | // NOTE: maybe reference Rust memory buffer 170 | // 171 | //go:inline 172 | func (b TBytes[T]) Unmarshal(unmarshal func([]byte, any) error) (*T, error) { 173 | var t T 174 | if b.Len() > 0 { 175 | err := unmarshal(b.bytes, &t) 176 | if err != nil { 177 | return nil, err 178 | } 179 | } 180 | return &t, nil 181 | } 182 | 183 | // UnmarshalUnchecked unmarshal to object 184 | // NOTE: maybe reference Rust memory buffer 185 | // 186 | //go:inline 187 | func (b TBytes[T]) UnmarshalUnchecked(unmarshal func([]byte, any) error) *T { 188 | var t T 189 | if b.Len() > 0 { 190 | _ = unmarshal(b.bytes, &t) 191 | } 192 | return &t 193 | } 194 | 195 | //go:inline 196 | func (b TBytes[T]) ForCBuffer() (unsafe.Pointer, int) { 197 | size := len(b.bytes) 198 | if size == 0 { 199 | return nil, 0 200 | } 201 | if cap(b.bytes) > size { 202 | b.bytes = b.bytes[0:size:size] 203 | } 204 | return unsafe.Pointer(&b.bytes[0]), size 205 | } 206 | 207 | //go:inline 208 | func (b TBytes[T]) asBuffer() C.struct_Buffer { 209 | p, size := b.ForCBuffer() 210 | if size == 0 { 211 | return C.struct_Buffer{} 212 | } 213 | return C.struct_Buffer{ 214 | ptr: (*C.uint8_t)(p), 215 | len: C.uintptr_t(size), 216 | cap: C.uintptr_t(size), 217 | } 218 | } 219 | 220 | // CBuffer Rust buffer for Go 221 | type CBuffer struct { 222 | buf C.struct_Buffer 223 | } 224 | 225 | // Free free rust memory buffer, must be called! 226 | // 227 | //go:inline 228 | func (b CBuffer) Free() { 229 | if b.buf.len > 0 { 230 | C.free_buffer(b.buf) 231 | } 232 | } 233 | 234 | //go:inline 235 | func (b CBuffer) Len() int { 236 | return int(b.buf.len) 237 | } 238 | 239 | //go:inline 240 | func (b CBuffer) AsBytes() []byte { 241 | if b.buf.len == 0 { 242 | return nil 243 | } 244 | return *(*[]byte)(unsafe.Pointer(&reflect.SliceHeader{ 245 | Data: uintptr(unsafe.Pointer(b.buf.ptr)), 246 | Len: int(b.buf.len), 247 | Cap: int(b.buf.cap), 248 | })) 249 | } 250 | 251 | //go:inline 252 | func (b CBuffer) AsString() string { 253 | if b.buf.len == 0 { 254 | return "" 255 | } 256 | return valconv.BytesToString[string](b.AsBytes()) 257 | } 258 | 259 | // RustFfiResult Rust FFI Result for Go 260 | // NOTE: must call Free method to free rust memory buffer! 261 | type RustFfiResult[T any] struct { 262 | CBuffer 263 | Code ResultCode 264 | _nil *T 265 | } 266 | 267 | //go:inline 268 | func newRustFfiResult[T any](ret C.struct_RustFfiResult) RustFfiResult[T] { 269 | return RustFfiResult[T]{ 270 | CBuffer: CBuffer{buf: ret.data}, 271 | Code: ResultCode(ret.code), 272 | _nil: nil, 273 | } 274 | } 275 | 276 | //go:inline 277 | func (r RustFfiResult[T]) String() string { 278 | return fmt.Sprintf("Code: %d, CBuffer: %s", r.Code, r.CBuffer.AsString()) 279 | } 280 | 281 | //go:inline 282 | func (r RustFfiResult[T]) IsOk() bool { 283 | return r.Code == RcNoError 284 | } 285 | 286 | // AsError as an error 287 | // NOTE: reference Rust memory buffer 288 | // 289 | //go:inline 290 | func (r RustFfiResult[T]) AsError() error { 291 | if r.Code != RcNoError { 292 | return errors.New(r.AsString()) 293 | } 294 | return nil 295 | } 296 | 297 | // PbUnmarshal as protobuf to unmarshal 298 | // NOTE: maybe reference Rust memory buffer 299 | // 300 | //go:inline 301 | func (r RustFfiResult[T]) PbUnmarshal() (*T, error) { 302 | if err := r.AsError(); err != nil { 303 | return nil, err 304 | } 305 | var t T 306 | if r.Len() > 0 { 307 | err := proto.Unmarshal(r.AsBytes(), any(&t).(proto.Message)) 308 | if err != nil { 309 | return nil, err 310 | } 311 | } 312 | return &t, nil 313 | } 314 | 315 | // PbUnmarshalUnchecked as protobuf to unmarshal 316 | // NOTE: maybe reference Rust memory buffer 317 | // 318 | //go:inline 319 | func (r RustFfiResult[T]) PbUnmarshalUnchecked() *T { 320 | if err := r.AsError(); err != nil { 321 | return nil 322 | } 323 | var t T 324 | if r.Len() > 0 { 325 | _ = proto.Unmarshal(r.AsBytes(), any(&t).(proto.Message)) 326 | } 327 | return &t 328 | } 329 | 330 | // JsonUnmarshal as json to unmarshal 331 | // NOTE: maybe reference Rust memory buffer 332 | // 333 | //go:inline 334 | func (r RustFfiResult[T]) JsonUnmarshal() (*T, error) { 335 | if err := r.AsError(); err != nil { 336 | return nil, err 337 | } 338 | var t T 339 | if r.Len() > 0 { 340 | err := sonic.Unmarshal(r.AsBytes(), &t) 341 | if err != nil { 342 | return nil, err 343 | } 344 | } 345 | return &t, nil 346 | } 347 | 348 | // JsonUnmarshalUnchecked as json to unmarshal 349 | // NOTE: maybe reference Rust memory buffer 350 | // 351 | //go:inline 352 | func (r RustFfiResult[T]) JsonUnmarshalUnchecked() *T { 353 | if err := r.AsError(); err != nil { 354 | return nil 355 | } 356 | var t T 357 | if r.Len() > 0 { 358 | _ = sonic.Unmarshal(r.AsBytes(), &t) 359 | } 360 | return &t 361 | } 362 | 363 | // Unmarshal unmarshal to object 364 | // NOTE: maybe reference Rust memory buffer 365 | // 366 | //go:inline 367 | func (r RustFfiResult[T]) Unmarshal(unmarshal func([]byte, any) error) (*T, error) { 368 | if err := r.AsError(); err != nil { 369 | return nil, err 370 | } 371 | var t T 372 | if r.Len() > 0 { 373 | err := unmarshal(r.AsBytes(), &t) 374 | if err != nil { 375 | return nil, err 376 | } 377 | } 378 | return &t, nil 379 | } 380 | 381 | // UnmarshalUnchecked unmarshal to object 382 | // NOTE: maybe reference Rust memory buffer 383 | // 384 | //go:inline 385 | func (r RustFfiResult[T]) UnmarshalUnchecked(unmarshal func([]byte, any) error) *T { 386 | if err := r.AsError(); err != nil { 387 | return nil 388 | } 389 | var t T 390 | if r.Len() > 0 { 391 | _ = unmarshal(r.AsBytes(), &t) 392 | } 393 | return &t 394 | } 395 | -------------------------------------------------------------------------------- /samples/echo_pb/echo_pb_gen_linux_amd64.go: -------------------------------------------------------------------------------- 1 | // Code generated by fcplug. DO NOT EDIT. 2 | 3 | package echo_pb 4 | 5 | /* 6 | #cgo CFLAGS: -I../../target/debug 7 | #cgo LDFLAGS: -L../../target/debug -lecho_pb -ldl -lm 8 | 9 | #include "echo_pb.h" 10 | */ 11 | import "C" 12 | 13 | import ( 14 | "errors" 15 | "fmt" 16 | "reflect" 17 | "unsafe" 18 | 19 | "github.com/andeya/gust/valconv" 20 | "github.com/bytedance/sonic" 21 | "google.golang.org/protobuf/proto" 22 | ) 23 | 24 | var ( 25 | _ = errors.New 26 | _ = fmt.Sprintf 27 | _ reflect.SliceHeader 28 | _ unsafe.Pointer 29 | ) 30 | var _ valconv.ReadonlyBytes 31 | var _ = sonic.Marshal 32 | var _ = proto.Marshal 33 | 34 | var GlobalRustFfi RustFfi = RustFfiImpl{} 35 | 36 | type RustFfi interface { 37 | EchoRs(req TBytes[*Ping]) RustFfiResult[Pong] 38 | } 39 | type RustFfiImpl struct{} 40 | 41 | //go:inline 42 | func (RustFfiImpl) EchoRs(req TBytes[*Ping]) RustFfiResult[Pong] { 43 | return newRustFfiResult[Pong](C.rustffi_echo_rs(req.asBuffer())) 44 | } 45 | 46 | type ResultCode = int8 47 | 48 | const ( 49 | RcNoError ResultCode = 0 50 | RcDecode ResultCode = -1 51 | RcEncode ResultCode = -2 52 | RcUnknown ResultCode = -128 53 | ) 54 | 55 | // TBytes bytes with type marker 56 | type TBytes[T any] struct { 57 | bytes []byte 58 | _nil *T 59 | } 60 | 61 | // TBytesFromBytes new TBytes from bytes 62 | // 63 | //go:inline 64 | func TBytesFromBytes[T any](bytes []byte) TBytes[T] { 65 | return TBytes[T]{bytes: bytes} 66 | } 67 | 68 | // TBytesFromString new TBytes from string 69 | // 70 | //go:inline 71 | func TBytesFromString[T any](s string) TBytes[T] { 72 | return TBytes[T]{bytes: valconv.StringToReadonlyBytes[string](s)} 73 | } 74 | 75 | //go:inline 76 | func TBytesFromPbUnchecked[T proto.Message](obj T) TBytes[T] { 77 | tb, _ := TBytesFromPb[T](obj) 78 | return tb 79 | } 80 | 81 | //go:inline 82 | func TBytesFromPb[T proto.Message](obj T) (TBytes[T], error) { 83 | var tb TBytes[T] 84 | var err error 85 | tb.bytes, err = proto.Marshal(obj) 86 | if err != nil { 87 | return TBytes[T]{}, err 88 | } 89 | return tb, nil 90 | } 91 | 92 | //go:inline 93 | func TBytesFromJsonUnchecked[T proto.Message](obj T) TBytes[T] { 94 | tb, _ := TBytesFromJson[T](obj) 95 | return tb 96 | } 97 | 98 | //go:inline 99 | func TBytesFromJson[T any](obj T) (TBytes[T], error) { 100 | var tb TBytes[T] 101 | var err error 102 | tb.bytes, err = sonic.Marshal(obj) 103 | if err != nil { 104 | return TBytes[T]{}, err 105 | } 106 | return tb, nil 107 | } 108 | 109 | //go:inline 110 | func (b TBytes[T]) Len() int { 111 | return len(b.bytes) 112 | } 113 | 114 | // PbUnmarshal as protobuf to unmarshal 115 | // NOTE: maybe reference Rust memory buffer 116 | // 117 | //go:inline 118 | func (b TBytes[T]) PbUnmarshal() (*T, error) { 119 | var t T 120 | if b.Len() > 0 { 121 | err := proto.Unmarshal(b.bytes, any(&t).(proto.Message)) 122 | if err != nil { 123 | return nil, err 124 | } 125 | } 126 | return &t, nil 127 | } 128 | 129 | // PbUnmarshalUnchecked as protobuf to unmarshal 130 | // NOTE: maybe reference Rust memory buffer 131 | // 132 | //go:inline 133 | func (b TBytes[T]) PbUnmarshalUnchecked() *T { 134 | var t T 135 | if b.Len() > 0 { 136 | _ = proto.Unmarshal(b.bytes, any(&t).(proto.Message)) 137 | } 138 | return &t 139 | } 140 | 141 | // JsonUnmarshal as json to unmarshal 142 | // NOTE: maybe reference Rust memory buffer 143 | // 144 | //go:inline 145 | func (b TBytes[T]) JsonUnmarshal() (*T, error) { 146 | var t T 147 | if b.Len() > 0 { 148 | err := sonic.Unmarshal(b.bytes, &t) 149 | if err != nil { 150 | return nil, err 151 | } 152 | } 153 | return &t, nil 154 | } 155 | 156 | // JsonUnmarshalUnchecked as json to unmarshal 157 | // NOTE: maybe reference Rust memory buffer 158 | // 159 | //go:inline 160 | func (b TBytes[T]) JsonUnmarshalUnchecked() *T { 161 | var t T 162 | if b.Len() > 0 { 163 | _ = sonic.Unmarshal(b.bytes, &t) 164 | } 165 | return &t 166 | } 167 | 168 | // Unmarshal unmarshal to object 169 | // NOTE: maybe reference Rust memory buffer 170 | // 171 | //go:inline 172 | func (b TBytes[T]) Unmarshal(unmarshal func([]byte, any) error) (*T, error) { 173 | var t T 174 | if b.Len() > 0 { 175 | err := unmarshal(b.bytes, &t) 176 | if err != nil { 177 | return nil, err 178 | } 179 | } 180 | return &t, nil 181 | } 182 | 183 | // UnmarshalUnchecked unmarshal to object 184 | // NOTE: maybe reference Rust memory buffer 185 | // 186 | //go:inline 187 | func (b TBytes[T]) UnmarshalUnchecked(unmarshal func([]byte, any) error) *T { 188 | var t T 189 | if b.Len() > 0 { 190 | _ = unmarshal(b.bytes, &t) 191 | } 192 | return &t 193 | } 194 | 195 | //go:inline 196 | func (b TBytes[T]) ForCBuffer() (unsafe.Pointer, int) { 197 | size := len(b.bytes) 198 | if size == 0 { 199 | return nil, 0 200 | } 201 | if cap(b.bytes) > size { 202 | b.bytes = b.bytes[0:size:size] 203 | } 204 | return unsafe.Pointer(&b.bytes[0]), size 205 | } 206 | 207 | //go:inline 208 | func (b TBytes[T]) asBuffer() C.struct_Buffer { 209 | p, size := b.ForCBuffer() 210 | if size == 0 { 211 | return C.struct_Buffer{} 212 | } 213 | return C.struct_Buffer{ 214 | ptr: (*C.uint8_t)(p), 215 | len: C.uintptr_t(size), 216 | cap: C.uintptr_t(size), 217 | } 218 | } 219 | 220 | // CBuffer Rust buffer for Go 221 | type CBuffer struct { 222 | buf C.struct_Buffer 223 | } 224 | 225 | // Free free rust memory buffer, must be called! 226 | // 227 | //go:inline 228 | func (b CBuffer) Free() { 229 | if b.buf.len > 0 { 230 | C.free_buffer(b.buf) 231 | } 232 | } 233 | 234 | //go:inline 235 | func (b CBuffer) Len() int { 236 | return int(b.buf.len) 237 | } 238 | 239 | //go:inline 240 | func (b CBuffer) AsBytes() []byte { 241 | if b.buf.len == 0 { 242 | return nil 243 | } 244 | return *(*[]byte)(unsafe.Pointer(&reflect.SliceHeader{ 245 | Data: uintptr(unsafe.Pointer(b.buf.ptr)), 246 | Len: int(b.buf.len), 247 | Cap: int(b.buf.cap), 248 | })) 249 | } 250 | 251 | //go:inline 252 | func (b CBuffer) AsString() string { 253 | if b.buf.len == 0 { 254 | return "" 255 | } 256 | return valconv.BytesToString[string](b.AsBytes()) 257 | } 258 | 259 | // RustFfiResult Rust FFI Result for Go 260 | // NOTE: must call Free method to free rust memory buffer! 261 | type RustFfiResult[T any] struct { 262 | CBuffer 263 | Code ResultCode 264 | _nil *T 265 | } 266 | 267 | //go:inline 268 | func newRustFfiResult[T any](ret C.struct_RustFfiResult) RustFfiResult[T] { 269 | return RustFfiResult[T]{ 270 | CBuffer: CBuffer{buf: ret.data}, 271 | Code: ResultCode(ret.code), 272 | _nil: nil, 273 | } 274 | } 275 | 276 | //go:inline 277 | func (r RustFfiResult[T]) String() string { 278 | return fmt.Sprintf("Code: %d, CBuffer: %s", r.Code, r.CBuffer.AsString()) 279 | } 280 | 281 | //go:inline 282 | func (r RustFfiResult[T]) IsOk() bool { 283 | return r.Code == RcNoError 284 | } 285 | 286 | // AsError as an error 287 | // NOTE: reference Rust memory buffer 288 | // 289 | //go:inline 290 | func (r RustFfiResult[T]) AsError() error { 291 | if r.Code != RcNoError { 292 | return errors.New(r.AsString()) 293 | } 294 | return nil 295 | } 296 | 297 | // PbUnmarshal as protobuf to unmarshal 298 | // NOTE: maybe reference Rust memory buffer 299 | // 300 | //go:inline 301 | func (r RustFfiResult[T]) PbUnmarshal() (*T, error) { 302 | if err := r.AsError(); err != nil { 303 | return nil, err 304 | } 305 | var t T 306 | if r.Len() > 0 { 307 | err := proto.Unmarshal(r.AsBytes(), any(&t).(proto.Message)) 308 | if err != nil { 309 | return nil, err 310 | } 311 | } 312 | return &t, nil 313 | } 314 | 315 | // PbUnmarshalUnchecked as protobuf to unmarshal 316 | // NOTE: maybe reference Rust memory buffer 317 | // 318 | //go:inline 319 | func (r RustFfiResult[T]) PbUnmarshalUnchecked() *T { 320 | if err := r.AsError(); err != nil { 321 | return nil 322 | } 323 | var t T 324 | if r.Len() > 0 { 325 | _ = proto.Unmarshal(r.AsBytes(), any(&t).(proto.Message)) 326 | } 327 | return &t 328 | } 329 | 330 | // JsonUnmarshal as json to unmarshal 331 | // NOTE: maybe reference Rust memory buffer 332 | // 333 | //go:inline 334 | func (r RustFfiResult[T]) JsonUnmarshal() (*T, error) { 335 | if err := r.AsError(); err != nil { 336 | return nil, err 337 | } 338 | var t T 339 | if r.Len() > 0 { 340 | err := sonic.Unmarshal(r.AsBytes(), &t) 341 | if err != nil { 342 | return nil, err 343 | } 344 | } 345 | return &t, nil 346 | } 347 | 348 | // JsonUnmarshalUnchecked as json to unmarshal 349 | // NOTE: maybe reference Rust memory buffer 350 | // 351 | //go:inline 352 | func (r RustFfiResult[T]) JsonUnmarshalUnchecked() *T { 353 | if err := r.AsError(); err != nil { 354 | return nil 355 | } 356 | var t T 357 | if r.Len() > 0 { 358 | _ = sonic.Unmarshal(r.AsBytes(), &t) 359 | } 360 | return &t 361 | } 362 | 363 | // Unmarshal unmarshal to object 364 | // NOTE: maybe reference Rust memory buffer 365 | // 366 | //go:inline 367 | func (r RustFfiResult[T]) Unmarshal(unmarshal func([]byte, any) error) (*T, error) { 368 | if err := r.AsError(); err != nil { 369 | return nil, err 370 | } 371 | var t T 372 | if r.Len() > 0 { 373 | err := unmarshal(r.AsBytes(), &t) 374 | if err != nil { 375 | return nil, err 376 | } 377 | } 378 | return &t, nil 379 | } 380 | 381 | // UnmarshalUnchecked unmarshal to object 382 | // NOTE: maybe reference Rust memory buffer 383 | // 384 | //go:inline 385 | func (r RustFfiResult[T]) UnmarshalUnchecked(unmarshal func([]byte, any) error) *T { 386 | if err := r.AsError(); err != nil { 387 | return nil 388 | } 389 | var t T 390 | if r.Len() > 0 { 391 | _ = unmarshal(r.AsBytes(), &t) 392 | } 393 | return &t 394 | } 395 | -------------------------------------------------------------------------------- /samples/echo_pb/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/andeya/fcplug/samples/echo_pb 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/andeya/gust v1.5.2 7 | github.com/bytedance/sonic v1.11.3 8 | google.golang.org/protobuf v1.26.0 9 | ) 10 | 11 | require ( 12 | github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect 13 | github.com/chenzhuoyu/iasm v0.9.0 // indirect 14 | github.com/klauspost/cpuid/v2 v2.0.9 // indirect 15 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect 16 | golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect 17 | ) 18 | -------------------------------------------------------------------------------- /samples/echo_pb/go_call_rust_test.go: -------------------------------------------------------------------------------- 1 | package echo_pb_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/andeya/fcplug/samples/echo_pb" 7 | ) 8 | 9 | func TestEcho(t *testing.T) { 10 | ret := echo_pb.GlobalRustFfi.EchoRs(echo_pb.TBytesFromPbUnchecked[*echo_pb.Ping](&echo_pb.Ping{ 11 | Msg: "this is ping from go", 12 | })) 13 | if ret.IsOk() { 14 | t.Logf("%#v", ret.PbUnmarshalUnchecked()) 15 | } else { 16 | t.Logf("fail: err=%v", ret.AsError()) 17 | } 18 | ret.Free() 19 | } 20 | 21 | func BenchmarkEcho(b *testing.B) { 22 | args := echo_pb.TBytesFromPbUnchecked[*echo_pb.Ping](&echo_pb.Ping{ 23 | Msg: "this is ping from go", 24 | }) 25 | b.ResetTimer() 26 | for i := 0; i < b.N; i++ { 27 | ret := echo_pb.GlobalRustFfi.EchoRs(args) 28 | if ret.IsOk() { 29 | _ = ret.AsBytes() 30 | } else { 31 | b.Logf("fail: err=%v", ret.AsError()) 32 | return 33 | } 34 | ret.Free() 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /samples/echo_pb/src/echo_pb_ffi/echo_pb_gen.rs: -------------------------------------------------------------------------------- 1 | // Code generated by fcplug. DO NOT EDIT. 2 | #![allow(warnings, clippy::all)] 3 | #[derive( 4 | PartialOrd, 5 | Hash, 6 | Eq, 7 | Ord, 8 | Debug, 9 | Default, 10 | ::serde::Serialize, 11 | ::serde::Deserialize, 12 | Clone, 13 | PartialEq, 14 | )] 15 | pub struct Ping { 16 | pub msg: ::std::string::String, 17 | } 18 | impl ::pilota::prost::Message for Ping { 19 | #[inline] 20 | fn encoded_len(&self) -> usize { 21 | 0 + ::pilota::prost::encoding::string::encoded_len(1, &self.msg) 22 | } 23 | 24 | #[allow(unused_variables)] 25 | fn encode_raw(&self, buf: &mut B) 26 | where 27 | B: ::pilota::prost::bytes::BufMut, 28 | { 29 | ::pilota::prost::encoding::string::encode(1, &self.msg, buf); 30 | } 31 | 32 | #[allow(unused_variables)] 33 | fn merge_field( 34 | &mut self, 35 | tag: u32, 36 | wire_type: ::pilota::prost::encoding::WireType, 37 | buf: &mut B, 38 | ctx: ::pilota::prost::encoding::DecodeContext, 39 | ) -> ::core::result::Result<(), ::pilota::prost::DecodeError> 40 | where 41 | B: ::pilota::prost::bytes::Buf, 42 | { 43 | const STRUCT_NAME: &'static str = stringify!(Ping); 44 | match tag { 45 | 1 => { 46 | let mut _inner_pilota_value = &mut self.msg; 47 | ::pilota::prost::encoding::string::merge(wire_type, _inner_pilota_value, buf, ctx) 48 | .map_err(|mut error| { 49 | error.push(STRUCT_NAME, stringify!(msg)); 50 | error 51 | }) 52 | } 53 | _ => ::pilota::prost::encoding::skip_field(wire_type, tag, buf, ctx), 54 | } 55 | } 56 | } 57 | 58 | pub(super) trait RustFfi { 59 | fn echo_rs(req: ::fcplug::RustFfiArg) -> ::fcplug::ABIResult<::fcplug::TBytes>; 60 | } 61 | #[no_mangle] 62 | #[inline] 63 | pub extern "C" fn rustffi_echo_rs(req: ::fcplug::Buffer) -> ::fcplug::RustFfiResult { 64 | ::fcplug::RustFfiResult::from(::echo_rs(::fcplug::RustFfiArg::from( 65 | req, 66 | ))) 67 | } 68 | #[derive( 69 | PartialOrd, 70 | Hash, 71 | Eq, 72 | Ord, 73 | Debug, 74 | Default, 75 | ::serde::Serialize, 76 | ::serde::Deserialize, 77 | Clone, 78 | PartialEq, 79 | )] 80 | pub struct Pong { 81 | pub msg: ::std::string::String, 82 | } 83 | impl ::pilota::prost::Message for Pong { 84 | #[inline] 85 | fn encoded_len(&self) -> usize { 86 | 0 + ::pilota::prost::encoding::string::encoded_len(1, &self.msg) 87 | } 88 | 89 | #[allow(unused_variables)] 90 | fn encode_raw(&self, buf: &mut B) 91 | where 92 | B: ::pilota::prost::bytes::BufMut, 93 | { 94 | ::pilota::prost::encoding::string::encode(1, &self.msg, buf); 95 | } 96 | 97 | #[allow(unused_variables)] 98 | fn merge_field( 99 | &mut self, 100 | tag: u32, 101 | wire_type: ::pilota::prost::encoding::WireType, 102 | buf: &mut B, 103 | ctx: ::pilota::prost::encoding::DecodeContext, 104 | ) -> ::core::result::Result<(), ::pilota::prost::DecodeError> 105 | where 106 | B: ::pilota::prost::bytes::Buf, 107 | { 108 | const STRUCT_NAME: &'static str = stringify!(Pong); 109 | match tag { 110 | 1 => { 111 | let mut _inner_pilota_value = &mut self.msg; 112 | ::pilota::prost::encoding::string::merge(wire_type, _inner_pilota_value, buf, ctx) 113 | .map_err(|mut error| { 114 | error.push(STRUCT_NAME, stringify!(msg)); 115 | error 116 | }) 117 | } 118 | _ => ::pilota::prost::encoding::skip_field(wire_type, tag, buf, ctx), 119 | } 120 | } 121 | } 122 | 123 | pub(super) trait GoFfi { 124 | unsafe fn echo_go_set_result(go_ret: ::fcplug::RustFfiArg) -> ::fcplug::GoFfiResult; 125 | } 126 | 127 | pub trait GoFfiCall { 128 | unsafe fn echo_go(mut req: ::fcplug::TBytes) -> ::fcplug::ABIResult { 129 | ::fcplug::ABIResult::from(goffi_echo_go(::fcplug::Buffer::from_vec_mut( 130 | &mut req.bytes, 131 | ))) 132 | } 133 | } 134 | 135 | #[link(name = "go_echo_pb", kind = "static")] 136 | extern "C" { 137 | fn goffi_echo_go(req: ::fcplug::Buffer) -> ::fcplug::GoFfiResult; 138 | } 139 | #[no_mangle] 140 | #[inline] 141 | pub extern "C" fn goffi_echo_go_set_result(buf: ::fcplug::Buffer) -> ::fcplug::GoFfiResult { 142 | unsafe { ::echo_go_set_result(::fcplug::RustFfiArg::from(buf)) } 143 | } 144 | trait Ffi: RustFfi + GoFfi + GoFfiCall {} 145 | 146 | pub struct FfiImpl; 147 | 148 | impl GoFfiCall for FfiImpl {} 149 | impl Ffi for FfiImpl {} 150 | -------------------------------------------------------------------------------- /samples/echo_pb/src/echo_pb_ffi/mod.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_variables)] 2 | 3 | pub use echo_pb_gen::*; 4 | use fcplug::protobuf::PbMessage; 5 | use fcplug::{GoFfiResult, TryIntoTBytes}; 6 | 7 | mod echo_pb_gen; 8 | 9 | impl RustFfi for FfiImpl { 10 | fn echo_rs(mut req: ::fcplug::RustFfiArg) -> ::fcplug::ABIResult<::fcplug::TBytes> { 11 | let _req = req.try_to_object::>(); 12 | #[cfg(debug_assertions)] 13 | println!("rust receive req: {:?}", _req); 14 | Pong { 15 | msg: "this is pong from rust".to_string(), 16 | } 17 | .try_into_tbytes::>() 18 | } 19 | } 20 | 21 | impl GoFfi for FfiImpl { 22 | #[allow(unused_mut)] 23 | unsafe fn echo_go_set_result(mut go_ret: ::fcplug::RustFfiArg) -> ::fcplug::GoFfiResult { 24 | return GoFfiResult::from_ok(go_ret.try_to_object::>()?); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /samples/echo_pb/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(test)] 2 | 3 | extern crate test; 4 | 5 | pub mod echo_pb_ffi; 6 | 7 | 8 | #[cfg(test)] 9 | mod tests { 10 | use test::Bencher; 11 | 12 | use fcplug::protobuf::PbMessage; 13 | use fcplug::TryIntoTBytes; 14 | 15 | use crate::echo_pb_ffi::{FfiImpl, GoFfiCall, Ping, Pong}; 16 | 17 | #[test] 18 | fn test_call_echo_go() { 19 | let pong = unsafe { 20 | FfiImpl::echo_go::(Ping { 21 | msg: "this is ping from rust".to_string(), 22 | }.try_into_tbytes::>().unwrap()) 23 | }; 24 | println!("{:?}", pong); 25 | } 26 | 27 | #[bench] 28 | fn bench_call_echo_go(b: &mut Bencher) { 29 | let req = Ping { 30 | msg: "this is ping from rust".to_string(), 31 | } 32 | .try_into_tbytes::>() 33 | .unwrap(); 34 | b.iter(|| { 35 | let pong = unsafe { FfiImpl::echo_go::>(req.clone()) }; 36 | let _ = test::black_box(pong); 37 | }); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /samples/echo_pb/src/main.rs: -------------------------------------------------------------------------------- 1 | use echo_pb::echo_pb_ffi::{FfiImpl, GoFfiCall, Ping, Pong}; 2 | use fcplug::protobuf::PbMessage; 3 | use fcplug::TryIntoTBytes; 4 | 5 | fn main() { 6 | for i in 0..1000000 { 7 | println!("i={i}"); 8 | let pong = unsafe { 9 | FfiImpl::echo_go::( 10 | Ping { 11 | msg: "this is ping from rust".to_string(), 12 | } 13 | .try_into_tbytes::>() 14 | .unwrap(), 15 | ) 16 | }; 17 | let pong = pong.unwrap(); 18 | if pong.msg != "this is pong from go" { 19 | panic!("pong==============:{pong:?}") 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /samples/echo_thrift/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "echo_thrift" 3 | edition.workspace = true 4 | version.workspace = true 5 | authors.workspace = true 6 | description.workspace = true 7 | repository.workspace = true 8 | license.workspace = true 9 | readme.workspace = true 10 | documentation.workspace = true 11 | keywords.workspace = true 12 | categories.workspace = true 13 | 14 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 15 | 16 | [lib] 17 | crate-type = ["rlib", "staticlib"] 18 | 19 | [dependencies] 20 | fcplug = "0.3" 21 | pilota = "0.7.0" 22 | serde = "1" 23 | serde_json = "1" 24 | 25 | [build-dependencies] 26 | fcplug-build = "0.3" 27 | -------------------------------------------------------------------------------- /samples/echo_thrift/README.md: -------------------------------------------------------------------------------- 1 | # echo_thrift 2 | 3 | Thrift IDL codec sample. 4 | 5 | NOTE: In development! 6 | -------------------------------------------------------------------------------- /samples/echo_thrift/build.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_imports)] 2 | 3 | use fcplug_build::{generate_code, Config, UnitLikeStructPath}; 4 | 5 | fn main() { 6 | generate_code(Config { 7 | idl_file: "./echo.thrift".into(), 8 | go_root_path: None, 9 | go_mod_parent: "github.com/andeya/fcplug/samples", 10 | target_crate_dir: None, 11 | add_clib_to_git: false, 12 | }); 13 | } 14 | -------------------------------------------------------------------------------- /samples/echo_thrift/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cargo build --release 4 | cargo build --release 5 | cargo build --release 6 | -------------------------------------------------------------------------------- /samples/echo_thrift/cgobin/clib_goffi_gen.go: -------------------------------------------------------------------------------- 1 | // Code generated by fcplug. DO NOT EDIT. 2 | 3 | package main 4 | 5 | /* 6 | #cgo CFLAGS: -I/Users/henrylee2cn/rust/fcplug/target/debug 7 | #cgo LDFLAGS: -L/Users/henrylee2cn/rust/fcplug/target/debug -lecho_thrift 8 | 9 | #include "echo_thrift.h" 10 | */ 11 | import "C" 12 | import ( 13 | "reflect" 14 | "unsafe" 15 | 16 | "github.com/andeya/fcplug/samples/echo_thrift" 17 | "github.com/andeya/gust" 18 | ) 19 | 20 | // main function is never called by C to. 21 | func main() {} 22 | 23 | var ( 24 | _ reflect.SliceHeader 25 | _ unsafe.Pointer 26 | _ gust.EnumResult[any, any] 27 | _ echo_thrift.ResultCode 28 | ) 29 | 30 | var GlobalGoFfi GoFfi = _UnimplementedGoFfi{} 31 | 32 | type ResultMsg struct { 33 | Code echo_thrift.ResultCode 34 | Msg string 35 | } 36 | 37 | //go:inline 38 | func asBuffer[T any](b echo_thrift.TBytes[T]) C.struct_Buffer { 39 | p, size := b.ForCBuffer() 40 | if size == 0 { 41 | return C.struct_Buffer{} 42 | } 43 | return C.struct_Buffer{ 44 | ptr: (*C.uint8_t)(p), 45 | len: C.uintptr_t(size), 46 | cap: C.uintptr_t(size), 47 | } 48 | } 49 | 50 | //go:inline 51 | func asBytes[T any](buf C.struct_Buffer) echo_thrift.TBytes[T] { 52 | if buf.len == 0 { 53 | return echo_thrift.TBytes[T]{} 54 | } 55 | return echo_thrift.TBytesFromBytes[T](*(*[]byte)(unsafe.Pointer(&reflect.SliceHeader{ 56 | Data: uintptr(unsafe.Pointer(buf.ptr)), 57 | Len: int(buf.len), 58 | Cap: int(buf.cap), 59 | }))) 60 | } 61 | 62 | type GoFfi interface { 63 | EchoGo(req echo_thrift.TBytes[echo_thrift.Ping]) gust.EnumResult[echo_thrift.TBytes[*echo_thrift.Pong], ResultMsg] 64 | } 65 | type _UnimplementedGoFfi struct{} 66 | 67 | func (_UnimplementedGoFfi) EchoGo(req echo_thrift.TBytes[echo_thrift.Ping]) gust.EnumResult[echo_thrift.TBytes[*echo_thrift.Pong], ResultMsg] { 68 | panic("unimplemented") 69 | } 70 | 71 | //go:inline 72 | //export goffi_echo_go 73 | func goffi_echo_go(req C.struct_Buffer) C.struct_GoFfiResult { 74 | if _EchoGo_Ret := GlobalGoFfi.EchoGo(asBytes[echo_thrift.Ping](req)); _EchoGo_Ret.IsOk() { 75 | return C.goffi_echo_go_set_result(asBuffer(_EchoGo_Ret.Unwrap())) 76 | } else { 77 | _EchoGo_Ret_Msg := _EchoGo_Ret.UnwrapErr() 78 | if _EchoGo_Ret_Msg.Code == echo_thrift.RcNoError { 79 | _EchoGo_Ret_Msg.Code = echo_thrift.RcUnknown 80 | } 81 | return C.struct_GoFfiResult{ 82 | code: C.int8_t(_EchoGo_Ret_Msg.Code), 83 | data_ptr: C.leak_buffer(asBuffer(echo_thrift.TBytesFromString[string](_EchoGo_Ret_Msg.Msg))), 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /samples/echo_thrift/cgobin/clib_goffi_impl.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/andeya/fcplug/samples/echo_thrift" 5 | "github.com/andeya/gust" 6 | ) 7 | 8 | func init() { 9 | // TODO: Replace with your own implementation, then re-execute `cargo build` 10 | GlobalGoFfi = GoFfiImpl{} 11 | } 12 | 13 | type GoFfiImpl struct{} 14 | 15 | func (g GoFfiImpl) EchoGo(req echo_thrift.TBytes[echo_thrift.Ping]) gust.EnumResult[echo_thrift.TBytes[*echo_thrift.Pong], ResultMsg] { 16 | _ = req.PbUnmarshalUnchecked() 17 | // fmt.Printf("go receive req: %v\n", req.PbUnmarshalUnchecked()) 18 | return gust.EnumOk[echo_thrift.TBytes[*echo_thrift.Pong], ResultMsg](echo_thrift.TBytesFromPbUnchecked(&echo_thrift.Pong{ 19 | Msg: "this is pong from go", 20 | })) 21 | } 22 | -------------------------------------------------------------------------------- /samples/echo_thrift/echo.thrift: -------------------------------------------------------------------------------- 1 | struct Ping { 2 | 1: string msg, 3 | } 4 | 5 | struct Pong { 6 | 1: string msg, 7 | } 8 | 9 | service rustFFI { 10 | Pong echo_rs (1: Ping req), 11 | } 12 | 13 | service goFFI { 14 | Pong echo_go (1: Ping req), 15 | } 16 | -------------------------------------------------------------------------------- /samples/echo_thrift/echo_thrift_gen.go: -------------------------------------------------------------------------------- 1 | // Code generated by fcplug. DO NOT EDIT. 2 | 3 | package echo_thrift 4 | 5 | /* 6 | #cgo CFLAGS: -I/Users/henrylee2cn/rust/fcplug/target/debug 7 | #cgo LDFLAGS: -L/Users/henrylee2cn/rust/fcplug/target/debug -lecho_thrift 8 | 9 | #include "echo_thrift.h" 10 | */ 11 | import "C" 12 | 13 | import ( 14 | "errors" 15 | "fmt" 16 | "reflect" 17 | "unsafe" 18 | 19 | "github.com/andeya/gust/valconv" 20 | "github.com/bytedance/sonic" 21 | "google.golang.org/protobuf/proto" 22 | ) 23 | 24 | var ( 25 | _ = errors.New 26 | _ = fmt.Sprintf 27 | _ reflect.SliceHeader 28 | _ unsafe.Pointer 29 | _ valconv.ReadonlyBytes 30 | _ = sonic.Marshal 31 | _ = proto.Marshal 32 | ) 33 | 34 | var GlobalRustFfi RustFfi = RustFfiImpl{} 35 | 36 | type ResultCode = int8 37 | 38 | const ( 39 | RcNoError ResultCode = 0 40 | RcDecode ResultCode = -1 41 | RcEncode ResultCode = -2 42 | RcUnknown ResultCode = -128 43 | ) 44 | 45 | // TBytes bytes with type marker 46 | type TBytes[T any] struct { 47 | bytes []byte 48 | _nil *T 49 | } 50 | 51 | // TBytesFromBytes new TBytes from bytes 52 | //go:inline 53 | func TBytesFromBytes[T any](bytes []byte) TBytes[T] { 54 | return TBytes[T]{bytes: bytes} 55 | } 56 | 57 | // TBytesFromString new TBytes from string 58 | //go:inline 59 | func TBytesFromString[T any](s string) TBytes[T] { 60 | return TBytes[T]{bytes: valconv.StringToReadonlyBytes[string](s)} 61 | } 62 | 63 | //go:inline 64 | func TBytesFromPbUnchecked[T proto.Message](obj T) TBytes[T] { 65 | tb, _ := TBytesFromPb[T](obj) 66 | return tb 67 | } 68 | 69 | //go:inline 70 | func TBytesFromPb[T proto.Message](obj T) (TBytes[T], error) { 71 | var tb TBytes[T] 72 | var err error 73 | tb.bytes, err = proto.Marshal(obj) 74 | if err != nil { 75 | return TBytes[T]{}, err 76 | } 77 | return tb, nil 78 | } 79 | 80 | //go:inline 81 | func TBytesFromJsonUnchecked[T proto.Message](obj T) TBytes[T] { 82 | tb, _ := TBytesFromJson[T](obj) 83 | return tb 84 | } 85 | 86 | //go:inline 87 | func TBytesFromJson[T any](obj T) (TBytes[T], error) { 88 | var tb TBytes[T] 89 | var err error 90 | tb.bytes, err = sonic.Marshal(obj) 91 | if err != nil { 92 | return TBytes[T]{}, err 93 | } 94 | return tb, nil 95 | } 96 | 97 | //go:inline 98 | func (b TBytes[T]) Len() int { 99 | return len(b.bytes) 100 | } 101 | 102 | // PbUnmarshal as protobuf to unmarshal 103 | // NOTE: maybe reference Rust memory buffer 104 | // 105 | //go:inline 106 | func (b TBytes[T]) PbUnmarshal() (*T, error) { 107 | var t T 108 | if b.Len() > 0 { 109 | err := proto.Unmarshal(b.bytes, any(&t).(proto.Message)) 110 | if err != nil { 111 | return nil, err 112 | } 113 | } 114 | return &t, nil 115 | } 116 | 117 | // PbUnmarshalUnchecked as protobuf to unmarshal 118 | // NOTE: maybe reference Rust memory buffer 119 | // 120 | //go:inline 121 | func (b TBytes[T]) PbUnmarshalUnchecked() *T { 122 | var t T 123 | if b.Len() > 0 { 124 | _ = proto.Unmarshal(b.bytes, any(&t).(proto.Message)) 125 | } 126 | return &t 127 | } 128 | 129 | // JsonUnmarshal as json to unmarshal 130 | // NOTE: maybe reference Rust memory buffer 131 | // 132 | //go:inline 133 | func (b TBytes[T]) JsonUnmarshal() (*T, error) { 134 | var t T 135 | if b.Len() > 0 { 136 | err := sonic.Unmarshal(b.bytes, &t) 137 | if err != nil { 138 | return nil, err 139 | } 140 | } 141 | return &t, nil 142 | } 143 | 144 | // JsonUnmarshalUnchecked as json to unmarshal 145 | // NOTE: maybe reference Rust memory buffer 146 | // 147 | //go:inline 148 | func (b TBytes[T]) JsonUnmarshalUnchecked() *T { 149 | var t T 150 | if b.Len() > 0 { 151 | _ = sonic.Unmarshal(b.bytes, &t) 152 | } 153 | return &t 154 | } 155 | 156 | // Unmarshal unmarshal to object 157 | // NOTE: maybe reference Rust memory buffer 158 | // 159 | //go:inline 160 | func (b TBytes[T]) Unmarshal(unmarshal func([]byte, any) error) (*T, error) { 161 | var t T 162 | if b.Len() > 0 { 163 | err := unmarshal(b.bytes, &t) 164 | if err != nil { 165 | return nil, err 166 | } 167 | } 168 | return &t, nil 169 | } 170 | 171 | // UnmarshalUnchecked unmarshal to object 172 | // NOTE: maybe reference Rust memory buffer 173 | // 174 | //go:inline 175 | func (b TBytes[T]) UnmarshalUnchecked(unmarshal func([]byte, any) error) *T { 176 | var t T 177 | if b.Len() > 0 { 178 | _ = unmarshal(b.bytes, &t) 179 | } 180 | return &t 181 | } 182 | 183 | //go:inline 184 | func (b TBytes[T]) ForCBuffer() (unsafe.Pointer, int) { 185 | size := len(b.bytes) 186 | if size == 0 { 187 | return nil, 0 188 | } 189 | if cap(b.bytes) > size { 190 | b.bytes = b.bytes[0:size:size] 191 | } 192 | return unsafe.Pointer(&b.bytes[0]), size 193 | } 194 | 195 | //go:inline 196 | func (b TBytes[T]) asBuffer() C.struct_Buffer { 197 | p, size := b.ForCBuffer() 198 | if size == 0 { 199 | return C.struct_Buffer{} 200 | } 201 | return C.struct_Buffer{ 202 | ptr: (*C.uint8_t)(p), 203 | len: C.uintptr_t(size), 204 | cap: C.uintptr_t(size), 205 | } 206 | } 207 | 208 | // CBuffer Rust buffer for Go 209 | type CBuffer struct { 210 | buf C.struct_Buffer 211 | } 212 | 213 | // Free free rust memory buffer, must be called! 214 | // 215 | //go:inline 216 | func (b CBuffer) Free() { 217 | if b.buf.len > 0 { 218 | C.free_buffer(b.buf) 219 | } 220 | } 221 | 222 | //go:inline 223 | func (b CBuffer) Len() int { 224 | return int(b.buf.len) 225 | } 226 | 227 | //go:inline 228 | func (b CBuffer) AsBytes() []byte { 229 | if b.buf.len == 0 { 230 | return nil 231 | } 232 | return *(*[]byte)(unsafe.Pointer(&reflect.SliceHeader{ 233 | Data: uintptr(unsafe.Pointer(b.buf.ptr)), 234 | Len: int(b.buf.len), 235 | Cap: int(b.buf.cap), 236 | })) 237 | } 238 | 239 | //go:inline 240 | func (b CBuffer) AsString() string { 241 | if b.buf.len == 0 { 242 | return "" 243 | } 244 | return valconv.BytesToString[string](b.AsBytes()) 245 | } 246 | 247 | // RustFfiResult Rust FFI Result for Go 248 | // NOTE: must call Free method to free rust memory buffer! 249 | type RustFfiResult[T any] struct { 250 | CBuffer 251 | Code ResultCode 252 | _nil *T 253 | } 254 | 255 | //go:inline 256 | func newRustFfiResult[T any](ret C.struct_RustFfiResult) RustFfiResult[T] { 257 | return RustFfiResult[T]{ 258 | CBuffer: CBuffer{buf: ret.data}, 259 | Code: ResultCode(ret.code), 260 | _nil: nil, 261 | } 262 | } 263 | 264 | //go:inline 265 | func (r RustFfiResult[T]) String() string { 266 | return fmt.Sprintf("Code: %d, CBuffer: %s", r.Code, r.CBuffer.AsString()) 267 | } 268 | 269 | //go:inline 270 | func (r RustFfiResult[T]) IsOk() bool { 271 | return r.Code == RcNoError 272 | } 273 | 274 | // AsError as an error 275 | // NOTE: reference Rust memory buffer 276 | // 277 | //go:inline 278 | func (r RustFfiResult[T]) AsError() error { 279 | if r.Code != RcNoError { 280 | return errors.New(r.AsString()) 281 | } 282 | return nil 283 | } 284 | 285 | // PbUnmarshal as protobuf to unmarshal 286 | // NOTE: maybe reference Rust memory buffer 287 | // 288 | //go:inline 289 | func (r RustFfiResult[T]) PbUnmarshal() (*T, error) { 290 | if err := r.AsError(); err != nil { 291 | return nil, err 292 | } 293 | var t T 294 | if r.Len() > 0 { 295 | err := proto.Unmarshal(r.AsBytes(), any(&t).(proto.Message)) 296 | if err != nil { 297 | return nil, err 298 | } 299 | } 300 | return &t, nil 301 | } 302 | 303 | // PbUnmarshalUnchecked as protobuf to unmarshal 304 | // NOTE: maybe reference Rust memory buffer 305 | // 306 | //go:inline 307 | func (r RustFfiResult[T]) PbUnmarshalUnchecked() *T { 308 | if err := r.AsError(); err != nil { 309 | return nil 310 | } 311 | var t T 312 | if r.Len() > 0 { 313 | _ = proto.Unmarshal(r.AsBytes(), any(&t).(proto.Message)) 314 | } 315 | return &t 316 | } 317 | 318 | // JsonUnmarshal as json to unmarshal 319 | // NOTE: maybe reference Rust memory buffer 320 | // 321 | //go:inline 322 | func (r RustFfiResult[T]) JsonUnmarshal() (*T, error) { 323 | if err := r.AsError(); err != nil { 324 | return nil, err 325 | } 326 | var t T 327 | if r.Len() > 0 { 328 | err := sonic.Unmarshal(r.AsBytes(), &t) 329 | if err != nil { 330 | return nil, err 331 | } 332 | } 333 | return &t, nil 334 | } 335 | 336 | // JsonUnmarshalUnchecked as json to unmarshal 337 | // NOTE: maybe reference Rust memory buffer 338 | // 339 | //go:inline 340 | func (r RustFfiResult[T]) JsonUnmarshalUnchecked() *T { 341 | if err := r.AsError(); err != nil { 342 | return nil 343 | } 344 | var t T 345 | if r.Len() > 0 { 346 | _ = sonic.Unmarshal(r.AsBytes(), &t) 347 | } 348 | return &t 349 | } 350 | 351 | // Unmarshal unmarshal to object 352 | // NOTE: maybe reference Rust memory buffer 353 | // 354 | //go:inline 355 | func (r RustFfiResult[T]) Unmarshal(unmarshal func([]byte, any) error) (*T, error) { 356 | if err := r.AsError(); err != nil { 357 | return nil, err 358 | } 359 | var t T 360 | if r.Len() > 0 { 361 | err := unmarshal(r.AsBytes(), &t) 362 | if err != nil { 363 | return nil, err 364 | } 365 | } 366 | return &t, nil 367 | } 368 | 369 | // UnmarshalUnchecked unmarshal to object 370 | // NOTE: maybe reference Rust memory buffer 371 | // 372 | //go:inline 373 | func (r RustFfiResult[T]) UnmarshalUnchecked(unmarshal func([]byte, any) error) *T { 374 | if err := r.AsError(); err != nil { 375 | return nil 376 | } 377 | var t T 378 | if r.Len() > 0 { 379 | _ = unmarshal(r.AsBytes(), &t) 380 | } 381 | return &t 382 | } 383 | 384 | type RustFfi interface { 385 | EchoRs(req TBytes[*Ping]) RustFfiResult[Pong] 386 | } 387 | type RustFfiImpl struct{} 388 | 389 | //go:inline 390 | func (RustFfiImpl) EchoRs(req TBytes[*Ping]) RustFfiResult[Pong] { 391 | return newRustFfiResult[Pong](C.rustffi_echo_rs(req.asBuffer())) 392 | } 393 | -------------------------------------------------------------------------------- /samples/echo_thrift/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/andeya/fcplug/samples/echo_thrift 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/andeya/gust v1.5.2 7 | github.com/apache/thrift v0.13.0 8 | github.com/bytedance/sonic v1.10.0 9 | google.golang.org/protobuf v1.26.0 10 | ) 11 | 12 | require ( 13 | github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect 14 | github.com/chenzhuoyu/iasm v0.9.0 // indirect 15 | github.com/klauspost/cpuid/v2 v2.0.9 // indirect 16 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect 17 | golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect 18 | ) 19 | -------------------------------------------------------------------------------- /samples/echo_thrift/go_call_rust_test.go: -------------------------------------------------------------------------------- 1 | package echo_thrift_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/andeya/fcplug/samples/echo_thrift" 7 | ) 8 | 9 | func TestEcho(t *testing.T) { 10 | ret := echo_thrift.GlobalRustFfi.EchoRs(echo_thrift.TBytesFromPbUnchecked[*echo_thrift.Ping](&echo_thrift.Ping{ 11 | Msg: "this is ping from go", 12 | })) 13 | if ret.IsOk() { 14 | t.Logf("%#v", ret.PbUnmarshalUnchecked()) 15 | } else { 16 | t.Logf("fail: err=%v", ret.AsError()) 17 | } 18 | ret.Free() 19 | } 20 | 21 | func BenchmarkEcho(b *testing.B) { 22 | args := echo_thrift.TBytesFromPbUnchecked[*echo_thrift.Ping](&echo_thrift.Ping{ 23 | Msg: "this is ping from go", 24 | }) 25 | b.ResetTimer() 26 | for i := 0; i < b.N; i++ { 27 | ret := echo_thrift.GlobalRustFfi.EchoRs(args) 28 | if ret.IsOk() { 29 | _ = ret.AsBytes() 30 | } else { 31 | b.Logf("fail: err=%v", ret.AsError()) 32 | return 33 | } 34 | ret.Free() 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /samples/echo_thrift/src/echo_thrift_ffi/echo_thrift_gen.rs: -------------------------------------------------------------------------------- 1 | // Code generated by fcplug. DO NOT EDIT. 2 | #![allow(warnings, clippy::all)] 3 | #[derive( 4 | PartialOrd, 5 | Hash, 6 | Eq, 7 | Ord, 8 | Debug, 9 | Default, 10 | ::serde::Serialize, 11 | ::serde::Deserialize, 12 | Clone, 13 | PartialEq, 14 | )] 15 | pub struct Ping { 16 | pub msg: ::std::string::String, 17 | } 18 | #[::async_trait::async_trait] 19 | impl ::pilota::thrift::Message for Ping { 20 | fn encode( 21 | &self, 22 | protocol: &mut T, 23 | ) -> ::std::result::Result<(), ::pilota::thrift::EncodeError> { 24 | #[allow(unused_imports)] 25 | use ::pilota::thrift::TOutputProtocolExt; 26 | let struct_ident = ::pilota::thrift::TStructIdentifier { name: "Ping" }; 27 | 28 | protocol.write_struct_begin(&struct_ident)?; 29 | protocol.write_string_field(1, &&self.msg)?; 30 | protocol.write_field_stop()?; 31 | protocol.write_struct_end()?; 32 | Ok(()) 33 | } 34 | 35 | fn decode( 36 | protocol: &mut T, 37 | ) -> ::std::result::Result { 38 | let mut msg = None; 39 | 40 | let mut __pilota_decoding_field_id = None; 41 | 42 | protocol.read_struct_begin()?; 43 | if let Err(err) = (|| { 44 | loop { 45 | let field_ident = protocol.read_field_begin()?; 46 | if field_ident.field_type == ::pilota::thrift::TType::Stop { 47 | break; 48 | } 49 | __pilota_decoding_field_id = field_ident.id; 50 | match field_ident.id { 51 | Some(1) if field_ident.field_type == ::pilota::thrift::TType::Binary => { 52 | msg = Some(protocol.read_string()?); 53 | } 54 | _ => { 55 | protocol.skip(field_ident.field_type)?; 56 | } 57 | } 58 | 59 | protocol.read_field_end()?; 60 | } 61 | Ok::<_, ::pilota::thrift::DecodeError>(()) 62 | })() { 63 | if let Some(field_id) = __pilota_decoding_field_id { 64 | return Err(::pilota::thrift::DecodeError::new( 65 | ::pilota::thrift::DecodeErrorKind::WithContext(::std::boxed::Box::new(err)), 66 | format!("decode struct `Ping` field(#{}) failed", field_id), 67 | )); 68 | } else { 69 | return Err(err); 70 | } 71 | }; 72 | protocol.read_struct_end()?; 73 | 74 | let Some(msg) = msg else { 75 | return Err(::pilota::thrift::DecodeError::new( 76 | ::pilota::thrift::DecodeErrorKind::InvalidData, 77 | "field msg is required".to_string(), 78 | )); 79 | }; 80 | 81 | let data = Self { msg }; 82 | Ok(data) 83 | } 84 | 85 | async fn decode_async( 86 | protocol: &mut T, 87 | ) -> ::std::result::Result { 88 | let mut msg = None; 89 | 90 | let mut __pilota_decoding_field_id = None; 91 | 92 | protocol.read_struct_begin().await?; 93 | if let Err(err) = async { 94 | loop { 95 | let field_ident = protocol.read_field_begin().await?; 96 | if field_ident.field_type == ::pilota::thrift::TType::Stop { 97 | break; 98 | } 99 | __pilota_decoding_field_id = field_ident.id; 100 | match field_ident.id { 101 | Some(1) if field_ident.field_type == ::pilota::thrift::TType::Binary => { 102 | msg = Some(protocol.read_string().await?); 103 | } 104 | _ => { 105 | protocol.skip(field_ident.field_type).await?; 106 | } 107 | } 108 | 109 | protocol.read_field_end().await?; 110 | } 111 | Ok::<_, ::pilota::thrift::DecodeError>(()) 112 | } 113 | .await 114 | { 115 | if let Some(field_id) = __pilota_decoding_field_id { 116 | return Err(::pilota::thrift::DecodeError::new( 117 | ::pilota::thrift::DecodeErrorKind::WithContext(::std::boxed::Box::new(err)), 118 | format!("decode struct `Ping` field(#{}) failed", field_id), 119 | )); 120 | } else { 121 | return Err(err); 122 | } 123 | }; 124 | protocol.read_struct_end().await?; 125 | 126 | let Some(msg) = msg else { 127 | return Err(::pilota::thrift::DecodeError::new( 128 | ::pilota::thrift::DecodeErrorKind::InvalidData, 129 | "field msg is required".to_string(), 130 | )); 131 | }; 132 | 133 | let data = Self { msg }; 134 | Ok(data) 135 | } 136 | 137 | fn size(&self, protocol: &mut T) -> usize { 138 | #[allow(unused_imports)] 139 | use ::pilota::thrift::TLengthProtocolExt; 140 | protocol.write_struct_begin_len(&::pilota::thrift::TStructIdentifier { name: "Ping" }) 141 | + protocol.write_string_field_len(Some(1), &&self.msg) 142 | + protocol.write_field_stop_len() 143 | + protocol.write_struct_end_len() 144 | } 145 | } 146 | pub(super) trait RustFfi { 147 | fn echo_rs(req: ::fcplug::RustFfiArg) -> ::fcplug::ABIResult<::fcplug::TBytes>; 148 | } 149 | #[no_mangle] 150 | #[inline] 151 | pub extern "C" fn rustffi_echo_rs(req: ::fcplug::Buffer) -> ::fcplug::RustFfiResult { 152 | ::fcplug::RustFfiResult::from(::echo_rs(::fcplug::RustFfiArg::from( 153 | req, 154 | ))) 155 | } 156 | #[derive( 157 | PartialOrd, 158 | Hash, 159 | Eq, 160 | Ord, 161 | Debug, 162 | Default, 163 | ::serde::Serialize, 164 | ::serde::Deserialize, 165 | Clone, 166 | PartialEq, 167 | )] 168 | pub struct Pong { 169 | pub msg: ::std::string::String, 170 | } 171 | #[::async_trait::async_trait] 172 | impl ::pilota::thrift::Message for Pong { 173 | fn encode( 174 | &self, 175 | protocol: &mut T, 176 | ) -> ::std::result::Result<(), ::pilota::thrift::EncodeError> { 177 | #[allow(unused_imports)] 178 | use ::pilota::thrift::TOutputProtocolExt; 179 | let struct_ident = ::pilota::thrift::TStructIdentifier { name: "Pong" }; 180 | 181 | protocol.write_struct_begin(&struct_ident)?; 182 | protocol.write_string_field(1, &&self.msg)?; 183 | protocol.write_field_stop()?; 184 | protocol.write_struct_end()?; 185 | Ok(()) 186 | } 187 | 188 | fn decode( 189 | protocol: &mut T, 190 | ) -> ::std::result::Result { 191 | let mut msg = None; 192 | 193 | let mut __pilota_decoding_field_id = None; 194 | 195 | protocol.read_struct_begin()?; 196 | if let Err(err) = (|| { 197 | loop { 198 | let field_ident = protocol.read_field_begin()?; 199 | if field_ident.field_type == ::pilota::thrift::TType::Stop { 200 | break; 201 | } 202 | __pilota_decoding_field_id = field_ident.id; 203 | match field_ident.id { 204 | Some(1) if field_ident.field_type == ::pilota::thrift::TType::Binary => { 205 | msg = Some(protocol.read_string()?); 206 | } 207 | _ => { 208 | protocol.skip(field_ident.field_type)?; 209 | } 210 | } 211 | 212 | protocol.read_field_end()?; 213 | } 214 | Ok::<_, ::pilota::thrift::DecodeError>(()) 215 | })() { 216 | if let Some(field_id) = __pilota_decoding_field_id { 217 | return Err(::pilota::thrift::DecodeError::new( 218 | ::pilota::thrift::DecodeErrorKind::WithContext(::std::boxed::Box::new(err)), 219 | format!("decode struct `Pong` field(#{}) failed", field_id), 220 | )); 221 | } else { 222 | return Err(err); 223 | } 224 | }; 225 | protocol.read_struct_end()?; 226 | 227 | let Some(msg) = msg else { 228 | return Err(::pilota::thrift::DecodeError::new( 229 | ::pilota::thrift::DecodeErrorKind::InvalidData, 230 | "field msg is required".to_string(), 231 | )); 232 | }; 233 | 234 | let data = Self { msg }; 235 | Ok(data) 236 | } 237 | 238 | async fn decode_async( 239 | protocol: &mut T, 240 | ) -> ::std::result::Result { 241 | let mut msg = None; 242 | 243 | let mut __pilota_decoding_field_id = None; 244 | 245 | protocol.read_struct_begin().await?; 246 | if let Err(err) = async { 247 | loop { 248 | let field_ident = protocol.read_field_begin().await?; 249 | if field_ident.field_type == ::pilota::thrift::TType::Stop { 250 | break; 251 | } 252 | __pilota_decoding_field_id = field_ident.id; 253 | match field_ident.id { 254 | Some(1) if field_ident.field_type == ::pilota::thrift::TType::Binary => { 255 | msg = Some(protocol.read_string().await?); 256 | } 257 | _ => { 258 | protocol.skip(field_ident.field_type).await?; 259 | } 260 | } 261 | 262 | protocol.read_field_end().await?; 263 | } 264 | Ok::<_, ::pilota::thrift::DecodeError>(()) 265 | } 266 | .await 267 | { 268 | if let Some(field_id) = __pilota_decoding_field_id { 269 | return Err(::pilota::thrift::DecodeError::new( 270 | ::pilota::thrift::DecodeErrorKind::WithContext(::std::boxed::Box::new(err)), 271 | format!("decode struct `Pong` field(#{}) failed", field_id), 272 | )); 273 | } else { 274 | return Err(err); 275 | } 276 | }; 277 | protocol.read_struct_end().await?; 278 | 279 | let Some(msg) = msg else { 280 | return Err(::pilota::thrift::DecodeError::new( 281 | ::pilota::thrift::DecodeErrorKind::InvalidData, 282 | "field msg is required".to_string(), 283 | )); 284 | }; 285 | 286 | let data = Self { msg }; 287 | Ok(data) 288 | } 289 | 290 | fn size(&self, protocol: &mut T) -> usize { 291 | #[allow(unused_imports)] 292 | use ::pilota::thrift::TLengthProtocolExt; 293 | protocol.write_struct_begin_len(&::pilota::thrift::TStructIdentifier { name: "Pong" }) 294 | + protocol.write_string_field_len(Some(1), &&self.msg) 295 | + protocol.write_field_stop_len() 296 | + protocol.write_struct_end_len() 297 | } 298 | } 299 | pub trait GoFfiCall { 300 | unsafe fn echo_go(req: ::fcplug::TBytes) -> ::fcplug::ABIResult { 301 | ::fcplug::ABIResult::from(goffi_echo_go(::fcplug::Buffer::from_vec(req.bytes))) 302 | } 303 | } 304 | 305 | pub(super) trait GoFfi { 306 | unsafe fn echo_go_set_result(go_ret: ::fcplug::RustFfiArg) -> ::fcplug::GoFfiResult; 307 | } 308 | extern "C" { 309 | fn goffi_echo_go(req: ::fcplug::Buffer) -> ::fcplug::GoFfiResult; 310 | } 311 | #[no_mangle] 312 | #[inline] 313 | pub extern "C" fn goffi_echo_go_set_result(buf: ::fcplug::Buffer) -> ::fcplug::GoFfiResult { 314 | unsafe { ::echo_go_set_result(::fcplug::RustFfiArg::from(buf)) } 315 | } 316 | trait Ffi: RustFfi + GoFfi + GoFfiCall {} 317 | 318 | pub struct FfiImpl; 319 | 320 | impl GoFfiCall for FfiImpl {} 321 | impl Ffi for FfiImpl {} 322 | -------------------------------------------------------------------------------- /samples/echo_thrift/src/echo_thrift_ffi/mod.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_variables)] 2 | 3 | pub use echo_thrift_gen::*; 4 | 5 | mod echo_thrift_gen; 6 | 7 | impl RustFfi for FfiImpl { 8 | fn echo_rs(req: ::fcplug::RustFfiArg) -> ::fcplug::ABIResult<::fcplug::TBytes> { 9 | todo!() 10 | } 11 | } 12 | 13 | impl GoFfi for FfiImpl { 14 | unsafe fn echo_go_set_result(go_ret: ::fcplug::RustFfiArg) -> ::fcplug::GoFfiResult { 15 | todo!() 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /samples/echo_thrift/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(test)] 2 | 3 | extern crate test; 4 | 5 | mod echo_thrift_ffi; 6 | 7 | 8 | #[cfg(test)] 9 | mod tests { 10 | use test::Bencher; 11 | 12 | use fcplug::protobuf::PbMessage; 13 | use fcplug::TryIntoTBytes; 14 | 15 | use crate::echo_thrift_ffi::{FfiImpl, GoFfiCall, Ping, Pong}; 16 | 17 | #[test] 18 | fn test_call_echo_go() { 19 | let pong = unsafe { 20 | FfiImpl::echo_go::(Ping { 21 | msg: "this is ping from rust".to_string(), 22 | }.try_into_tbytes::>().unwrap()) 23 | }; 24 | println!("{:?}", pong); 25 | } 26 | 27 | #[bench] 28 | fn bench_call_echo_go(b: &mut Bencher) { 29 | let req = Ping { 30 | msg: "this is ping from rust".to_string(), 31 | } 32 | .try_into_tbytes::>() 33 | .unwrap(); 34 | b.iter(|| { 35 | let pong = unsafe { FfiImpl::echo_go::>(req.clone()) }; 36 | let _ = test::black_box(pong); 37 | }); 38 | } 39 | } 40 | --------------------------------------------------------------------------------