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)]
Description
Allow reinterpret the generic type in builder.
For simple
skipcase:For
IsUnsetcase: reinterpet is only allowed for unset fields.Implementation
The implementation is very trivial. For example, for
skipcase:But it requires destructing the builder and fires a
deprecatedwarning. So I think it should better be implemented inbonitself 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:
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.We want to provide wrapper API like below.
To craft such an API, we define the parameters with
bon.and provide custom builder methods:
then wrappers:
Why prefer such API?
As Rust does not allow default generic in function, users have to bind the generic in front,
This syntax poses more cognitive and typing burden for lightweight users, compared to the former one.
More to discussion
#[builder(reinterpret)]#[builder(reinterpret(bound = T)]