-
Notifications
You must be signed in to change notification settings - Fork 41
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
Add builder patterns to build requests #693
Comments
Since I have to leave again soonish, I didn't read all of this (yet), but still, here is a quick first reaction: We do not yet have a builder pattern, but we have something "more structured" than the existing functions. conn.send_trait_request_without_reply(CreateWindowRequest{
depth: COPY_DEPTH_FROM_PARENT,
wid: window,
parent: screen.root,
x: 0,
y: 0,
width: 150,
height: 150,
border_width: 10,
class: WindowClass::INPUT_OUTPUT,
visual: screen.root_visual,
value_list: CreateWindowAux::default().into(), // dunno if this line works like this, this has to construct a Cow
})?; Does this do what you want it to do? Is there some downside compared to the builder? On the plus side: All the |
The main advantage of the Builder pattern (note: older page) is that it allows the user to construct large values in a piecemeal way, which the struct notation doesn’t allow for. In addition, I think it would make dealing with the auxiliary structures easier; I think there’s at least one issue in Overall, it opens the door for usage in more styles of coding. |
That page says:
I think we already (pretty much) do that, except for the @eduardosm What do you think? (And what does everyone else think?) |
I agree, the builder pattern is useful when you have few required parameters and many optionals. Using |
The other option, which outsources the checks to compile time, would be to do this: struct FooBuilder<Foo, Bar> {
foo: Foo,
bar: Bar,
aux: FooRequestAux,
} Then, we initialize it like this: impl FooBuilder<(), ()> {
fn new() -> Self {
Self { foo: (), bar: (), aux: Default::default() }
}
} Then, for each field, you do this: impl<Bar> FooBuilder<(), Bar> {
fn foo(self, value: u32) -> FooBuilder<u32, Bar> {
let FooBuilder { foo: (), bar, aux } = self;
FooBuilder { foo: value, bar, aux }
}
} Finally, once every field has been set, you can do this to build it: impl FooBuilder<u32, u16> {
fn build(self) -> FooRequest {
let Self { foo, bar, aux } = self;
FooRequest { foo, bar, aux }
}
} The advantage of this design is that is uses compile-time guarantees to ensure that there is never a case where a value can’t be constructed at runtime. The disadvantage is that it’s less usable. You have to use it from a |
Closing as I do not plan to pursue this further in the wake of #883 |
The Why
In order to create a window in current
x11rb
, you do this:x11rb/x11rb/examples/tutorial.rs
Lines 405 to 418 in e83d647
This function takes 11 arguments, not including the ones bundled into the
Aux
structure. In the above case, most of the arguments are numbers. In the past, I've found it difficult to use these functions. It's tough to remember which argument is which ("say, is the width the 4th or 5th one?"), and this has led to hard-to-diagnose buggy behavior in the past. Although thisThe What
My solution would be to leverage Rust's "builder pattern", which would go something like this:
This ensures that:
CreateWindowRequest
could be built up over a period of time.The How
There are two ways of creating builders in Rust. You could have a struct where every field is its own generic, and then have a few pipeline methods that replace those generic fields successively. But I personally like the classical method better.
Take this request:
We would have the generator also emit a new "builder" struct. The "classical" way of creating builder patterns is like this:
(Although, probably without the
unwrap
ing.build()
could just be a fallible function.)This stores the fields in
Option<T>
(except for auxiliary fields, which are already stored inOption<T>
so I think this is irrelevant) and thentake
s them to be used in the eventualFooRequest
. As for a potentialsend()
method... we could always put this on theprotocol
side:...and then have an extension trait on the
x11rb
end:This is simpler and can be used from an
&mut
position, but there is a small amount of overhead in the form of theOption
s. What are your thoughts on this, @psychon?The text was updated successfully, but these errors were encountered: