Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,37 @@ let view = OwnedView::<PersonView>::decode(bytes)?;
println!("name: {}", view.name); // Deref, zero-copy, 'static + Send
```

**Generated code layout — parallel trees per kind:**

Ancillary generated types live in **kind-namespaced parallel trees** under a single reserved `__buffa::` parent at the package root, not interleaved with the owned messages. The `__buffa` prefix is reserved by codegen validation, so the kind trees can never collide with proto-derived module names like `message View {}` or `package foo.view`. The kind segment sits inside `__buffa::`; for kinds that modify other kinds (only `view` today), the modifier wraps the modified:

```text
<package>::<proto-path>::<ident> # owned messages / enums
<package>::__buffa::<kind>::<proto-path>::<ident> # ancillary kind
<package>::__buffa::<modifier>::<kind>::<proto-path>::<ident> # modifier wrapping kind (view of oneofs)
```

Current kinds are `view`, `oneofs`, and `ext`; only `view` is a modifier. Concretely, for proto package `my.pkg` containing message `Foo` with oneof `bar` and extension `baz`:

| Item | Generated path |
|----------------------------|------------------------------------------------------|
| Owned message struct | `my::pkg::Foo` |
| View message struct | `my::pkg::__buffa::view::FooView<'a>` |
| Oneof enum (owned) | `my::pkg::__buffa::oneofs::foo::Bar` |
| Oneof enum (view) | `my::pkg::__buffa::view::oneofs::foo::Bar<'a>` |
| File-level extension const | `my::pkg::__buffa::ext::BAZ` |

Two things to note:

- The **owner module** in the `oneofs::` tree (`foo` above) is snake-cased from the owner message name. The oneof enum keeps its PascalCase proto name (`Bar`) — no `Kind` suffix, no `View` suffix on view-of-oneof enums. The tree prefix (`oneofs::` vs `view::oneofs::`) disambiguates.
- **View message structs keep the `View` suffix** (`FooView<'a>`) even though they live in `view::`. This is a deliberate exception: users routinely import the owned type and the view type together (`use pkg::{Foo, __buffa::view::FooView}`) and a bare `View` would shadow too commonly.

This layout has three structural benefits:

1. **Collision-free.** A oneof whose owner name matches an existing nested type / enum / extension can't fight over the package-root namespace, because each lives in a different kind tree. The single `__buffa` reserved name is the only constraint codegen places on proto names.
2. **Predictable.** Every proto package emits exactly five sibling files per `.proto`: `<stem>.rs`, `<stem>.__view.rs`, `<stem>.__ext.rs`, `<stem>.__oneofs.rs`, `<stem>.__view_oneofs.rs` — empty-bodied when the kind has no content. `buffa-build` and the packaging plugin stitch these into `pub mod __buffa { pub mod view { … pub mod oneofs { … } } pub mod ext { … } pub mod oneofs { … } }` per package.
3. **Feature-gatable.** View and extension support are codegen options; disabling them simply leaves those trees empty. The structural layout is stable regardless.

### 3. MessageField\<T\> — Ergonomic Optional Messages

Prost uses `Option<Box<M>>` for optional message fields, which creates unwrapping ceremony everywhere:
Expand Down
6 changes: 5 additions & 1 deletion benchmarks/buffa/benches/protobuf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@ use buffa::{Message, MessageView};
use criterion::{criterion_group, criterion_main, Criterion, Throughput};
use serde::{de::DeserializeOwned, Serialize};

use bench_buffa::bench::__buffa::view::{
AnalyticsEventView, ApiResponseView, LogRecordView, MediaFrameView,
};
use bench_buffa::bench::*;
use bench_buffa::benchmarks::BenchmarkDataset;
use bench_buffa::proto3::__buffa::view::GoogleMessage1View;

fn load_dataset(data: &[u8]) -> BenchmarkDataset {
BenchmarkDataset::decode_from_slice(data).expect("failed to decode dataset")
Expand Down Expand Up @@ -180,7 +184,7 @@ fn bench_google_message1_view(c: &mut Criterion) {
group.bench_function("decode_view", |b| {
b.iter(|| {
for payload in &dataset.payload {
let view = bench_buffa::proto3::GoogleMessage1View::decode_view(payload).unwrap();
let view = GoogleMessage1View::decode_view(payload).unwrap();
criterion::black_box(&view);
}
});
Expand Down
27 changes: 24 additions & 3 deletions benchmarks/buffa/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,26 @@
//! Generated protobuf types for buffa benchmarks.

macro_rules! include_generated {
($stem:literal) => {
include!(concat!(env!("OUT_DIR"), "/", $stem, ".rs"));
#[allow(non_camel_case_types, unused_imports, dead_code)]
pub mod __buffa {
pub mod view {
include!(concat!(env!("OUT_DIR"), "/", $stem, ".__view.rs"));
pub mod oneofs {
include!(concat!(env!("OUT_DIR"), "/", $stem, ".__view_oneofs.rs"));
}
}
pub mod ext {
include!(concat!(env!("OUT_DIR"), "/", $stem, ".__ext.rs"));
}
pub mod oneofs {
include!(concat!(env!("OUT_DIR"), "/", $stem, ".__oneofs.rs"));
}
}
};
}

#[allow(
clippy::derivable_impls,
clippy::enum_variant_names,
Expand All @@ -10,7 +31,7 @@
dead_code
)]
pub mod bench {
include!(concat!(env!("OUT_DIR"), "/bench_messages.rs"));
include_generated!("bench_messages");
}

#[allow(
Expand All @@ -23,7 +44,7 @@ pub mod bench {
dead_code
)]
pub mod benchmarks {
include!(concat!(env!("OUT_DIR"), "/benchmarks.rs"));
include_generated!("benchmarks");
}

#[allow(
Expand All @@ -36,5 +57,5 @@ pub mod benchmarks {
dead_code
)]
pub mod proto3 {
include!(concat!(env!("OUT_DIR"), "/benchmark_message1_proto3.rs"));
include_generated!("benchmark_message1_proto3");
}
27 changes: 24 additions & 3 deletions benchmarks/gen-datasets/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,27 @@ use rand::{Rng, SeedableRng};
use std::fs;
use std::path::Path;

macro_rules! include_generated {
($stem:literal) => {
include!(concat!(env!("OUT_DIR"), "/", $stem, ".rs"));
#[allow(non_camel_case_types, unused_imports, dead_code)]
pub mod __buffa {
pub mod view {
include!(concat!(env!("OUT_DIR"), "/", $stem, ".__view.rs"));
pub mod oneofs {
include!(concat!(env!("OUT_DIR"), "/", $stem, ".__view_oneofs.rs"));
}
}
pub mod ext {
include!(concat!(env!("OUT_DIR"), "/", $stem, ".__ext.rs"));
}
pub mod oneofs {
include!(concat!(env!("OUT_DIR"), "/", $stem, ".__oneofs.rs"));
}
}
};
}

#[allow(
clippy::derivable_impls,
clippy::enum_variant_names,
Expand All @@ -18,7 +39,7 @@ use std::path::Path;
dead_code
)]
mod proto {
include!(concat!(env!("OUT_DIR"), "/bench_messages.rs"));
include_generated!("bench_messages");
}
#[allow(
clippy::derivable_impls,
Expand All @@ -30,10 +51,10 @@ mod proto {
dead_code
)]
mod dataset_proto {
include!(concat!(env!("OUT_DIR"), "/benchmarks.rs"));
include_generated!("benchmarks");
}

use proto::analytics_event::property::ValueOneof as Value;
use proto::__buffa::oneofs::analytics_event::property::Value;
use proto::analytics_event::{Nested, Property};
use proto::log_record::Context;
use proto::*;
Expand Down
Loading
Loading