-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Passing optional arguments to wrapped ImageBufAlgo
methods.
#2
Comments
As somebody who had to recently refactor a big lib+CLI combo to add a few more new arguments to a function call (which was called 96 times!), I feel either using an Init-Struct or a Builder-Pattern would be a good approach. The main difference between the two is that the builder pattern can be a little bit less heavy since on the client side, at the cost of the library being more boilerplate-y, since you can "hide" the default parameters within the impl of the Builder, while with the Init struct the caller has to explicitly pass Also: can I ask you the rotation itself is not part of the arguments ? I'd expect that passing You could also avoid having to wrap everything in my_image_buf.rotate(
RotateOptions::builder()
.degrees(42.0)
.pixel_filter(&pixel_filter)
.recompute_region_of_interest(true)
.build()
); which means that just rotating using defaults for everything expect the rotation would look like this: my_image_buf.rotate(
RotateOptions::builder()
.degrees(42.0)
.build()
); What do you think ? |
I very much prefer the init-struct pattern. I think This is not so obvious when using a builder.
Likewise, you still need to write Even if you use sth. like I think For example But this is not the case here (and for most of optional parameters in OIIO, especially in
The The rules are:
So far I have four variants of most impl ImageBuffer {
pub fn rotate(&mut self, angle: f32) -> Result<&mut self> { … }
pub fn rotate_with(&mut self, angle: f32, rotate_options: &RotateOptions) -> Result<&mut self> { … }
pub fn from_rotate(angle: f32) -> Result<ImageBuffer> { … }
pub fn from_rotate_with(&mut self, angle: f32, rotate_options: &RotateOptions) -> Result<ImageBuffer> { … }
} Note also that the 1st two methods return |
I don't quite get this. RotateOptions::builder()
.pixel_filter(&pixel_filter)
.recompute_region_of_interest(true)
.build() vs init-struct pattern, 100 characters: RotateOptions {
pixel_filter: &pixel_filter,
recompute_region_of_interest: true,
...Default::default()
} I wouldn't call a five character difference 'heavy' but if you do, that is yet another reason for not making non-defaults parameters part of the builder: they don't require the latter then -- even less typing. 😉 my_image_buf.rotate(42.0)?; |
That's true! However, in my experience with languages with named args (like Python) it's never really been a problem/source of confusion the fact that you don't see an explicit call to something akin to In any case, YMMV - what's verbose for some it's self-documenting code for others! I don't think there's anything wrong with the Init struct pattern! And I've personally used it plenty of times. One thing to consider is API breaking changes - I don't have a lot of experience there and I have a vague feeling that the builder makes it easier to avoid breaking changes but I might be wrong, I need to read https://github.com/rust-lang/rfcs/blob/master/text/1105-api-evolution.md#structs in depth.
That's^ the kinda of stuff that I can see one would like to encode in the type system instead of leaving it to the diligence of the caller, but maybe it's overkill here. Re your second post, you are mentioning just So you would have: my_image_buf.rotate(
RotateOptions::builder()
.degrees(42.0)
.pixel_filter(&pixel_filter)
.recompute_region_of_interest(true)
.build()
); vs your previous example, where the extra options where wrapped in an my_image_buf.rotate(
42.0,
Some(RotateBuilder::new()
.pixel_filter(&pixel_filter)
.recompute_region_of_interest(true),
.build()
)
); In any case, if you end up having 2 methods (one w/ no options and one w/ extra options), I think this is a non-issue (not that it was a huge issue anyway). You can just have: my_image_buf.rotate(42.0) and my_image_buf.rotate_with_options(
42.0,
RotateOptions::builder()
.pixel_filter(&pixel_filter)
.recompute_region_of_interest(true),
.build()
)
); or, via struct init: my_image_buf.rotate_with_options(
42.0,
RotateOptions {
.pixel_filter: &pixel_filter,
.recompute_region_of_interest: true,
..Default::default()
}
); Also note that you're referring to The builder is also used extensively in the std, e.g.: https://doc.rust-lang.org/std/process/struct.Command.html (as mentioned by https://rust-unofficial.github.io/patterns/patterns/creational/builder.html) so that's another thing to keep in mind. Also, didn't want to start a bike-shed on this, I was just happy to see some movement here in OIIO/Rust-land! 😄 |
Yeah, when I opened the ticket I didn't have the split into If you look at the four signatures I gave in the original reply to you, you'll notice they already do not have the Because of Rust's zero-cost abstractions, using However, The former can be abbreviated with But once you have I find this cleaner (not least on the eyes) than having a single version of the resp. function that then gets called with
You still need four variants. Two are constructors that create & return a new The C++ API btw. also usually has four overloaded variants, for similar reasons. |
Yes, builders are much older than init-struct. Also init-struct has the issue of missing struct-exhaustiveness possibly breaking existing code when new struct members are added on the API-side and the user has specified all (previous) members and thus omitted I think of this as an advantage though as you automatically get notifed of the API change by the compiler in that case. Solutions: look up the new stuff you missed now and add it or simply add the missing The quoted blog post suggest adding a Both builder- and init-struct patterns are really workarounds for the lack of named arguments in Rust. 😞 |
Named arguments are btw. only half of what is needed to resolve this issue on the compiler-side. You also need defaults for arguments which is another can of worms. Because for something that has a bunch of (named) arguments and defaults not being available in the compiler, all arguments have to be written out still, even if they're all default ( See the 2nd example ( TLDR; if named arguments were added to |
Yeah that's one way of looking at it - however if everybody followed that approach in the crates ecosystem it would probably be death by a thousand papercuts 😄 Imagine having to release a bugfix because your CI broke just because a dependency (maybe a transitive one!) updated a struct and you (or a dep you use) were not using
Yep - I agree. To be honest, in the last ~3 years that I've been using Rust, I didn't miss named args that much. And when I really wanted to provide self-documenting but rust-y ergonomics, I wrapped stuff into macros that looked like they had named args, e.g.: let something = my_macro!(thing = 1, thong = "hello"); Of course with macros comes longer compile times and harder error messages, but life is about tradeoffs. In my case I haven't really found compile times to be unbearably long.
Yep. BTW there's not even an RFC on named args, I doubt they will ever make into the language before a 2.0: rust-lang/rfcs#323.
Yeah I agree - I think that's much better for LSPs/autocompletion and also doc-comments/PRs readability! PS: There's also https://docs.rs/bon/latest/bon/ to help create builders |
There are three approaches I considered so far:
Init-Struct Pattern
I am leaning towards this one as it is easy on the eyes when reading the code and not too verbose.
Example:
Option
al ArgumentsSee also #1. There would be two versions of each method, one that takes only required arguments and one with a
_with
postfix taking optional arguments asOptions
.The problem is that Rust doesn't have named arguments. The code becomes harder to understand w/o comments.
E.g., what does
Some(true)
mean below?Builder Pattern
Lots of boilerplate needed, not better than init-struct, just different.
The text was updated successfully, but these errors were encountered: