Name Date Size #Lines LOC

..--

ffi/H25-Apr-2025-7437

.bazelrcH A D25-Apr-20253 KiB7960

.bazelversionH A D25-Apr-20255 11

.gitignoreH A D25-Apr-202518 22

BUILD.bazelH A D25-Apr-20250

MODULE.bazelH A D25-Apr-2025941 3326

README.mdH A D25-Apr-20254.1 KiB138105

WORKSPACE.bzlmodH A D25-Apr-2025737 1514

README.md

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