-
-
Notifications
You must be signed in to change notification settings - Fork 34
Description
Description
Allow reinterpret the generic type in builder.
For simple skip case:
#[derive(Builder)]
struct Foo<T> {
#[builder(skip)]
foo: Option<T>
}
impl<T1, S> FooBuilder<T1, S> {
// change generic T1 to T2
// here `foo` is name of the field
fn reinterpret_foo<T2>(self) -> FooBuilder<T2, S> {
todo!()
}
}For IsUnset case: reinterpet is only allowed for unset fields.
#[derive(Builder)]
struct Bar<T> {
bar: T
}
impl<T1, S> BarBuilder<T1, S>
where
S::Value: IsUnset
{
fn reintepret_bar<T2>(self) -> BarBuilder<T2, S> {
todo!()
}
}Implementation
The implementation is very trivial. For example, for skip case:
impl<T1, S> FooBuilder<T1, S>
{
#[allow(deprecated)] // <- generating deprecated warnings here
fn reinterpret_foo<T2>(self) -> FooBuilder<T2, S>
where {
let Self {
__unsafe_private_named,
..
} = self;
FooBuilder {
__unsafe_private_phantom: PhantomData,
__unsafe_private_named,
}
}
}But it requires destructing the builder and fires a deprecated warning. So I think it should better be implemented in bon itself rather than user side.
A use case
This method is useful for default generic implementation and user customizable trait impl inside builder struct.
Imagine the case that we have a raw API which takes a string input and returns many fields:
fn fake_query_user_info_raw(name: &str) -> serde_json::Value {
json!({
"name": name,
"age": 30,
"phone": "123456789",
"department": "Accounting",
// many more fields in real world
})
}We want to provide a wrapper that deserialize part of the fields in advance, but still allow the user to customize deserialization for more fields, using the #[serde(flatten] feature.
#[derive(Deserialize, Debug)]
struct Message<Extra = ()> {
// provided by our wrapper
name: String,
age: u32,
// user customizable
#[serde(flatten)]
extra: Extra,
}We want to provide wrapper API like below.
// The simple API call, no need to type generic
let simple = query_user_info().name("Alice").fetch();
dbg!(simple);
// Advanced users can provide custom deserialize
let advanced = query_user_info().name("Alice").extra::<PhoneExtra>().fetch();
dbg!(advanced);
#[derive(Deserialize, Debug)]
struct PhoneExtra {
phone: String,
}To craft such an API, we define the parameters with bon.
/// Arguments to the API.
#[derive(Builder)]
pub struct Args<Extra = ()> {
#[builder(into)]
name: String,
#[builder(skip)]
_extra: PhantomData<Extra>,
}and provide custom builder methods:
impl<Extra, S> ArgsBuilder<Extra, S> {
fn extra<NewExtra>(self) -> ArgsBuilder<NewExtra, S>
where {
self.reinterpret_extra::<NewExtra>()
}
}then wrappers:
fn query_user_info() -> ArgsBuilder {
Args::builder()
}
impl<Extra, S> ArgsBuilder<Extra, S>
where
S: args_builder::IsComplete,
{
fn fetch(self) -> Message<Extra>
where
Extra: DeserializeOwned,
{
let args = self.build();
let raw = fake_query_user_info_raw(&args.name);
serde_json::from_value(raw).unwrap()
}
}Why prefer such API?
As Rust does not allow default generic in function, users have to bind the generic in front,
let simple = query_user_info::<()>.name("Alice").fetch();This syntax poses more cognitive and typing burden for lightweight users, compared to the former one.
More to discussion
- the syntax: automatically generated for generic field, or requires explicit
#[builder(reinterpret)] - reinterpret bound:
#[builder(reinterpret(bound = T)]