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 — 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–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