1# Rust FFI 2 3In case of an existing C++, Rust can call into the C++ function via FFI. 4With Bazel, this is straightforward. However, your C++ API needs an extern "C" 5declaration to generate the C compatibility required for FFI. 6 7## Setup 8 9The setup is twofold, the Rust rules are declared in the MODULE file, 10but the rules_cc are not yet available in the Bazelmod format and thus are declared in 11the WORKSPACE.bzlmod file. 12 13In your MODULE.bazel file, ensure to have the following entry: 14 15```starlark 16module( 17 name = "ffi", 18 version = "0.0.0" 19) 20############################################################################### 21# B A Z E L C E N T R A L R E G I S T R Y # https://registry.bazel.build/ 22############################################################################### 23# https://github.com/bazelbuild/rules_rust/releases 24bazel_dep(name = "rules_rust", version = "0.46.0") 25 26############################################################################### 27# T O O L C H A I N S 28############################################################################### 29# Rust toolchain 30RUST_EDITION = "2021" 31RUST_VERSION = "1.79.0" 32 33rust = use_extension("@rules_rust//rust:extensions.bzl", "rust") 34rust.toolchain( 35 edition = RUST_EDITION, 36 versions = [RUST_VERSION], 37) 38use_repo(rust, "rust_toolchains") 39register_toolchains("@rust_toolchains//:all") 40``` 41 42Then, create or open the WORKSPACE.bzlmod file and add the CC rules: 43 44```starlark 45############################################################################### 46# Bzlmod and WORKSPACE can work side by side, which allows migrating dependencies 47# from the WORKSPACE file to Bzlmod to be a gradual process. 48# https://bazel.build/external/migration#hybrid-mode 49############################################################################### 50# rule http_archive 51load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") 52 53# rules_cc 54# https://github.com/bazelbuild/rules_cc/releases 55http_archive( 56 name = "rules_cc", 57 urls = ["https://github.com/bazelbuild/rules_cc/releases/download/0.0.10-rc1/rules_cc-0.0.10-rc1.tar.gz"], 58 sha256 = "d75a040c32954da0d308d3f2ea2ba735490f49b3a7aa3e4b40259ca4b814f825", 59) 60``` 61 62 63## C++ Target 64 65Assuming you have a C++ library that defines a simple func() you declare it as a regular CC library in the BUILD file: 66 67```starlark 68load("@rules_cc//cc:defs.bzl", "cc_import", "cc_library") 69 70cc_library( 71 name = "nonstandard_name_cc_lib", 72 srcs = ["c/cc_library.cc"], 73) 74``` 75 76In some cases, you have to deal with non standard naming. In that case you define a 77custom gen_rule to take of that and then define a cc_import. 78 79```starlark 80load("@rules_cc//cc:defs.bzl", "cc_import", "cc_library") 81 82genrule( 83 name = "nonstandard_name_gen", 84 srcs = [":nonstandard_name_cc_lib"], 85 outs = ["nonstandard_name_gen.a"], 86 # Copy the first member (libnonstandard_name_cc_lib.a) from the srcs to the 87 # output nonstandard_name_gen.a. 88 cmd = "cp $$(awk '{print $$1}' <<< '$(SRCS)') $@", 89) 90 91cc_import( 92 name = "static_cclib", 93 static_library = "nonstandard_name_gen.a", 94) 95``` 96 97## Rust Callsite 98 99On the Rust side, interestingly, you just declare the cc_import as a dependency of 100your Rust target. 101 102```starlark 103load("@rules_rust//rust:defs.bzl", "rust_shared_library") 104 105# A rust_shared_library (forcing the use of pic) that depends on a native 106# linker library with only a static_library member. 107rust_shared_library( 108 name = "rust_shared_lib_with_static_dep", 109 srcs = ["src/rust_shared_lib_with_static_dep.rs"], 110 deps = [":static_cclib"], 111) 112``` 113 114Then in your Rust source file, your create a FFI binding and wrap the call to it into unsafe. You can do that because the Rust standard library provides all the c raw types for FFI so you just import them and unsafe informs the Rust borrow checker to hold off certain checks. The public Rust function f() can then be used in regular Rust code. 115 116```rust 117use std::os::raw::c_int; 118 119extern "C" { 120 pub fn func() -> c_int; 121} 122 123pub fn f() { 124 println!("hi {}", 125 unsafe { 126 func() 127 } 128 ); 129} 130``` 131 132And with that, you build your FFI target as usual: 133 134`bazel build //...` 135 136 137 138