option to completely customise input type of builder methods
I'd love to be able to completely customise the input type for builder methods, for situations where impl Into isn't enough.
For example, if I have a HashSet<T> as a struct field, it would be much nicer to accept an impl IntoIterator<Item = T> then have the buider method do input.into_iter().collect() rather than the builder method accepting impl Into<HashSet<T>> and the user having to do the rest.
Not sure how this could be implemented, there would have to be a way to specify the desired input type, and also perhaps pass in to the macro a function that goes from the input type to the type of the struct field. E.g:
#[builder]
struct ExampleStruct {
#[builder(input_type = impl IntoIterator<Item = u32>, convert_fn = convert)]
example: HashSet<u32>,
}
fn convert(input: impl IntoIterator<Item = u32>) -> HashSet<u32> {
input.into_iter().collect()
}
Or perhaps it could be possible to supply a closure:
#[builder]
struct ExampleStruct {
#[builder(input_type = impl IntoIterator<Item = u32>, convert_fn = |x| x.into_iter().collect())]
example: HashSet<u32>,
}
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.
Thank you for bringing this up! This is something I was also thinking about. For example typed-builder provides #[builder(setter(transform = |x: f32, y: f32| Point { x, y }))] attribute to customize the setter. It expects a closure with type annotations to do this. This is entirely possible to implement in bon as well.
I didn't do this initially to keep the initial release simple and to keep the number of attributes and magic as low as possible. For example, today you can implement this by defining your builder via a new() method instead:
use bon::bon;
struct ExampleStruct {
example: HashSet<u32>,
}
#[bon]
impl ExampleStruct {
#[builder]
fn new(example: impl IntoIterator<Item = u32>) -> Self {
Self { example: <_>::from_iter(example) }
}
}
I understand that this requires a bit more boilerplate, however, this makes the code simpler. The fallback to a #[builder] on fn new basically solves any requirements for custom logic overrides via attributes.
On the other hand, I suppose you'd like to have a general capability to accept impl IntoIterator for collections, right? This is something that can be special-cased with #[builder(from_iter)], because I think this may be a frequent use case.
I'd prefer a #[builder(from_iter)] in this case and recommend falling back to an explicit #[builder] fn new() method for any other advanced use cases.
What do you think? Will #[builder(from_iter)] be enough for your observable use cases?
Yeah, for me at least #[builder(from_iter)] would be fine! Not sure if you should name it differently though, given from_iter implies impl Iterator when it's really impl IntoIterator... your choice!
In the long run, having something like a closure would be nice, but for now this would solve everything I want to do :)
given from_iter implies
impl Iteratorwhen it's reallyimpl IntoIterator
I agree. It's unfortunate this naming choice was established in Rust FromIterator trait that actually powers the .collect() method. Even though the trait and its method imply impl Iterator, it accepts impl IntoIterator instead. On the other hand I guess #[builder(into_iter)] may read better
Yeah it's kind of weird but if that's the norm in rust then it definitely makes sense! Anyway thanks a lot for responding to this so fast, I've been having a lot of fun refactoring parts of my project to use bon (saved loads of lines of code!) - I discovered it off your reddit post and I don't think I'll be able to live without it from now on! I do have one or two other suggestions for features but I guess I'll open other issues for those.
Glad to hear that! I noticed that you added bon as a git dependency to your project. Is there any reason not to use it from crates.io?
Oh yeah, that was from when you had fixed the issue about optional types having to implement default on main, but before you had published a new crates.io release. I have changed it just not committed those changes to gh yet!
I added an annotation #[builder(with = ...)] that accepts a custom closure or a well-known function #157 / #145.
For the case of accepting an IntoIterator parameter specifically, it's possible to write this:
#[derive(bon::Builder)]
struct Example {
#[builder(with = FromIterator::from_iter)]
vec: Vec<i32>,
#[builder(with = <_>::from_iter)]
map: std::collections::BTreeMap<i32, i32>
}
Both syntaxes FromIterator::from_iter and <_>::from_iter work the same just like they do in regular Rust code (yes, you can omit the name of the trait with <_>:: syntax). The macro tries to infer the type of item for the input iterator based on the types name suffix. The following suffixes are supported: Vec, Set, Deque, Heap, List, Map, where only Map implies a tuple of Key/Value for the input iterator item type.
This feature is in master and will be released as part of 3.0 version. The 3.0 release checklist is in #156. I'll close that issue once the release is done.
I you have any feedback, I'd be glad to hear that
3.0.0-rc version was published. I'll prepare a blog post for the release and switch it to 3.0.0 on November 13-th
Amazing, thanks for all your work on this, looks great!