Summary
Add a #[derive(...)] #[buffa(remote = "...")] macro (the serde(remote = ...) pattern) that generates the crate-local newtype and its forwarding impls needed to use a foreign type as a custom owned field type, collapsing today's hand-written boilerplate to one annotation.
Background
The owned Rust type for string/bytes (#206), repeated (#208), map (#210), and message-field/oneof pointers (#209) is now pluggable. A custom type implements a small buffa-owned trait (ProtoString / ProtoBytes / ProtoList / MapStorage / ProtoBox).
The friction is the orphan rule: a foreign type (e.g. ecow::EcoString, smallvec::SmallVec, indexmap::IndexMap) cannot implement a buffa-owned trait directly. The integrator must wrap it in a crate-local newtype and hand-write the trait impl plus the supporting impls (Deref, From both directions, FromIterator/PartialEq/serde/Arbitrary/ReflectList/ReflectMap as applicable). That's correct and consistent across the family, but it's ~30-50 lines of mechanical code per type. A type the integrator owns needs no newtype (foreign-trait-on-local-type is allowed), so this tax falls only on foreign types.
Proposal
A derive/attribute macro that emits the newtype and the forwarding impls from one annotation, e.g.:
#[derive(buffa::ProtoString)]
#[buffa(remote = "ecow::EcoString")]
pub struct MyEcoString(ecow::EcoString);
generating the trait impl + Deref + From (both directions) + the std derives + (feature-gated) serde / Arbitrary / reflection forwarders. One macro family can cover ProtoString / ProtoBytes / ProtoList / MapStorage / ProtoBox.
This is the long-tail counterpart to the existing curated newtype crate (buffa-smolstr): the crate stays the worked example, the macro generalizes it.
Notes / open questions
- New proc-macro crate (or a feature on an existing one). Keep it optional so non-custom users don't pay for it.
- Decide which forwarders are mandatory vs feature-gated (reflection is std-only; serde is JSON-only;
Arbitrary is fuzz-only).
- Explicitly not a blanket impl (e.g.
impl<T: AsRef<[u8]>> ProtoBytes for T): a blanket consumes the coherence space for a foundational trait and is the wrong trade — the macro generates per-type newtypes instead.
Follow-up to #156; pick up after #208 / #209 / #210 merge.
Summary
Add a
#[derive(...)] #[buffa(remote = "...")]macro (theserde(remote = ...)pattern) that generates the crate-local newtype and its forwarding impls needed to use a foreign type as a custom owned field type, collapsing today's hand-written boilerplate to one annotation.Background
The owned Rust type for
string/bytes(#206),repeated(#208),map(#210), and message-field/oneof pointers (#209) is now pluggable. A custom type implements a small buffa-owned trait (ProtoString/ProtoBytes/ProtoList/MapStorage/ProtoBox).The friction is the orphan rule: a foreign type (e.g.
ecow::EcoString,smallvec::SmallVec,indexmap::IndexMap) cannot implement a buffa-owned trait directly. The integrator must wrap it in a crate-local newtype and hand-write the trait impl plus the supporting impls (Deref,Fromboth directions,FromIterator/PartialEq/serde/Arbitrary/ReflectList/ReflectMapas applicable). That's correct and consistent across the family, but it's ~30-50 lines of mechanical code per type. A type the integrator owns needs no newtype (foreign-trait-on-local-type is allowed), so this tax falls only on foreign types.Proposal
A derive/attribute macro that emits the newtype and the forwarding impls from one annotation, e.g.:
generating the trait impl +
Deref+From(both directions) + the std derives + (feature-gated) serde /Arbitrary/ reflection forwarders. One macro family can coverProtoString/ProtoBytes/ProtoList/MapStorage/ProtoBox.This is the long-tail counterpart to the existing curated newtype crate (
buffa-smolstr): the crate stays the worked example, the macro generalizes it.Notes / open questions
Arbitraryis fuzz-only).impl<T: AsRef<[u8]>> ProtoBytes for T): a blanket consumes the coherence space for a foundational trait and is the wrong trade — the macro generates per-type newtypes instead.Follow-up to #156; pick up after #208 / #209 / #210 merge.