Switch intro example to blobstore client

This commit is contained in:
David Tolnay 2020-11-10 20:55:31 -08:00
parent 7614e77068
commit 4ca366fa57
No known key found for this signature in database
GPG Key ID: F9BA143B95FF6D82
10 changed files with 231 additions and 146 deletions

View File

@ -63,6 +63,12 @@ function calls Rust's `len()`.
## Example
In this example we are writing a Rust application that wishes to take advantage
of an existing C++ client for a large-file blobstore service. The blobstore
supports a `put` operation for a discontiguous buffer upload. For example we
might be uploading snapshots of a circular buffer which would tend to consist of
2 chunks, or fragments of a file spread across memory for some other reason.
A runnable version of this example is provided under the *demo* directory of
this repo. To try it out, run `cargo run` from that directory.
@ -70,49 +76,49 @@ this repo. To try it out, run `cargo run` from that directory.
#[cxx::bridge]
mod ffi {
// Any shared structs, whose fields will be visible to both languages.
struct SharedThing {
z: i32,
y: Box<ThingR>,
x: UniquePtr<ThingC>,
}
extern "C" {
// One or more headers with the matching C++ declarations. Our code
// generators don't read it but it gets #include'd and used in static
// assertions to ensure our picture of the FFI boundary is accurate.
include!("demo/include/demo.h");
// Zero or more opaque types which both languages can pass around but
// only C++ can see the fields.
type ThingC;
// Functions implemented in C++.
fn make_demo(appname: &str) -> UniquePtr<ThingC>;
fn get_name(thing: &ThingC) -> &CxxString;
fn do_thing(state: SharedThing);
struct BlobMetadata {
size: usize,
tags: Vec<String>,
}
extern "Rust" {
// Zero or more opaque types which both languages can pass around but
// only Rust can see the fields.
type ThingR;
type MultiBuf;
// Functions implemented in Rust.
fn print_r(r: &ThingR);
fn next_chunk(buf: &mut MultiBuf) -> &[u8];
}
extern "C++" {
// One or more headers with the matching C++ declarations. Our code
// generators don't read it but it gets #include'd and used in static
// assertions to ensure our picture of the FFI boundary is accurate.
include!("demo/include/blobstore.h");
// Zero or more opaque types which both languages can pass around but
// only C++ can see the fields.
type BlobstoreClient;
// Functions implemented in C++.
fn new_blobstore_client() -> UniquePtr<BlobstoreClient>;
fn put(&self, parts: &mut MultiBuf) -> u64;
fn tag(&self, blobid: u64, tag: &str);
fn metadata(&self, blobid: u64) -> BlobMetadata;
}
}
```
Now we simply provide C++ definitions of all the things in the `extern "C"`
block and Rust definitions of all the things in the `extern "Rust"` block, and
get to call back and forth safely.
Now we simply provide Rust definitions of all the things in the `extern "Rust"`
block and C++ definitions of all the things in the `extern "C++"` block, and get
to call back and forth safely.
Here are links to the complete set of source files involved in the demo:
- [demo/src/main.rs](demo/src/main.rs)
- [demo/build.rs](demo/build.rs)
- [demo/include/demo.h](demo/include/demo.h)
- [demo/src/demo.cc](demo/src/demo.cc)
- [demo/include/blobstore.h](demo/include/blobstore.h)
- [demo/src/blobstore.cc](demo/src/blobstore.cc)
To look at the code generated in both languages for the example by the CXX code
generators:

View File

@ -4,8 +4,8 @@ rust_binary(
name = "demo",
srcs = glob(["src/**/*.rs"]),
deps = [
":blobstore-sys",
":bridge",
":demo-sys",
"//:cxx",
],
)
@ -13,21 +13,21 @@ rust_binary(
rust_cxx_bridge(
name = "bridge",
src = "src/main.rs",
deps = [":demo-include"],
deps = [":blobstore-include"],
)
cxx_library(
name = "demo-sys",
srcs = ["src/demo.cc"],
name = "blobstore-sys",
srcs = ["src/blobstore.cc"],
compiler_flags = ["-std=c++14"],
deps = [
":blobstore-include",
":bridge/include",
":demo-include",
],
)
cxx_library(
name = "demo-include",
exported_headers = ["include/demo.h"],
name = "blobstore-include",
exported_headers = ["include/blobstore.h"],
deps = ["//:core"],
)

View File

@ -6,8 +6,8 @@ rust_binary(
name = "demo",
srcs = glob(["src/**/*.rs"]),
deps = [
":blobstore-sys",
":bridge",
":demo-sys",
"//:cxx",
],
)
@ -15,21 +15,21 @@ rust_binary(
rust_cxx_bridge(
name = "bridge",
src = "src/main.rs",
deps = [":demo-include"],
deps = [":blobstore-include"],
)
cc_library(
name = "demo-sys",
srcs = ["src/demo.cc"],
name = "blobstore-sys",
srcs = ["src/blobstore.cc"],
copts = ["-std=c++14"],
deps = [
":blobstore-include",
":bridge/include",
":demo-include",
],
)
cc_library(
name = "demo-include",
hdrs = ["include/demo.h"],
name = "blobstore-include",
hdrs = ["include/blobstore.h"],
deps = ["//:core"],
)

View File

@ -1,10 +1,10 @@
fn main() {
cxx_build::bridge("src/main.rs")
.file("src/demo.cc")
.file("src/blobstore.cc")
.flag_if_supported("-std=c++14")
.compile("cxxbridge-demo");
println!("cargo:rerun-if-changed=src/main.rs");
println!("cargo:rerun-if-changed=src/demo.cc");
println!("cargo:rerun-if-changed=include/demo.h");
println!("cargo:rerun-if-changed=src/blobstore.cc");
println!("cargo:rerun-if-changed=include/blobstore.h");
}

26
demo/include/blobstore.h Normal file
View File

@ -0,0 +1,26 @@
#pragma once
#include "rust/cxx.h"
#include <memory>
namespace org {
namespace blobstore {
struct MultiBuf;
struct BlobMetadata;
class BlobstoreClient {
public:
BlobstoreClient();
uint64_t put(MultiBuf &buf) const;
void tag(uint64_t blobid, rust::Str tag) const;
BlobMetadata metadata(uint64_t blobid) const;
private:
class impl;
std::shared_ptr<impl> impl;
};
std::unique_ptr<BlobstoreClient> new_blobstore_client();
} // namespace blobstore
} // namespace org

View File

@ -1,24 +0,0 @@
#pragma once
#include "rust/cxx.h"
#include <memory>
#include <string>
namespace org {
namespace example {
class ThingC {
public:
ThingC(std::string appname);
~ThingC();
std::string appname;
};
struct SharedThing;
std::unique_ptr<ThingC> make_demo(rust::Str appname);
const std::string &get_name(const ThingC &thing);
void do_thing(SharedThing state);
} // namespace example
} // namespace org

71
demo/src/blobstore.cc Normal file
View File

@ -0,0 +1,71 @@
#include "demo/include/blobstore.h"
#include "demo/src/main.rs.h"
#include <algorithm>
#include <functional>
#include <set>
#include <string>
#include <unordered_map>
namespace org {
namespace blobstore {
// Toy implementation of an in-memory blobstore.
//
// In reality the implementation of BlobstoreClient could be a large complex C++
// library.
class BlobstoreClient::impl {
friend BlobstoreClient;
using Blob = struct {
std::string data;
std::set<std::string> tags;
};
std::unordered_map<uint64_t, Blob> blobs;
};
BlobstoreClient::BlobstoreClient() : impl(new typename BlobstoreClient::impl) {}
// Upload a new blob and return a blobid that serves as a handle to the blob.
uint64_t BlobstoreClient::put(MultiBuf &buf) const {
std::string contents;
// Traverse the caller's chunk iterator.
//
// In reality there might be sophisticated batching of chunks and/or parallel
// upload implemented by the blobstore's C++ client.
while (true) {
auto chunk = next_chunk(buf);
if (chunk.size() == 0) {
break;
}
contents.append(reinterpret_cast<const char *>(chunk.data()), chunk.size());
}
// Insert into map and provide caller the handle.
auto blobid = std::hash<std::string>{}(contents);
impl->blobs[blobid] = {std::move(contents), {}};
return blobid;
}
// Add tag to an existing blob.
void BlobstoreClient::tag(uint64_t blobid, rust::Str tag) const {
impl->blobs[blobid].tags.emplace(tag);
}
// Retrieve metadata about a blob.
BlobMetadata BlobstoreClient::metadata(uint64_t blobid) const {
BlobMetadata metadata{};
auto blob = impl->blobs.find(blobid);
if (blob != impl->blobs.end()) {
metadata.size = blob->second.data.size();
std::for_each(blob->second.tags.begin(), blob->second.tags.end(),
[&](auto &t) { metadata.tags.emplace_back(t); });
}
return metadata;
}
std::unique_ptr<BlobstoreClient> new_blobstore_client() {
return std::make_unique<BlobstoreClient>();
}
} // namespace blobstore
} // namespace org

View File

@ -1,21 +0,0 @@
#include "demo/include/demo.h"
#include "demo/src/main.rs.h"
#include <iostream>
namespace org {
namespace example {
ThingC::ThingC(std::string appname) : appname(std::move(appname)) {}
ThingC::~ThingC() { std::cout << "done with ThingC" << std::endl; }
std::unique_ptr<ThingC> make_demo(rust::Str appname) {
return std::make_unique<ThingC>(std::string(appname));
}
const std::string &get_name(const ThingC &thing) { return thing.appname; }
void do_thing(SharedThing state) { print_r(*state.y); }
} // namespace example
} // namespace org

View File

@ -1,39 +1,59 @@
#[cxx::bridge(namespace = "org::example")]
#[cxx::bridge(namespace = "org::blobstore")]
mod ffi {
struct SharedThing {
z: i32,
y: Box<ThingR>,
x: UniquePtr<ThingC>,
}
extern "C" {
include!("demo/include/demo.h");
type ThingC;
fn make_demo(appname: &str) -> UniquePtr<ThingC>;
fn get_name(thing: &ThingC) -> &CxxString;
fn do_thing(state: SharedThing);
// Shared structs with fields visible to both languages.
struct BlobMetadata {
size: usize,
tags: Vec<String>,
}
// Rust types and signatures exposed to C++.
extern "Rust" {
type ThingR;
fn print_r(r: &ThingR);
type MultiBuf;
fn next_chunk(buf: &mut MultiBuf) -> &[u8];
}
// C++ types and signatures exposed to Rust.
extern "C++" {
include!("demo/include/blobstore.h");
type BlobstoreClient;
fn new_blobstore_client() -> UniquePtr<BlobstoreClient>;
fn put(&self, parts: &mut MultiBuf) -> u64;
fn tag(&self, blobid: u64, tag: &str);
fn metadata(&self, blobid: u64) -> BlobMetadata;
}
}
pub struct ThingR(usize);
fn print_r(r: &ThingR) {
println!("called back with r={}", r.0);
// An iterator over contiguous chunks of a discontiguous file object.
//
// Toy implementation uses a Vec<Vec<u8>> but in reality this might be iterating
// over some more complex Rust data structure like a rope, or maybe loading
// chunks lazily from somewhere.
pub struct MultiBuf {
chunks: Vec<Vec<u8>>,
pos: usize,
}
pub fn next_chunk(buf: &mut MultiBuf) -> &[u8] {
let next = buf.chunks.get(buf.pos);
buf.pos += 1;
next.map(Vec::as_slice).unwrap_or(&[])
}
fn main() {
let x = ffi::make_demo("demo of cxx::bridge");
println!("this is a {}", ffi::get_name(x.as_ref().unwrap()));
let client = ffi::new_blobstore_client();
ffi::do_thing(ffi::SharedThing {
z: 222,
y: Box::new(ThingR(333)),
x,
});
// Upload a blob.
let chunks = vec![b"fearless".to_vec(), b"concurrency".to_vec()];
let mut buf = MultiBuf { chunks, pos: 0 };
let blobid = client.put(&mut buf);
println!("blobid = {}", blobid);
// Add a tag.
client.tag(blobid, "rust");
// Read back the tags.
let metadata = client.metadata(blobid);
println!("tags = {:?}", metadata.tags);
}

View File

@ -57,6 +57,13 @@
//!
//! # Example
//!
//! In this example we are writing a Rust application that wishes to take
//! advantage of an existing C++ client for a large-file blobstore service. The
//! blobstore supports a `put` operation for a discontiguous buffer upload. For
//! example we might be uploading snapshots of a circular buffer which would
//! tend to consist of 2 chunks, or fragments of a file spread across memory for
//! some other reason.
//!
//! A runnable version of this example is provided under the *demo* directory of
//! <https://github.com/dtolnay/cxx>. To try it out, run `cargo run` from that
//! directory.
@ -65,57 +72,57 @@
//! #[cxx::bridge]
//! mod ffi {
//! // Any shared structs, whose fields will be visible to both languages.
//! struct SharedThing {
//! z: i32,
//! y: Box<ThingR>,
//! x: UniquePtr<ThingC>,
//! }
//!
//! extern "C" {
//! // One or more headers with the matching C++ declarations. Our code
//! // generators don't read it but it gets #include'd and used in static
//! // assertions to ensure our picture of the FFI boundary is accurate.
//! include!("demo/include/demo.h");
//!
//! // Zero or more opaque types which both languages can pass around but
//! // only C++ can see the fields.
//! type ThingC;
//!
//! // Functions implemented in C++.
//! fn make_demo(appname: &str) -> UniquePtr<ThingC>;
//! fn get_name(thing: &ThingC) -> &CxxString;
//! fn do_thing(state: SharedThing);
//! struct BlobMetadata {
//! size: usize,
//! tags: Vec<String>,
//! }
//!
//! extern "Rust" {
//! // Zero or more opaque types which both languages can pass around but
//! // only Rust can see the fields.
//! type ThingR;
//! type MultiBuf;
//!
//! // Functions implemented in Rust.
//! fn print_r(r: &ThingR);
//! fn next_chunk(buf: &mut MultiBuf) -> &[u8];
//! }
//!
//! extern "C++" {
//! // One or more headers with the matching C++ declarations. Our code
//! // generators don't read it but it gets #include'd and used in static
//! // assertions to ensure our picture of the FFI boundary is accurate.
//! include!("demo/include/blobstore.h");
//!
//! // Zero or more opaque types which both languages can pass around but
//! // only C++ can see the fields.
//! type BlobstoreClient;
//!
//! // Functions implemented in C++.
//! fn new_blobstore_client() -> UniquePtr<BlobstoreClient>;
//! fn put(&self, parts: &mut MultiBuf) -> u64;
//! fn tag(&self, blobid: u64, tag: &str);
//! fn metadata(&self, blobid: u64) -> BlobMetadata;
//! }
//! }
//! #
//! # pub struct ThingR(usize);
//! # pub struct MultiBuf;
//! #
//! # fn print_r(r: &ThingR) {
//! # println!("called back with r={}", r.0);
//! # fn next_chunk(_buf: &mut MultiBuf) -> &[u8] {
//! # unimplemented!()
//! # }
//! #
//! # fn main() {}
//! ```
//!
//! Now we simply provide C++ definitions of all the things in the `extern "C"`
//! block and Rust definitions of all the things in the `extern "Rust"` block,
//! and get to call back and forth safely.
//! Now we simply provide Rust definitions of all the things in the `extern
//! "Rust"` block and C++ definitions of all the things in the `extern "C++"`
//! block, and get to call back and forth safely.
//!
//! Here are links to the complete set of source files involved in the demo:
//!
//! - [demo/src/main.rs](https://github.com/dtolnay/cxx/blob/master/demo/src/main.rs)
//! - [demo/build.rs](https://github.com/dtolnay/cxx/blob/master/demo/build.rs)
//! - [demo/include/demo.h](https://github.com/dtolnay/cxx/blob/master/demo/include/demo.h)
//! - [demo/src/demo.cc](https://github.com/dtolnay/cxx/blob/master/demo/src/demo.cc)
//! - [demo/include/blobstore.h](https://github.com/dtolnay/cxx/blob/master/demo/include/blobstore.h)
//! - [demo/src/blobstore.cc](https://github.com/dtolnay/cxx/blob/master/demo/src/blobstore.cc)
//!
//! To look at the code generated in both languages for the example by the CXX
//! code generators: