-
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
EXPERIMENT: builder APIs via bon
#96
base: trunk
Are you sure you want to change the base?
Conversation
Hi @ErichDonGubler, I'm the maintainer of |
pub struct RenderPipelineDescriptor<'a> { | ||
/// Debug label of the pipeline. This will show up in graphics debuggers for easy identification. | ||
#[builder(default, into)] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given that Label
is just a type alias for Option<&str>
, I'd suggest to inline that type alias so that bon
recognizes it as an Option
under the hood (it just uses a heuristic text match by the type's path to detect an Option
).
For example with the current
#[builder(default, into)] label: Label<'a>
the following setters will be generated:
fn label(self, value: impl Into<Option<&str>>) -> NextBuilderState
fn maybe_label(self, value: Option<impl Into<Option<&str>>) -> NextBuilderState
which adds unnecessary Option
nesting. If you inline the type alias, you can omit any #[builder]
configs as well:
label: Option<&'a str>
and this will produce the following setters:
fn label(self, value: &str) -> NextBuilderState
fn maybe_label(self, value: Option<&str>) -> NextBuilderState
which removes the Option
nesting, and greatly simplifies the code
afd6085
to
2f9f51c
Compare
This comment was marked as resolved.
This comment was marked as resolved.
e77244d
to
577ee3a
Compare
Thank you for the background. I'm not familiar with wgpu (my background is in the distributed systems in the cloud), but I'd be glad to help with any effort of using
So from the context you've given, I think that using I've also heard opinions from people that "flat" method call API like this: .create_texure()
.param(...)
.param(...)
.build() doesn't look very nice when this method chain is extended with other similar builder API calls: .create_texure()
.param(...)
.param(...)
.build()
.do_smth_else()
.other_method_param(...)
.other_method_param(...)
.call()
.other_method()
.param(...)
// ... Instead if the API is supposed to be chained a lot, then the "Descriptor" parameter structs syntax makes it easier to read with additional small nesting: .create_texure(
TextureDescriptor::builder()
.param(...)
.param(...)
.build()
)
.do_smth_else(
OtherDescriptior::builder()
.other_method_param(...)
.other_method_param(...)
.build()
)
.other_method(
OtherDescriptior2::builder()
.other_method_param(...)
.other_method_param(...)
.build()
) The style of "flat builder" API with method-based builders was inspired by AWS SDK API. In that case it makes sense since API calls in AWS SDK are not chained with each other, so such readability problem doesn't appear. Also, the descriptor-struct-based API makes it possible to build the descriptor separately and and store that struct somewhere else, which I suppose may be potentially a frequent use case in wgpu. This also gives me an idea that there should be a way to generate a separate "parameters" struct from the function with the A cargo-feature gated API definitely makes sense in this case. It allows you to keep the builder API coupled with the existing descriptor structs declarations (which it should be indeed) and this way you avoid the need for publishing a separate crate (and the CI burden it may require). Conditional compilation is supported just fine with |
9e4704f
to
198be2b
Compare
198be2b
to
f7cb6e5
Compare
ff0dbcf
to
0df4de8
Compare
Hi! Small update, I've released a 3.0 version of |
TODO: unsquash removals of fields into next commit
0df4de8
to
cb068f1
Compare
@Veetaha: Did the bump, and it still compiles! Any recommendations for new features to take advantage of? 😀 |
I think for the use cases here, there aren't obvious game-changer features from the ones added in 3.0. The only thing I may add is to this comment: #96 (comment). If you'd like to keep the type alias #[builder(default, with = |value: &'a str| Some(value))]
pub label: Label<'a>, The Other than that there were just some background changes that you got automatically with this update like generated documentation improvements. For example, values specified with |
/// Debug label of the texture. This will show up in graphics debuggers for easy identification. | ||
#[builder(default, into)] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unfortunately, it's not obvious, but optional fields, that use a generic type are discouraged. This is because it leads to weakened type inference.
In short, if you minimize the problem it boils down to this analogous pattern:
fn example<T>(_value: Option<T>) {}
fn main() {
example(None);
}
This code doesn't compile with the error:
| example(None);
| ^^^^^^^ ---- type must be known at this point
| |
| cannot infer type of the type parameter `T` declared on the function `example`
This similar problem would occur if you use TextureDescriptor::builder()
and omit to call the label
setter or pass None
to maybe_label()
setter. The compiler will not be able to infer the generic type L
in such case.
I described this problem in detail here, feel free to check that explanation on your leisure.
This PR is an experiment that uses
bon
'sderive
APIsSteps to take to advance this experiment:
bon
's method builders (as separate methods from the existing ones)?Warning
Heavily WIP, and, notably, not a priority for my work at Mozilla.