1{{#title Tutorial — Rust ♡ C++}}
2# Tutorial: CXX blobstore client
3
4This example walks through a Rust application that calls into a C++ client of a
5blobstore service. In fact we'll see calls going in both directions: Rust to C++
6as well as C++ to Rust. For your own use case it may be that you need just one
7of these directions.
8
9All of the code involved in the example is shown on this page, but it's also
10provided in runnable form in the *demo* directory of
11<https://github.com/dtolnay/cxx>. To try it out directly, run `cargo run` from
12that directory.
13
14This tutorial assumes you've read briefly about **shared structs**, **opaque
15types**, and **functions** in the [*Core concepts*](concepts.md) page.
16
17## Creating the project
18
19We'll use Cargo, which is the build system commonly used by open source Rust
20projects. (CXX works with other build systems too; refer to chapter 5.)
21
22Create a blank Cargo project: `mkdir cxx-demo`; `cd cxx-demo`; `cargo init`.
23
24Edit the Cargo.toml to add a dependency on the `cxx` crate:
25
26```toml,hidelines=...
27# Cargo.toml
28...[package]
29...name = "cxx-demo"
30...version = "0.1.0"
31...edition = "2021"
32
33[dependencies]
34cxx = "1.0"
35```
36
37We'll revisit this Cargo.toml later when we get to compiling some C++ code.
38
39## Defining the language boundary
40
41CXX relies on a description of the function signatures that will be exposed from
42each language to the other. You provide this description using `extern` blocks
43in a Rust module annotated with the `#[cxx::bridge]` attribute macro.
44
45We'll open with just the following at the top of src/main.rs and walk through
46each item in detail.
47
48```rust,noplayground
49// src/main.rs
50
51#[cxx::bridge]
52mod ffi {
53
54}
55#
56# fn main() {}
57```
58
59The contents of this module will be everything that needs to be agreed upon by
60both sides of the FFI boundary.
61
62## Calling a C++ function from Rust
63
64Let's obtain an instance of the C++ blobstore client, a class `BlobstoreClient`
65defined in C++.
66
67We'll treat `BlobstoreClient` as an *opaque type* in CXX's classification so
68that Rust does not need to assume anything about its implementation, not even
69its size or alignment. In general, a C++ type might have a move-constructor
70which is incompatible with Rust's move semantics, or may hold internal
71references which cannot be modeled by Rust's borrowing system. Though there are
72alternatives, the easiest way to not care about any such thing on an FFI
73boundary is to require no knowledge about a type by treating it as opaque.
74
75Opaque types may only be manipulated behind an indirection such as a reference
76`&`, a Rust `Box`, or a `UniquePtr` (Rust binding of `std::unique_ptr`). We'll
77add a function through which C++ can return a `std::unique_ptr<BlobstoreClient>`
78to Rust.
79
80```rust,noplayground
81// src/main.rs
82
83#[cxx::bridge]
84mod ffi {
85    unsafe extern "C++" {
86        include!("cxx-demo/include/blobstore.h");
87
88        type BlobstoreClient;
89
90        fn new_blobstore_client() -> UniquePtr<BlobstoreClient>;
91    }
92}
93
94fn main() {
95    let client = ffi::new_blobstore_client();
96}
97```
98
99The nature of `unsafe` extern blocks is clarified in more detail in the
100[*extern "C++"*](extern-c++.md) chapter. In brief: the programmer is **not**
101promising that the signatures they have typed in are accurate; that would be
102unreasonable. CXX performs static assertions that the signatures exactly match
103what is declared in C++. Rather, the programmer is only on the hook for things
104that C++'s semantics are not precise enough to capture, i.e. things that would
105only be represented at most by comments in the C++ code. In this case, it's
106whether `new_blobstore_client` is safe or unsafe to call. If that function said
107something like "must be called at most once or we'll stomp yer memery", Rust
108would instead want to expose it as `unsafe fn new_blobstore_client`, this time
109inside a safe `extern "C++"` block because the programmer is no longer on the
110hook for any safety claim about the signature.
111
112If you build this file right now with `cargo build`, it won't build because we
113haven't written a C++ implementation of `new_blobstore_client` nor instructed
114Cargo about how to link it into the resulting binary. You'll see an error from
115the linker like this:
116
117```console
118error: linking with `cc` failed: exit code: 1
119 |
120 = /bin/ld: target/debug/deps/cxx-demo-7cb7fddf3d67d880.rcgu.o: in function `cxx_demo::ffi::new_blobstore_client':
121   src/main.rs:1: undefined reference to `cxxbridge1$new_blobstore_client'
122   collect2: error: ld returned 1 exit status
123```
124
125## Adding in the C++ code
126
127In CXX's integration with Cargo, all #include paths begin with a crate name by
128default (when not explicitly selected otherwise by a crate; see
129`CFG.include_prefix` in chapter 5). That's why we see
130`include!("cxx-demo/include/blobstore.h")` above &mdash; we'll be putting the
131C++ header at relative path `include/blobstore.h` within the Rust crate. If your
132crate is named something other than `cxx-demo` according to the `name` field in
133Cargo.toml, you will need to use that name everywhere in place of `cxx-demo`
134throughout this tutorial.
135
136```cpp
137// include/blobstore.h
138
139#pragma once
140#include <memory>
141
142class BlobstoreClient {
143public:
144  BlobstoreClient();
145};
146
147std::unique_ptr<BlobstoreClient> new_blobstore_client();
148```
149
150```cpp
151// src/blobstore.cc
152
153#include "cxx-demo/include/blobstore.h"
154
155BlobstoreClient::BlobstoreClient() {}
156
157std::unique_ptr<BlobstoreClient> new_blobstore_client() {
158  return std::unique_ptr<BlobstoreClient>(new BlobstoreClient());
159}
160```
161
162Using `std::make_unique` would work too, as long as you pass `std("c++14")` to
163the C++ compiler as described later on.
164
165The placement in *include/* and *src/* is not significant; you can place C++
166code anywhere else in the crate as long as you use the right paths throughout
167the tutorial.
168
169Be aware that *CXX does not look at any of these files.* You're free to put
170arbitrary C++ code in here, #include your own libraries, etc. All we do is emit
171static assertions against what you provide in the headers.
172
173## Compiling the C++ code with Cargo
174
175Cargo has a [build scripts] feature suitable for compiling non-Rust code.
176
177We need to introduce a new build-time dependency on CXX's C++ code generator in
178Cargo.toml:
179
180```toml,hidelines=...
181# Cargo.toml
182...[package]
183...name = "cxx-demo"
184...version = "0.1.0"
185...edition = "2021"
186
187[dependencies]
188cxx = "1.0"
189
190[build-dependencies]
191cxx-build = "1.0"
192```
193
194Then add a build.rs build script adjacent to Cargo.toml to run the cxx-build
195code generator and C++ compiler. The relevant arguments are the path to the Rust
196source file containing the cxx::bridge language boundary definition, and the
197paths to any additional C++ source files to be compiled during the Rust crate's
198build.
199
200```rust,noplayground
201// build.rs
202
203fn main() {
204    cxx_build::bridge("src/main.rs")
205        .file("src/blobstore.cc")
206        .compile("cxx-demo");
207
208    println!("cargo:rerun-if-changed=src/main.rs");
209    println!("cargo:rerun-if-changed=src/blobstore.cc");
210    println!("cargo:rerun-if-changed=include/blobstore.h");
211}
212```
213
214This build.rs would also be where you set up C++ compiler flags, for example if
215you'd like to have access to `std::make_unique` from C++14. See the page on
216***[Cargo-based builds](build/cargo.md)*** for more details about CXX's Cargo
217integration.
218
219```rust,noplayground
220# // build.rs
221#
222# fn main() {
223    cxx_build::bridge("src/main.rs")
224        .file("src/blobstore.cc")
225        .std("c++14")
226        .compile("cxx-demo");
227# }
228```
229
230[build scripts]: https://doc.rust-lang.org/cargo/reference/build-scripts.html
231
232The project should now build and run successfully, though not do anything useful
233yet.
234
235```console
236cxx-demo$  cargo run
237  Compiling cxx-demo v0.1.0
238  Finished dev [unoptimized + debuginfo] target(s) in 0.34s
239  Running `target/debug/cxx-demo`
240
241cxx-demo$
242```
243
244## Calling a Rust function from C++
245
246Our C++ blobstore supports a `put` operation for a discontiguous buffer upload.
247For example we might be uploading snapshots of a circular buffer which would
248tend to consist of 2 pieces, or fragments of a file spread across memory for
249some other reason (like a rope data structure).
250
251We'll express this by handing off an iterator over contiguous borrowed chunks.
252This loosely resembles the API of the widely used `bytes` crate's `Buf` trait.
253During a `put`, we'll make C++ call back into Rust to obtain contiguous chunks
254of the upload (all with no copying or allocation on the language boundary). In
255reality the C++ client might contain some sophisticated batching of chunks
256and/or parallel uploading that all of this ties into.
257
258```rust,noplayground
259// src/main.rs
260
261#[cxx::bridge]
262mod ffi {
263    extern "Rust" {
264        type MultiBuf;
265
266        fn next_chunk(buf: &mut MultiBuf) -> &[u8];
267    }
268
269    unsafe extern "C++" {
270        include!("cxx-demo/include/blobstore.h");
271
272        type BlobstoreClient;
273
274        fn new_blobstore_client() -> UniquePtr<BlobstoreClient>;
275        fn put(&self, parts: &mut MultiBuf) -> u64;
276    }
277}
278#
279# fn main() {
280#     let client = ffi::new_blobstore_client();
281# }
282```
283
284Any signature having a `self` parameter (the Rust name for C++'s `this`) is
285considered a method / non-static member function. If there is only one `type` in
286the surrounding extern block, it'll be a method of that type. If there is more
287than one `type`, you can disambiguate which one a method belongs to by writing
288`self: &BlobstoreClient` in the argument list.
289
290As usual, now we need to provide Rust definitions of everything declared by the
291`extern "Rust"` block and a C++ definition of the new signature declared by the
292`extern "C++"` block.
293
294```rust,noplayground
295// src/main.rs
296#
297# #[cxx::bridge]
298# mod ffi {
299#     extern "Rust" {
300#         type MultiBuf;
301#
302#         fn next_chunk(buf: &mut MultiBuf) -> &[u8];
303#     }
304#
305#     unsafe extern "C++" {
306#         include!("cxx-demo/include/blobstore.h");
307#
308#         type BlobstoreClient;
309#
310#         fn new_blobstore_client() -> UniquePtr<BlobstoreClient>;
311#         fn put(&self, parts: &mut MultiBuf) -> u64;
312#     }
313# }
314
315// An iterator over contiguous chunks of a discontiguous file object. Toy
316// implementation uses a Vec<Vec<u8>> but in reality this might be iterating
317// over some more complex Rust data structure like a rope, or maybe loading
318// chunks lazily from somewhere.
319pub struct MultiBuf {
320    chunks: Vec<Vec<u8>>,
321    pos: usize,
322}
323
324pub fn next_chunk(buf: &mut MultiBuf) -> &[u8] {
325    let next = buf.chunks.get(buf.pos);
326    buf.pos += 1;
327    next.map_or(&[], Vec::as_slice)
328}
329#
330# fn main() {
331#     let client = ffi::new_blobstore_client();
332# }
333```
334
335```cpp,hidelines=...
336// include/blobstore.h
337
338...#pragma once
339...#include <memory>
340...
341struct MultiBuf;
342
343class BlobstoreClient {
344public:
345  BlobstoreClient();
346  uint64_t put(MultiBuf &buf) const;
347};
348...
349...std::unique_ptr<BlobstoreClient> new_blobstore_client();
350```
351
352In blobstore.cc we're able to call the Rust `next_chunk` function, exposed to
353C++ by a header `main.rs.h` generated by the CXX code generator. In CXX's Cargo
354integration this generated header has a path containing the crate name, the
355relative path of the Rust source file within the crate, and a `.rs.h` extension.
356
357```cpp,hidelines=...
358// src/blobstore.cc
359
360#include "cxx-demo/include/blobstore.h"
361#include "cxx-demo/src/main.rs.h"
362#include <functional>
363#include <string>
364...
365...BlobstoreClient::BlobstoreClient() {}
366...
367...std::unique_ptr<BlobstoreClient> new_blobstore_client() {
368...  return std::make_unique<BlobstoreClient>();
369...}
370
371// Upload a new blob and return a blobid that serves as a handle to the blob.
372uint64_t BlobstoreClient::put(MultiBuf &buf) const {
373  // Traverse the caller's chunk iterator.
374  std::string contents;
375  while (true) {
376    auto chunk = next_chunk(buf);
377    if (chunk.size() == 0) {
378      break;
379    }
380    contents.append(reinterpret_cast<const char *>(chunk.data()), chunk.size());
381  }
382
383  // Pretend we did something useful to persist the data.
384  auto blobid = std::hash<std::string>{}(contents);
385  return blobid;
386}
387```
388
389This is now ready to use. :)
390
391```rust,noplayground
392// src/main.rs
393#
394# #[cxx::bridge]
395# mod ffi {
396#     extern "Rust" {
397#         type MultiBuf;
398#
399#         fn next_chunk(buf: &mut MultiBuf) -> &[u8];
400#     }
401#
402#     unsafe extern "C++" {
403#         include!("cxx-demo/include/blobstore.h");
404#
405#         type BlobstoreClient;
406#
407#         fn new_blobstore_client() -> UniquePtr<BlobstoreClient>;
408#         fn put(&self, parts: &mut MultiBuf) -> u64;
409#     }
410# }
411#
412# pub struct MultiBuf {
413#     chunks: Vec<Vec<u8>>,
414#     pos: usize,
415# }
416# pub fn next_chunk(buf: &mut MultiBuf) -> &[u8] {
417#     let next = buf.chunks.get(buf.pos);
418#     buf.pos += 1;
419#     next.map_or(&[], Vec::as_slice)
420# }
421
422fn main() {
423    let client = ffi::new_blobstore_client();
424
425    // Upload a blob.
426    let chunks = vec![b"fearless".to_vec(), b"concurrency".to_vec()];
427    let mut buf = MultiBuf { chunks, pos: 0 };
428    let blobid = client.put(&mut buf);
429    println!("blobid = {}", blobid);
430}
431```
432
433```console
434cxx-demo$  cargo run
435  Compiling cxx-demo v0.1.0
436  Finished dev [unoptimized + debuginfo] target(s) in 0.41s
437  Running `target/debug/cxx-demo`
438
439blobid = 9851996977040795552
440```
441
442## Interlude: What gets generated?
443
444For the curious, it's easy to look behind the scenes at what CXX has done to
445make these function calls work. You shouldn't need to do this during normal
446usage of CXX, but for the purpose of this tutorial it can be educative.
447
448CXX comprises *two* code generators: a Rust one (which is the cxx::bridge
449attribute procedural macro) and a C++ one.
450
451### Rust generated code
452
453It's easiest to view the output of the procedural macro by installing
454[cargo-expand]. Then run `cargo expand ::ffi` to macro-expand the `mod ffi`
455module.
456
457[cargo-expand]: https://github.com/dtolnay/cargo-expand
458
459```console
460cxx-demo$  cargo install cargo-expand
461cxx-demo$  cargo expand ::ffi
462```
463
464You'll see some deeply unpleasant code involving `#[repr(C)]`, `#[link_name]`,
465and `#[export_name]`.
466
467### C++ generated code
468
469For debugging convenience, `cxx_build` links all generated C++ code into Cargo's
470target directory under *target/cxxbridge/*.
471
472```console
473cxx-demo$  exa -T target/cxxbridge/
474target/cxxbridge
475├── cxx-demo
476│  └── src
477│     ├── main.rs.cc -> ../../../debug/build/cxx-demo-11c6f678ce5c3437/out/cxxbridge/sources/cxx-demo/src/main.rs.cc
478│     └── main.rs.h -> ../../../debug/build/cxx-demo-11c6f678ce5c3437/out/cxxbridge/include/cxx-demo/src/main.rs.h
479└── rust
480   └── cxx.h -> ~/.cargo/registry/src/github.com-1ecc6299db9ec823/cxx-1.0.0/include/cxx.h
481```
482
483In those files you'll see declarations or templates of any CXX Rust types
484present in your language boundary (like `rust::Slice<T>` for `&[T]`) and `extern
485"C"` signatures corresponding to your extern functions.
486
487If it fits your workflow better, the CXX C++ code generator is also available as
488a standalone executable which outputs generated code to stdout.
489
490```console
491cxx-demo$  cargo install cxxbridge-cmd
492cxx-demo$  cxxbridge src/main.rs
493```
494
495## Shared data structures
496
497So far the calls in both directions above only used **opaque types**, not
498**shared structs**.
499
500Shared structs are data structures whose complete definition is visible to both
501languages, making it possible to pass them by value across the language
502boundary. Shared structs translate to a C++ aggregate-initialization compatible
503struct exactly matching the layout of the Rust one.
504
505As the last step of this demo, we'll use a shared struct `BlobMetadata` to pass
506metadata about blobs between our Rust application and C++ blobstore client.
507
508```rust,noplayground
509// src/main.rs
510
511#[cxx::bridge]
512mod ffi {
513    struct BlobMetadata {
514        size: usize,
515        tags: Vec<String>,
516    }
517
518    extern "Rust" {
519        // ...
520#         type MultiBuf;
521#
522#         fn next_chunk(buf: &mut MultiBuf) -> &[u8];
523    }
524
525    unsafe extern "C++" {
526        // ...
527#         include!("cxx-demo/include/blobstore.h");
528#
529#         type BlobstoreClient;
530#
531#         fn new_blobstore_client() -> UniquePtr<BlobstoreClient>;
532#         fn put(&self, parts: &mut MultiBuf) -> u64;
533        fn tag(&self, blobid: u64, tag: &str);
534        fn metadata(&self, blobid: u64) -> BlobMetadata;
535    }
536}
537#
538# pub struct MultiBuf {
539#     chunks: Vec<Vec<u8>>,
540#     pos: usize,
541# }
542# pub fn next_chunk(buf: &mut MultiBuf) -> &[u8] {
543#     let next = buf.chunks.get(buf.pos);
544#     buf.pos += 1;
545#     next.map_or(&[], Vec::as_slice)
546# }
547
548fn main() {
549    let client = ffi::new_blobstore_client();
550
551    // Upload a blob.
552    let chunks = vec![b"fearless".to_vec(), b"concurrency".to_vec()];
553    let mut buf = MultiBuf { chunks, pos: 0 };
554    let blobid = client.put(&mut buf);
555    println!("blobid = {}", blobid);
556
557    // Add a tag.
558    client.tag(blobid, "rust");
559
560    // Read back the tags.
561    let metadata = client.metadata(blobid);
562    println!("tags = {:?}", metadata.tags);
563}
564```
565
566```cpp,hidelines=...
567// include/blobstore.h
568
569#pragma once
570#include "rust/cxx.h"
571...#include <memory>
572
573struct MultiBuf;
574struct BlobMetadata;
575
576class BlobstoreClient {
577public:
578  BlobstoreClient();
579  uint64_t put(MultiBuf &buf) const;
580  void tag(uint64_t blobid, rust::Str tag) const;
581  BlobMetadata metadata(uint64_t blobid) const;
582
583private:
584  class impl;
585  std::shared_ptr<impl> impl;
586};
587...
588...std::unique_ptr<BlobstoreClient> new_blobstore_client();
589```
590
591```cpp,hidelines=...
592// src/blobstore.cc
593
594#include "cxx-demo/include/blobstore.h"
595#include "cxx-demo/src/main.rs.h"
596#include <algorithm>
597#include <functional>
598#include <set>
599#include <string>
600#include <unordered_map>
601
602// Toy implementation of an in-memory blobstore.
603//
604// In reality the implementation of BlobstoreClient could be a large
605// complex C++ library.
606class BlobstoreClient::impl {
607  friend BlobstoreClient;
608  using Blob = struct {
609    std::string data;
610    std::set<std::string> tags;
611  };
612  std::unordered_map<uint64_t, Blob> blobs;
613};
614
615BlobstoreClient::BlobstoreClient() : impl(new class BlobstoreClient::impl) {}
616...
617...// Upload a new blob and return a blobid that serves as a handle to the blob.
618...uint64_t BlobstoreClient::put(MultiBuf &buf) const {
619...  // Traverse the caller's chunk iterator.
620...  std::string contents;
621...  while (true) {
622...    auto chunk = next_chunk(buf);
623...    if (chunk.size() == 0) {
624...      break;
625...    }
626...    contents.append(reinterpret_cast<const char *>(chunk.data()), chunk.size());
627...  }
628...
629...  // Insert into map and provide caller the handle.
630...  auto blobid = std::hash<std::string>{}(contents);
631...  impl->blobs[blobid] = {std::move(contents), {}};
632...  return blobid;
633...}
634
635// Add tag to an existing blob.
636void BlobstoreClient::tag(uint64_t blobid, rust::Str tag) const {
637  impl->blobs[blobid].tags.emplace(tag);
638}
639
640// Retrieve metadata about a blob.
641BlobMetadata BlobstoreClient::metadata(uint64_t blobid) const {
642  BlobMetadata metadata{};
643  auto blob = impl->blobs.find(blobid);
644  if (blob != impl->blobs.end()) {
645    metadata.size = blob->second.data.size();
646    std::for_each(blob->second.tags.cbegin(), blob->second.tags.cend(),
647                  [&](auto &t) { metadata.tags.emplace_back(t); });
648  }
649  return metadata;
650}
651...
652...std::unique_ptr<BlobstoreClient> new_blobstore_client() {
653...  return std::make_unique<BlobstoreClient>();
654...}
655```
656
657```console
658cxx-demo$  cargo run
659  Running `target/debug/cxx-demo`
660
661blobid = 9851996977040795552
662tags = ["rust"]
663```
664
665*You've now seen all the code involved in the tutorial. It's available all
666together in runnable form in the* demo *directory of
667<https://github.com/dtolnay/cxx>. You can run it directly without stepping
668through the steps above by running `cargo run` from that directory.*
669
670<br>
671
672# Takeaways
673
674The key contribution of CXX is it gives you Rust&ndash;C++ interop in which
675*all* of the Rust side of the code you write *really* looks like you are just
676writing normal Rust, and the C++ side *really* looks like you are just writing
677normal C++.
678
679You've seen in this tutorial that none of the code involved feels like C or like
680the usual perilous "FFI glue" prone to leaks or memory safety flaws.
681
682An expressive system of opaque types, shared types, and key standard library
683type bindings enables API design on the language boundary that captures the
684proper ownership and borrowing contracts of the interface.
685
686CXX plays to the strengths of the Rust type system *and* C++ type system *and*
687the programmer's intuitions. An individual working on the C++ side without a
688Rust background, or the Rust side without a C++ background, will be able to
689apply all their usual intuitions and best practices about development in their
690language to maintain a correct FFI.
691
692<br><br>
693