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##include <string> 360# 361# BlobstoreClient::BlobstoreClient() {} 362# 363# std::unique_ptr<BlobstoreClient> new_blobstore_client() { 364# return std::make_unique<BlobstoreClient>(); 365# } 366 367// Upload a new blob and return a blobid that serves as a handle to the blob. 368uint64_t BlobstoreClient::put(MultiBuf &buf) const { 369 // Traverse the caller's chunk iterator. 370 std::string contents; 371 while (true) { 372 auto chunk = next_chunk(buf); 373 if (chunk.size() == 0) { 374 break; 375 } 376 contents.append(reinterpret_cast<const char *>(chunk.data()), chunk.size()); 377 } 378 379 // Pretend we did something useful to persist the data. 380 auto blobid = std::hash<std::string>{}(contents); 381 return blobid; 382} 383``` 384 385This is now ready to use. :) 386 387```rust,noplayground 388// src/main.rs 389# 390# #[cxx::bridge] 391# mod ffi { 392# extern "Rust" { 393# type MultiBuf; 394# 395# fn next_chunk(buf: &mut MultiBuf) -> &[u8]; 396# } 397# 398# unsafe extern "C++" { 399# include!("cxx-demo/include/blobstore.h"); 400# 401# type BlobstoreClient; 402# 403# fn new_blobstore_client() -> UniquePtr<BlobstoreClient>; 404# fn put(&self, parts: &mut MultiBuf) -> u64; 405# } 406# } 407# 408# pub struct MultiBuf { 409# chunks: Vec<Vec<u8>>, 410# pos: usize, 411# } 412# pub fn next_chunk(buf: &mut MultiBuf) -> &[u8] { 413# let next = buf.chunks.get(buf.pos); 414# buf.pos += 1; 415# next.map_or(&[], Vec::as_slice) 416# } 417 418fn main() { 419 let client = ffi::new_blobstore_client(); 420 421 // Upload a blob. 422 let chunks = vec![b"fearless".to_vec(), b"concurrency".to_vec()]; 423 let mut buf = MultiBuf { chunks, pos: 0 }; 424 let blobid = client.put(&mut buf); 425 println!("blobid = {}", blobid); 426} 427``` 428 429```console 430cxx-demo$ cargo run 431 Compiling cxx-demo v0.1.0 432 Finished dev [unoptimized + debuginfo] target(s) in 0.41s 433 Running `target/debug/cxx-demo` 434 435blobid = 9851996977040795552 436``` 437 438## Interlude: What gets generated? 439 440For the curious, it's easy to look behind the scenes at what CXX has done to 441make these function calls work. You shouldn't need to do this during normal 442usage of CXX, but for the purpose of this tutorial it can be educative. 443 444CXX comprises *two* code generators: a Rust one (which is the cxx::bridge 445attribute procedural macro) and a C++ one. 446 447### Rust generated code 448 449It's easiest to view the output of the procedural macro by installing 450[cargo-expand]. Then run `cargo expand ::ffi` to macro-expand the `mod ffi` 451module. 452 453[cargo-expand]: https://github.com/dtolnay/cargo-expand 454 455```console 456cxx-demo$ cargo install cargo-expand 457cxx-demo$ cargo expand ::ffi 458``` 459 460You'll see some deeply unpleasant code involving `#[repr(C)]`, `#[link_name]`, 461and `#[export_name]`. 462 463### C++ generated code 464 465For debugging convenience, `cxx_build` links all generated C++ code into Cargo's 466target directory under *target/cxxbridge/*. 467 468```console 469cxx-demo$ exa -T target/cxxbridge/ 470target/cxxbridge 471├── cxx-demo 472│ └── src 473│ ├── main.rs.cc -> ../../../debug/build/cxx-demo-11c6f678ce5c3437/out/cxxbridge/sources/cxx-demo/src/main.rs.cc 474│ └── main.rs.h -> ../../../debug/build/cxx-demo-11c6f678ce5c3437/out/cxxbridge/include/cxx-demo/src/main.rs.h 475└── rust 476 └── cxx.h -> ~/.cargo/registry/src/github.com-1ecc6299db9ec823/cxx-1.0.0/include/cxx.h 477``` 478 479In those files you'll see declarations or templates of any CXX Rust types 480present in your language boundary (like `rust::Slice<T>` for `&[T]`) and `extern 481"C"` signatures corresponding to your extern functions. 482 483If it fits your workflow better, the CXX C++ code generator is also available as 484a standalone executable which outputs generated code to stdout. 485 486```console 487cxx-demo$ cargo install cxxbridge-cmd 488cxx-demo$ cxxbridge src/main.rs 489``` 490 491## Shared data structures 492 493So far the calls in both directions above only used **opaque types**, not 494**shared structs**. 495 496Shared structs are data structures whose complete definition is visible to both 497languages, making it possible to pass them by value across the language 498boundary. Shared structs translate to a C++ aggregate-initialization compatible 499struct exactly matching the layout of the Rust one. 500 501As the last step of this demo, we'll use a shared struct `BlobMetadata` to pass 502metadata about blobs between our Rust application and C++ blobstore client. 503 504```rust,noplayground 505// src/main.rs 506 507#[cxx::bridge] 508mod ffi { 509 struct BlobMetadata { 510 size: usize, 511 tags: Vec<String>, 512 } 513 514 extern "Rust" { 515 // ... 516# type MultiBuf; 517# 518# fn next_chunk(buf: &mut MultiBuf) -> &[u8]; 519 } 520 521 unsafe extern "C++" { 522 // ... 523# include!("cxx-demo/include/blobstore.h"); 524# 525# type BlobstoreClient; 526# 527# fn new_blobstore_client() -> UniquePtr<BlobstoreClient>; 528# fn put(&self, parts: &mut MultiBuf) -> u64; 529 fn tag(&self, blobid: u64, tag: &str); 530 fn metadata(&self, blobid: u64) -> BlobMetadata; 531 } 532} 533# 534# pub struct MultiBuf { 535# chunks: Vec<Vec<u8>>, 536# pos: usize, 537# } 538# pub fn next_chunk(buf: &mut MultiBuf) -> &[u8] { 539# let next = buf.chunks.get(buf.pos); 540# buf.pos += 1; 541# next.map_or(&[], Vec::as_slice) 542# } 543 544fn main() { 545 let client = ffi::new_blobstore_client(); 546 547 // Upload a blob. 548 let chunks = vec![b"fearless".to_vec(), b"concurrency".to_vec()]; 549 let mut buf = MultiBuf { chunks, pos: 0 }; 550 let blobid = client.put(&mut buf); 551 println!("blobid = {}", blobid); 552 553 // Add a tag. 554 client.tag(blobid, "rust"); 555 556 // Read back the tags. 557 let metadata = client.metadata(blobid); 558 println!("tags = {:?}", metadata.tags); 559} 560``` 561 562```cpp,hidelines 563// include/blobstore.h 564 565##pragma once 566##include "rust/cxx.h" 567# #include <memory> 568 569struct MultiBuf; 570struct BlobMetadata; 571 572class BlobstoreClient { 573public: 574 BlobstoreClient(); 575 uint64_t put(MultiBuf &buf) const; 576 void tag(uint64_t blobid, rust::Str tag) const; 577 BlobMetadata metadata(uint64_t blobid) const; 578 579private: 580 class impl; 581 std::shared_ptr<impl> impl; 582}; 583# 584# std::unique_ptr<BlobstoreClient> new_blobstore_client(); 585``` 586 587```cpp,hidelines 588// src/blobstore.cc 589 590##include "cxx-demo/include/blobstore.h" 591##include "cxx-demo/src/main.rs.h" 592##include <algorithm> 593##include <functional> 594##include <set> 595##include <string> 596##include <unordered_map> 597 598// Toy implementation of an in-memory blobstore. 599// 600// In reality the implementation of BlobstoreClient could be a large 601// complex C++ library. 602class BlobstoreClient::impl { 603 friend BlobstoreClient; 604 using Blob = struct { 605 std::string data; 606 std::set<std::string> tags; 607 }; 608 std::unordered_map<uint64_t, Blob> blobs; 609}; 610 611BlobstoreClient::BlobstoreClient() : impl(new class BlobstoreClient::impl) {} 612# 613# // Upload a new blob and return a blobid that serves as a handle to the blob. 614# uint64_t BlobstoreClient::put(MultiBuf &buf) const { 615# // Traverse the caller's chunk iterator. 616# std::string contents; 617# while (true) { 618# auto chunk = next_chunk(buf); 619# if (chunk.size() == 0) { 620# break; 621# } 622# contents.append(reinterpret_cast<const char *>(chunk.data()), chunk.size()); 623# } 624# 625# // Insert into map and provide caller the handle. 626# auto blobid = std::hash<std::string>{}(contents); 627# impl->blobs[blobid] = {std::move(contents), {}}; 628# return blobid; 629# } 630 631// Add tag to an existing blob. 632void BlobstoreClient::tag(uint64_t blobid, rust::Str tag) const { 633 impl->blobs[blobid].tags.emplace(tag); 634} 635 636// Retrieve metadata about a blob. 637BlobMetadata BlobstoreClient::metadata(uint64_t blobid) const { 638 BlobMetadata metadata{}; 639 auto blob = impl->blobs.find(blobid); 640 if (blob != impl->blobs.end()) { 641 metadata.size = blob->second.data.size(); 642 std::for_each(blob->second.tags.cbegin(), blob->second.tags.cend(), 643 [&](auto &t) { metadata.tags.emplace_back(t); }); 644 } 645 return metadata; 646} 647# 648# std::unique_ptr<BlobstoreClient> new_blobstore_client() { 649# return std::make_unique<BlobstoreClient>(); 650# } 651``` 652 653```console 654cxx-demo$ cargo run 655 Running `target/debug/cxx-demo` 656 657blobid = 9851996977040795552 658tags = ["rust"] 659``` 660 661*You've now seen all the code involved in the tutorial. It's available all 662together in runnable form in the* demo *directory of 663<https://github.com/dtolnay/cxx>. You can run it directly without stepping 664through the steps above by running `cargo run` from that directory.* 665 666<br> 667 668# Takeaways 669 670The key contribution of CXX is it gives you Rust–C++ interop in which 671*all* of the Rust side of the code you write *really* looks like you are just 672writing normal Rust, and the C++ side *really* looks like you are just writing 673normal C++. 674 675You've seen in this tutorial that none of the code involved feels like C or like 676the usual perilous "FFI glue" prone to leaks or memory safety flaws. 677 678An expressive system of opaque types, shared types, and key standard library 679type bindings enables API design on the language boundary that captures the 680proper ownership and borrowing contracts of the interface. 681 682CXX plays to the strengths of the Rust type system *and* C++ type system *and* 683the programmer's intuitions. An individual working on the C++ side without a 684Rust background, or the Rust side without a C++ background, will be able to 685apply all their usual intuitions and best practices about development in their 686language to maintain a correct FFI. 687 688<br><br> 689