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 = "2018" 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 the 163C++ 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 = "2018" 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``` 209 210This build.rs would also be where you set up C++ compiler flags, for example if 211you'd like to have access to `std::make_unique` from C++14. See the page on 212***[Cargo-based builds](build/cargo.md)*** for more details about CXX's Cargo 213integration. 214 215```rust,noplayground 216# // build.rs 217# 218# fn main() { 219 cxx_build::bridge("src/main.rs") 220 .file("src/blobstore.cc") 221 .flag_if_supported("-std=c++14") 222 .compile("cxx-demo"); 223# } 224``` 225 226[build scripts]: https://doc.rust-lang.org/cargo/reference/build-scripts.html 227 228The project should now build and run successfully, though not do anything useful 229yet. 230 231```console 232cxx-demo$ cargo run 233 Compiling cxx-demo v0.1.0 234 Finished dev [unoptimized + debuginfo] target(s) in 0.34s 235 Running `target/debug/cxx-demo` 236 237cxx-demo$ 238``` 239 240## Calling a Rust function from C++ 241 242Our C++ blobstore supports a `put` operation for a discontiguous buffer upload. 243For example we might be uploading snapshots of a circular buffer which would 244tend to consist of 2 pieces, or fragments of a file spread across memory for 245some other reason (like a rope data structure). 246 247We'll express this by handing off an iterator over contiguous borrowed chunks. 248This loosely resembles the API of the widely used `bytes` crate's `Buf` trait. 249During a `put`, we'll make C++ call back into Rust to obtain contiguous chunks 250of the upload (all with no copying or allocation on the language boundary). In 251reality the C++ client might contain some sophisticated batching of chunks 252and/or parallel uploading that all of this ties into. 253 254```rust,noplayground 255// src/main.rs 256 257#[cxx::bridge] 258mod ffi { 259 extern "Rust" { 260 type MultiBuf; 261 262 fn next_chunk(buf: &mut MultiBuf) -> &[u8]; 263 } 264 265 unsafe extern "C++" { 266 include!("cxx-demo/include/blobstore.h"); 267 268 type BlobstoreClient; 269 270 fn new_blobstore_client() -> UniquePtr<BlobstoreClient>; 271 fn put(&self, parts: &mut MultiBuf) -> u64; 272 } 273} 274# 275# fn main() { 276# let client = ffi::new_blobstore_client(); 277# } 278``` 279 280Any signature having a `self` parameter (the Rust name for C++'s `this`) is 281considered a method / non-static member function. If there is only one `type` in 282the surrounding extern block, it'll be a method of that type. If there is more 283than one `type`, you can disambiguate which one a method belongs to by writing 284`self: &BlobstoreClient` in the argument list. 285 286As usual, now we need to provide Rust definitions of everything declared by the 287`extern "Rust"` block and a C++ definition of the new signature declared by the 288`extern "C++"` block. 289 290```rust,noplayground 291// src/main.rs 292# 293# #[cxx::bridge] 294# mod ffi { 295# extern "Rust" { 296# type MultiBuf; 297# 298# fn next_chunk(buf: &mut MultiBuf) -> &[u8]; 299# } 300# 301# unsafe extern "C++" { 302# include!("cxx-demo/include/blobstore.h"); 303# 304# type BlobstoreClient; 305# 306# fn new_blobstore_client() -> UniquePtr<BlobstoreClient>; 307# fn put(&self, parts: &mut MultiBuf) -> u64; 308# } 309# } 310 311// An iterator over contiguous chunks of a discontiguous file object. Toy 312// implementation uses a Vec<Vec<u8>> but in reality this might be iterating 313// over some more complex Rust data structure like a rope, or maybe loading 314// chunks lazily from somewhere. 315pub struct MultiBuf { 316 chunks: Vec<Vec<u8>>, 317 pos: usize, 318} 319 320pub fn next_chunk(buf: &mut MultiBuf) -> &[u8] { 321 let next = buf.chunks.get(buf.pos); 322 buf.pos += 1; 323 next.map_or(&[], Vec::as_slice) 324} 325# 326# fn main() { 327# let client = ffi::new_blobstore_client(); 328# } 329``` 330 331```cpp,hidelines 332// include/blobstore.h 333 334# #pragma once 335# #include <memory> 336# 337struct MultiBuf; 338 339class BlobstoreClient { 340public: 341 BlobstoreClient(); 342 uint64_t put(MultiBuf &buf) const; 343}; 344# 345#std::unique_ptr<BlobstoreClient> new_blobstore_client(); 346``` 347 348In blobstore.cc we're able to call the Rust `next_chunk` function, exposed to 349C++ by a header `main.rs.h` generated by the CXX code generator. In CXX's Cargo 350integration this generated header has a path containing the crate name, the 351relative path of the Rust source file within the crate, and a `.rs.h` extension. 352 353```cpp,hidelines 354// src/blobstore.cc 355 356##include "cxx-demo/include/blobstore.h" 357##include "cxx-demo/src/main.rs.h" 358##include <functional> 359# 360# BlobstoreClient::BlobstoreClient() {} 361# 362# std::unique_ptr<BlobstoreClient> new_blobstore_client() { 363# return std::make_unique<BlobstoreClient>(); 364# } 365 366// Upload a new blob and return a blobid that serves as a handle to the blob. 367uint64_t BlobstoreClient::put(MultiBuf &buf) const { 368 // Traverse the caller's chunk iterator. 369 std::string contents; 370 while (true) { 371 auto chunk = next_chunk(buf); 372 if (chunk.size() == 0) { 373 break; 374 } 375 contents.append(reinterpret_cast<const char *>(chunk.data()), chunk.size()); 376 } 377 378 // Pretend we did something useful to persist the data. 379 auto blobid = std::hash<std::string>{}(contents); 380 return blobid; 381} 382``` 383 384This is now ready to use. :) 385 386```rust,noplayground 387// src/main.rs 388# 389# #[cxx::bridge] 390# mod ffi { 391# extern "Rust" { 392# type MultiBuf; 393# 394# fn next_chunk(buf: &mut MultiBuf) -> &[u8]; 395# } 396# 397# unsafe extern "C++" { 398# include!("cxx-demo/include/blobstore.h"); 399# 400# type BlobstoreClient; 401# 402# fn new_blobstore_client() -> UniquePtr<BlobstoreClient>; 403# fn put(&self, parts: &mut MultiBuf) -> u64; 404# } 405# } 406# 407# pub struct MultiBuf { 408# chunks: Vec<Vec<u8>>, 409# pos: usize, 410# } 411# pub fn next_chunk(buf: &mut MultiBuf) -> &[u8] { 412# let next = buf.chunks.get(buf.pos); 413# buf.pos += 1; 414# next.map_or(&[], Vec::as_slice) 415# } 416 417fn main() { 418 let client = ffi::new_blobstore_client(); 419 420 // Upload a blob. 421 let chunks = vec![b"fearless".to_vec(), b"concurrency".to_vec()]; 422 let mut buf = MultiBuf { chunks, pos: 0 }; 423 let blobid = client.put(&mut buf); 424 println!("blobid = {}", blobid); 425} 426``` 427 428```console 429cxx-demo$ cargo run 430 Compiling cxx-demo v0.1.0 431 Finished dev [unoptimized + debuginfo] target(s) in 0.41s 432 Running `target/debug/cxx-demo` 433 434blobid = 9851996977040795552 435``` 436 437## Interlude: What gets generated? 438 439For the curious, it's easy to look behind the scenes at what CXX has done to 440make these function calls work. You shouldn't need to do this during normal 441usage of CXX, but for the purpose of this tutorial it can be educative. 442 443CXX comprises *two* code generators: a Rust one (which is the cxx::bridge 444attribute procedural macro) and a C++ one. 445 446### Rust generated code 447 448It's easiest to view the output of the procedural macro by installing 449[cargo-expand]. Then run `cargo expand ::ffi` to macro-expand the `mod ffi` 450module. 451 452[cargo-expand]: https://github.com/dtolnay/cargo-expand 453 454```console 455cxx-demo$ cargo install cargo-expand 456cxx-demo$ cargo expand ::ffi 457``` 458 459You'll see some deeply unpleasant code involving `#[repr(C)]`, `#[link_name]`, 460and `#[export_name]`. 461 462### C++ generated code 463 464For debugging convenience, `cxx_build` links all generated C++ code into Cargo's 465target directory under *target/cxxbridge/*. 466 467```console 468cxx-demo$ exa -T target/cxxbridge/ 469target/cxxbridge 470├── cxx-demo 471│ └── src 472│ ├── main.rs.cc -> ../../../debug/build/cxx-demo-11c6f678ce5c3437/out/cxxbridge/sources/cxx-demo/src/main.rs.cc 473│ └── main.rs.h -> ../../../debug/build/cxx-demo-11c6f678ce5c3437/out/cxxbridge/include/cxx-demo/src/main.rs.h 474└── rust 475 └── cxx.h -> ~/.cargo/registry/src/github.com-1ecc6299db9ec823/cxx-1.0.0/include/cxx.h 476``` 477 478In those files you'll see declarations or templates of any CXX Rust types 479present in your language boundary (like `rust::Slice<T>` for `&[T]`) and `extern 480"C"` signatures corresponding to your extern functions. 481 482If it fits your workflow better, the CXX C++ code generator is also available as 483a standalone executable which outputs generated code to stdout. 484 485```console 486cxx-demo$ cargo install cxxbridge-cmd 487cxx-demo$ cxxbridge src/main.rs 488``` 489 490## Shared data structures 491 492So far the calls in both directions above only used **opaque types**, not 493**shared structs**. 494 495Shared structs are data structures whose complete definition is visible to both 496languages, making it possible to pass them by value across the language 497boundary. Shared structs translate to a C++ aggregate-initialization compatible 498struct exactly matching the layout of the Rust one. 499 500As the last step of this demo, we'll use a shared struct `BlobMetadata` to pass 501metadata about blobs between our Rust application and C++ blobstore client. 502 503```rust,noplayground 504// src/main.rs 505 506#[cxx::bridge] 507mod ffi { 508 struct BlobMetadata { 509 size: usize, 510 tags: Vec<String>, 511 } 512 513 extern "Rust" { 514 // ... 515# type MultiBuf; 516# 517# fn next_chunk(buf: &mut MultiBuf) -> &[u8]; 518 } 519 520 unsafe extern "C++" { 521 // ... 522# include!("cxx-demo/include/blobstore.h"); 523# 524# type BlobstoreClient; 525# 526# fn new_blobstore_client() -> UniquePtr<BlobstoreClient>; 527# fn put(&self, parts: &mut MultiBuf) -> u64; 528 fn tag(&self, blobid: u64, tag: &str); 529 fn metadata(&self, blobid: u64) -> BlobMetadata; 530 } 531} 532# 533# pub struct MultiBuf { 534# chunks: Vec<Vec<u8>>, 535# pos: usize, 536# } 537# pub fn next_chunk(buf: &mut MultiBuf) -> &[u8] { 538# let next = buf.chunks.get(buf.pos); 539# buf.pos += 1; 540# next.map_or(&[], Vec::as_slice) 541# } 542 543fn main() { 544 let client = ffi::new_blobstore_client(); 545 546 // Upload a blob. 547 let chunks = vec![b"fearless".to_vec(), b"concurrency".to_vec()]; 548 let mut buf = MultiBuf { chunks, pos: 0 }; 549 let blobid = client.put(&mut buf); 550 println!("blobid = {}", blobid); 551 552 // Add a tag. 553 client.tag(blobid, "rust"); 554 555 // Read back the tags. 556 let metadata = client.metadata(blobid); 557 println!("tags = {:?}", metadata.tags); 558} 559``` 560 561```cpp,hidelines 562// include/blobstore.h 563# 564# #pragma once 565# #include "rust/cxx.h" 566# #include <memory> 567 568struct MultiBuf; 569struct BlobMetadata; 570 571class BlobstoreClient { 572public: 573 BlobstoreClient(); 574 uint64_t put(MultiBuf &buf) const; 575 void tag(uint64_t blobid, rust::Str tag) const; 576 BlobMetadata metadata(uint64_t blobid) const; 577 578private: 579 class impl; 580 std::shared_ptr<impl> impl; 581}; 582# 583# std::unique_ptr<BlobstoreClient> new_blobstore_client(); 584``` 585 586```cpp,hidelines 587// src/blobstore.cc 588 589##include "cxx-demo/include/blobstore.h" 590##include "cxx-demo/src/main.rs.h" 591##include <algorithm> 592##include <functional> 593##include <set> 594##include <string> 595##include <unordered_map> 596 597// Toy implementation of an in-memory blobstore. 598// 599// In reality the implementation of BlobstoreClient could be a large 600// complex C++ library. 601class BlobstoreClient::impl { 602 friend BlobstoreClient; 603 using Blob = struct { 604 std::string data; 605 std::set<std::string> tags; 606 }; 607 std::unordered_map<uint64_t, Blob> blobs; 608}; 609 610BlobstoreClient::BlobstoreClient() : impl(new class BlobstoreClient::impl) {} 611# 612# // Upload a new blob and return a blobid that serves as a handle to the blob. 613# uint64_t BlobstoreClient::put(MultiBuf &buf) const { 614# // Traverse the caller's chunk iterator. 615# std::string contents; 616# while (true) { 617# auto chunk = next_chunk(buf); 618# if (chunk.size() == 0) { 619# break; 620# } 621# contents.append(reinterpret_cast<const char *>(chunk.data()), chunk.size()); 622# } 623# 624# // Insert into map and provide caller the handle. 625# auto blobid = std::hash<std::string>{}(contents); 626# impl->blobs[blobid] = {std::move(contents), {}}; 627# return blobid; 628# } 629 630// Add tag to an existing blob. 631void BlobstoreClient::tag(uint64_t blobid, rust::Str tag) const { 632 impl->blobs[blobid].tags.emplace(tag); 633} 634 635// Retrieve metadata about a blob. 636BlobMetadata BlobstoreClient::metadata(uint64_t blobid) const { 637 BlobMetadata metadata{}; 638 auto blob = impl->blobs.find(blobid); 639 if (blob != impl->blobs.end()) { 640 metadata.size = blob->second.data.size(); 641 std::for_each(blob->second.tags.cbegin(), blob->second.tags.cend(), 642 [&](auto &t) { metadata.tags.emplace_back(t); }); 643 } 644 return metadata; 645} 646# 647# std::unique_ptr<BlobstoreClient> new_blobstore_client() { 648# return std::make_unique<BlobstoreClient>(); 649# } 650``` 651 652```console 653cxx-demo$ cargo run 654 Running `target/debug/cxx-demo` 655 656blobid = 9851996977040795552 657tags = ["rust"] 658``` 659 660*You've now seen all the code involved in the tutorial. It's available all 661together in runnable form in the* demo *directory of 662<https://github.com/dtolnay/cxx>. You can run it directly without stepping 663through the steps above by running `cargo run` from that directory.* 664 665<br> 666 667# Takeaways 668 669The key contribution of CXX is it gives you Rust–C++ interop in which 670*all* of the Rust side of the code you write *really* looks like you are just 671writing normal Rust, and the C++ side *really* looks like you are just writing 672normal C++. 673 674You've seen in this tutorial that none of the code involved feels like C or like 675the usual perilous "FFI glue" prone to leaks or memory safety flaws. 676 677An expressive system of opaque types, shared types, and key standard library 678type bindings enables API design on the language boundary that captures the 679proper ownership and borrowing contracts of the interface. 680 681CXX plays to the strengths of the Rust type system *and* C++ type system *and* 682the programmer's intuitions. An individual working on the C++ side without a 683Rust background, or the Rust side without a C++ background, will be able to 684apply all their usual intuitions and best practices about development in their 685language to maintain a correct FFI. 686 687<br><br> 688