Repository navigation
Description
use bon::builder;
#[builder(on(String, into))]
struct User {
name: String,
tags: Vec<String>,
alias: Option<String>,
description: Option<String>,
}
// Common part is here
let user = User::builder()
.name("Bon")
.tags(vec!["confectioner".to_owned()]);
// Differences are in each branch
let user = if user.GET_name().as_str() == "Bonny" {
user
.alias("Good girl")
.description("Knows mathematics 🐱")
.build()
} else {
user
.description("Skipped math classes 😿")
.build()
};real world usage
proptest allows to chain into hierarchy generated properties via prop_flat_map (with optimized shrinking retained).
so each prop_flat_map in chain depends on data to be generated from upper layer, uses that data to constraint its out put.
prop_flat_map(|prev| prev, prev+1 ).prop_flat_map(|(prev_0, prev_1|, ..., 0..prev_1).
so final test function gets do_test(prev_0, prev_1, prev_2).
so I thin of using builder on each stage and path only partial builder, instead of many parameters. but need access to already set fields. in the end I do call build and produce final data struct.
example without builder:
runner
.run(
&(
1..1_000_000u64,
0..(7u8 + 1),
0..(6u8 + 1),
0..(6u8 + 1),
Margins::valid_margins_strategy(),
)
.prop_flat_map(
|(
market_max_balance_in_units,
base_token_decimals,
quote_token_decimals,
market_price_decimals,
market_margins,
)| {
(
Just(market_max_balance_in_units),
Just(base_token_decimals),
Just(quote_token_decimals),
Just(market_price_decimals),
1u64..(market_max_balance_in_units + 1),
0..(base_token_decimals + 1),
1..(Decimals::from_repr(market_price_decimals)
.add_u8(2)
.scale_u64()
+ 1), // price_mantissa
Just(market_margins),
// TODO: variate markets and token counts
// TODO: variate Weigth and Margins - make sure that balance of user is inverse to this
// TODO: variate many makers vs one taker (ensure full fill always)
// TODO: pepr market
// TODO: variate limit via size/price/quote (use inverse formuals to set limit correct)
// TODO: variate fill types as long as they lead to same results (full fills)
// TODO: extend to partial fill results (will lead to test running very long time, need to configure test runner) - so some amounts stay on balance
)
},
),
|(
market_max_balance_in_units,
base_token_decimals,
quote_token_decimals,
market_price_decimals,
base_traded_in_size,
market_size_decimals,
price_mantissa,
market_margins,
)| {
//TODO: failed trades are fine, so somehow needed to count this
let _ = assert_different_market_setups_and_order_sizes_filled_well(
base_token_decimals,
quote_token_decimals,
market_size_decimals,
market_price_decimals,
market_max_balance_in_units,
base_traded_in_size,
price_mantissa,
market_margins,
);
Ok(())
},
)
.unwrap();https://altsysrq.github.io/proptest-book/proptest/tutorial/higher-order.html
A note for the community from the maintainers
Please vote on this issue by adding a 👍 reaction to help the maintainers with prioritizing it. You may add a comment describing your real use case related to this issue for us to better understand the problem domain.
Activity
dzmitry-lahoda commentedon Sep 8, 2024
I could have refactor to make bigger objects to consist of smaller, or write my own builders, or custom test inputs data objects.
Idea is that autogenerated builder may be useful for a while and prototype further actions, before doing all things manually(if at all ever).
dzmitry-lahoda commentedon Sep 8, 2024
Tried to quickly get things done, so as I see now
builder_implgenerates single builder type.So need some refactor to allow builder to generate many impls so each getter added iff value was set as witnessed by non generic parameter constrained specialezed.
Seems required changes are more broad than just for this single feature?
Veetaha commentedon Sep 8, 2024
Let's discuss the design for this feature first.
First of all, I expect adding getters is not something that everyone needs. Having them generated additionally will increase the compilation time (we have crates where there are ~320 structs with builders, where compile time is important).
So getters should definitely be an
opt-infeature. Then... How do we opt-in? There should be some attribute that enables getters for specific members. There are various different ways to generate getters:get_{member}(&self)get_{member}_mut(&mut self)into_{member}(self)Then what about
Copytypes? There should probably be a way to return the value byCopy, there should be a config for this as well.For
Optiontypes there should also be a way to return anOption<&T>instead of&Option<T>.For
Dereftypes there should be an attribute that configures returning&<T as Deref>::TargetThen there is probably a use case where one would like to deconstruct the builder into raw parts entirely. In this case, it would be cool if Rust had anonymous structs (rust-lang/rfcs#2584), but alas there aren't, so this would need to be a workaround. The macro should generate some projection struct that can hold any state of the members (set or unset).
In general, this looks like a big feature that will evolve substantially. If you'd like to have some subset of this feature quickly, I'm fine to have it but granted that it's added under an
unstablecargo feature flag, and it's opt-in via attributes.My first naive take at this would be replicating some of the API of
getsetand improving it over time under anunstablefeature gate. The config should probalby be by-member. If one needs to generate setters for all members at once they can use#[builder(on(_, ...))]to configure it at the top-leveldzmitry-lahoda commentedon Sep 8, 2024
Subset like this
Enabling only for specific fields sounds great, because people can pick exact places where common splits in shared partial builder happen (likely will compose nice with groups - so splits can be made around these points - as i do in manual builder anyway) with proper Option handling.
Readonly pub reference only. For mutation people use builder. Feature only for comparison and decision making, not for getting clone of value out.
#[builder(get)].Questions
Currently as I see I can set once, rls shows me I can set value second time, but compilation fails.
So is it possible to avoid showing me setter if I have already set?
Asking because for getter, I need to show getter iff I have already set.
What are options here?
What about life time? My assumption is that holding reference to property will not prevent setting other fields. Any changes envisioned here?
Veetaha commentedon Sep 8, 2024
I don't think so. I think it's more of an issue with rust-analyzer because it doesn't take into account trait bounds when showing completions. Maybe there is a way to work around that but I don't think it's worth the effort at least now. We have a readable compile error in case the field is set the second time. This is good enough.
Especially, I want to keep the current design where there is a single impl block for the builder. This is because it makes the
rustdocoutput much more cleaner and removes a lot of noise from the generated docs.Unfortunately, Rust doesn't work that way. The getter will take
&selfas an input, and return&MemberType. It means while the&MemberTypereference returned by the getter is alive no mutations toselfcan happen (especially consuming self).dzmitry-lahoda commentedon Sep 8, 2024
i need to dig into this design. never new it is possible to do without several impls.
so here unsafe needed.
for sure no references could be hold if we when build is called, that is fine.
also double set same field prevented.
so after field is set, it is set. fine to continue mutate rest of builder state and hold reference to old state, so just need to use unsafe internally within well known safe patter, same as array split into 2 reference (one is mut and other not, as long as slices do not overlap).
so need to check if this #97 desing works well with getters.
dzmitry-lahoda commentedon Sep 8, 2024
So I guess next step could be send proof of concept of manually modified expand of bon builder doing Getter and #97 ?
Veetaha commentedon Sep 8, 2024
That is currently a deal breaker for me. There is no unsafe code in
bonat this point and I don't want to start adding it right now. Especially in the context of getters. I don't think there is actually any reason to use unsafe for getters.Better if you could provide an example case that solves some particular problem where you'd think
unsafeis fine.dzmitry-lahoda commentedon Sep 8, 2024
what about defaults?
simplest is not to have getter for default which was not set explicitly.
any other options?
Veetaha commentedon Sep 8, 2024
Yes, uny unset member should have no getter generated for it. We have a guarantee that default values are evaluated lazily only when the final
build()orcall()method is invoked. This way users don't need to pay the cost of evaluating the default value when they just create the builder and especially if they intend set a non-default value for the field with a setterdzmitry-lahoda commentedon Sep 8, 2024
@Veetaha i am trying to thing up getter code to look like.
For example this simple builder expanded https://github.com/dzmitry-lahoda-forks/bon/blob/dz/2/bon-get/src/main.rs
As I see specialization of setter is done via return value.
Unfortunately I do not have return value in just field value to return.
Any ideas how to make it nice?
I tinkering next:
So is it possible at all to keep single impl or go right away with many?
dzmitry-lahoda commentedon Sep 8, 2024
Mat be
pub trait IsSet {}trait? Is goal to avoid many impls in expanded code?dzmitry-lahoda commentedon Sep 8, 2024
with
dzmitry-lahoda commentedon Sep 8, 2024
@Veetaha what about String vs str? I feel would not change String to str? So will return &String.
Veetaha commentedon Sep 8, 2024
What return value do you mean? Could you give more context or examples?
I'd like to keep a single impl block. We have
Unset<Required/Optional>/Set<T>types for which we can define new traits to get their state in a generic way.For example an
IsSettrait that is only implemented for theSet<T>and has a method to get the inner&Tvalue.I wonder what
getsethas for this. Is there a way to configure something likeget(deref)ingetset?dzmitry-lahoda commentedon Sep 9, 2024
From remaining questions I have only deref, as I see getset does not do it jbaublitz/getset#35 . So it can be added later if needed. Things like copy/deref can be added later.
dzmitry-lahoda commentedon Sep 15, 2024
please reopen when feature would have more broad usage.