Skip to content

Add buffa-remote-derive: ProtoString/ProtoBytes/ProtoList/ProtoBox/MapStorage for remote types#251

Open
rsd-darshan wants to merge 2 commits into
anthropics:mainfrom
rsd-darshan:feat/remote-derive
Open

Add buffa-remote-derive: ProtoString/ProtoBytes/ProtoList/ProtoBox/MapStorage for remote types#251
rsd-darshan wants to merge 2 commits into
anthropics:mainfrom
rsd-darshan:feat/remote-derive

Conversation

@rsd-darshan

@rsd-darshan rsd-darshan commented Jun 27, 2026

Copy link
Copy Markdown
Contributor

Closes #212 — a #[derive(...)] #[buffa(remote = ...)] macro that generates the newtype boilerplate (Deref, AsRef/DerefMut, the relevant From impls, and the buffa trait itself) needed to use a foreign type as a custom owned field type.

Two tiers, one crate

ProtoString, ProtoBytes, ProtoList — the remote type's existing supertrait bounds (From<&str>, From<Vec<u8>>, FromIterator<T>, etc.) give the macro everything it needs; no extra configuration beyond naming the remote type.

#[derive(Clone, PartialEq, Default, Debug, buffa_remote_derive::ProtoString)]
#[buffa(remote = ecow::EcoString)]
pub struct MyEcoString(pub ecow::EcoString);

ProtoBox, MapStorage — their reference newtypes (examples/custom-types/src/types/small_box.rs, index_map.rs) call inherent methods on the remote type (smallbox::SmallBox::into_inner(), indexmap::IndexMap::insert()), not trait methods, so the macro can't synthesize the call purely from bounds. These default to the conventional method names (new/into_inner for pointers; len/insert/clear/iter for maps) with an attribute override for a remote type that names them differently:

#[derive(buffa_remote_derive::ProtoBox)]
#[buffa(remote = smallbox::SmallBox<T, smallbox::space::S4>)]
pub struct SmallBox<T>(pub smallbox::SmallBox<T, smallbox::space::S4>);

// A map whose insert method is named `put` instead:
#[derive(Clone, PartialEq, Debug, buffa_remote_derive::MapStorage)]
#[buffa(remote = ..., insert = MyMap::put)]
pub struct MyMapStorage<K, V>(pub MyMap<K, V>);

The override and the default are both rendered as UFCS-style calls (Type::method(&mut self.0, args...)), so a differently-named method with the same receiver shape drops in directly.

Notes

  • The #[buffa(remote = ...)] attribute is documentation, not codegen input — the macro always reads the field's actual type. Comparing the attribute's value against the field's type isn't possible from within a derive macro without resolving use imports, so the attribute is required (and must parse as a type, catching typos) but isn't checked for equality against the field.
  • ProtoList's generated clear reinitializes via Default::default() rather than retaining capacity — documented trade-off, acceptable per the trait's "where the underlying type allows" contract.
  • MapStorage's Default/FromIterator are required by the message codec, not by this derive (a derived impl would wrongly force K: Default/V: Default) — must be hand-written, same reasoning as ProtoList.

Testing

Integration tests against real foreign types (ecow::EcoString, smallvec::SmallVec, smallbox::SmallBox, indexmap::IndexMap, plus hand-rolled types with non-conventional method names to exercise the override paths), covering both tuple-struct and named-field newtype shapes, decode/encode round-trips, and generic element types without Eq/Ord.

task lint and task test pass clean across the workspace.

…types

Generates the newtype + Deref/AsRef/From/buffa-trait boilerplate that the
orphan rule otherwise forces consumers to hand-write when wrapping a foreign
type (e.g. ecow::EcoString) to satisfy a pluggable owned-type trait.

ProtoBox and MapStorage are out of scope here: their reference newtypes call
inherent methods (smallbox::SmallBox::into_inner, indexmap::IndexMap::insert)
rather than trait methods, so covering them needs a different,
attribute-driven design and will ship as a follow-up.
@github-actions

Copy link
Copy Markdown

All contributors have signed the CLA ✍️ ✅
Posted by the CLA Assistant Lite bot.

These two call inherent methods on the remote type (smallbox::SmallBox::
into_inner, indexmap::IndexMap::insert) rather than trait methods, so unlike
ProtoString/ProtoBytes/ProtoList they can't be synthesized purely from
supertrait bounds. Default to the conventional method names (new/into_inner
for pointers, len/insert/clear/iter for maps) with an attribute override for
remote types that name them differently.

Also renames RemoteField::remote_ty to field_ty, since it always holds the
wrapped field's actual type rather than the (documentation-only) attribute
value -- the old name was misleading about what's actually used for codegen.
@rsd-darshan rsd-darshan changed the title Add buffa-remote-derive: ProtoString/ProtoBytes/ProtoList for remote types Add buffa-remote-derive: ProtoString/ProtoBytes/ProtoList/ProtoBox/MapStorage for remote types Jun 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Custom owned types: #[buffa(remote = ...)] derive to remove the foreign-type newtype tax

1 participant