Closed Veetaha closed 2 months ago
Small update. I measured the compilation performance of this repo both with typed-builder and bon. To my surprise, the difference is quite noticeable at this scale. Compiling from scratch (cargo build
) on my devbox takes:
typed-builder
: 17 secondsbon
: 22-23 secondsI'm looking into how I can optimize bon
based on this example. There is a sub-second performance win during the runtime of the macro itself in typed-builder (and I can definitely fine-tune bon
to close that gap by optimizing its algorithms/allocations for sure), but the main difference (bottleneck) is at the stage of typechecking the expanded code.
bon
uses a bit more traits and type indirection than typed-builder
does. I'm currently working on a version of bon
that avoids using traits and uses a simpler model of the generated code as much as possible such that the generated code more resembles typed-builder
's simplisitic type state. This optimization shouldn't be a breaking change for bon
, since builder's type state signature is not part of the semver guarantees.
Another theory is that bon
uses darling
dependency, while typed-builder
does not, so maybe this cost is constant. Looking into that..
Success! Indeed the trait abstractions in bon
were causing the difficulty for the compiler. I managed to bring the type checking perf. of the code generated by bon
to the level of typed-builder
:tada:. This is currently in a quickly-edited version of bon with lots of junk that I'll refactor tomorrow and make a patch release of bon
, but it works:
Here are the new compilation timings with bon
:
Same measurements but with typed-builder
:
There is now a 0.7
s difference between the from-scratch-compilation perf of bon
and typed-builder
, which is much more acceptable (given that bon
depends on darling
additionally and compilation of bon-macros
takes ~1
second, while typed-builder-macro
takes 0.5
seconds to compile).
Hey guys, I've updated bon to 2.1
with the compile perf. fixes and this PR is now ready for the re-review. The remaining things for this PR from the review:
into
conversions from primitive types, but I left them on composite types (struct, enums), because some of them do have useful From<>
implementations (see the comment https://github.com/ayrat555/frankenstein/pull/196#discussion_r1739384948)bon
has a bit different shape.I'm looking forward to hearing about any other things you'd like to change in this PR before merging it!
Marked the PR as ready for review.
@Veetaha hey. can you please resolve conflicts and we can merge
Rebase is complete 👌
Note for the future: I'm planning (https://github.com/elastio/bon/issues/68#issuecomment-2330353117) to change the syntax for structs to a more usual derive(bon::Builder)
(as is often requested in feedbacks). This won't be a breaking change, existing code will just show deprecation warnings for #[builder]
usages on structs when you upgrade. I'll create a separate PR to update frankenstein
to the derive(bon::Builder)
syntax once this feature is ready and released in a new minor version of bon 2.2
. Anyway, this shouldn't block this PR from merging and this will be done separately
I think we should wait a bit for bon to settle. Changes will quickly create a bunch of changes in frankenstein due to its quite heavy builder usage.
I really like its benefits over typed-builder, but I would like to prevent multiple refactorings while bon is still maturing.
Also, there might be ways to improve the usage of bon even further by condensing all into
conditions like on String into a single place which would reduce the human mistake rate of it?
Makes sense, I'll update the PR with the derive
syntax once that is ready.
Also, there might be ways to improve the usage of bon even further by condensing all into conditions like on String into a single place which would reduce the human mistake rate of it?
I don't have the best way to do that in mind, because macros in Rust are stateless, so there isn't a good way to apply some global config to them. I think the simplest and optimal way to do that is with a declarative macro wrapper as described in https://github.com/ayrat555/frankenstein/pull/196#discussion_r1740194474. Do you think that'll be acceptable? Note that wrapping the struct definition with a declarative macro doesn't necessarily imply a worse IDE experience (at least in Rust Analyzer). The trick is to use a $($tt:tt)*
input that allows for malformed (incomplete) syntax trees, so if you are editing the struct, the macro still provides useful expansion and Rust Analyzer still works just like with regular code
Hey @ayrat555, @EdJoPaTo I've updated bon to the version 2.2.0
and the diff became much more negative (there is no outstanding #[builder]
on structs now, the previous derive(Builder)
now works).
I propose to merge this PR. See my comment https://github.com/ayrat555/frankenstein/pull/196#issuecomment-2331646477 about the improvements suggested by @EdJoPaTo I propose to do that in a separate PR to keep the diff low and avoid future merge conflicts.
Given that bon has a function builder… what about the TelegramApi::send_message
(and all the others there)?
Currently, its basically TelegramApi::method(&self, params: &MethodParams) -> MethodResponse<Bla>
. I thought about simplifying this with a macro to reduce the repetition but maybe bon can be useful here too.
Maybe something like this could be possible?
api.send_message().message("lalala").parse_mode(ParseMode::Html).send();
Then the api_params wouldn't even be needed by the users of this crate.
I assume this could already work with the existing bon::builder
when moving the parameters into the trait, but that would end up as a huge duplication of code for sync/async.
Maybe something like this could be possible?
Yep, it's definitely possible with bon
.
I assume this could already work with the existing bon::builder when moving the parameters into the trait
I suppose you meant "moving the parameters into associated methods"? Currently, bon
doesn't support placing #[builder]
on traits or impl blocks for traits.
Note that the downside of this approach is that there is indeed no "parameters" struct type, so if users want to store the parameters on some other struct, they won't be able to do that.
Here is an example crate that defines such API using bon
, although it uses an older version of bon
. The rustdoc output improved since that version of bon
used there quite a bit
Here is an example crate that defines such API using
bon
, although it uses an older version ofbon
. The rustdoc output improved since that version ofbon
used there quite a bit
Doesn't look like what I've envisioned :thinking:
Currently,
bon
doesn't support placing#[builder]
on traits or impl blocks for traits.
That might be a dealbreaker here.
The basic idea here is to create a builder on a trait method which uses the single parameter struct as builder methods:
struct SendMessageParams {
pub text: String,
pub parse_mode: ParseMode,
…
}
trait TelegramApi {
…
#[whatever_needed]
fn send_message(&self, params: SendMessageParams) -> Result<MethodResponse<Message>, …> {…}
}
struct Api {…}
impl TelegramApi for Api {…}
let api = Api::new(…);
let message = api.sendMessage().text("hello!").parse_mode(ParseMode::Html).call().unwrap();
One idea I had is to put the Api into the params struct, so the call has a reference to that.
The params struct should continue to exist as they are needed both sync and async and code duplication should be prevented.
But I think this gets into its own discussion independent of the PR. So not relevant to be resolved before merging.
Yeah, support for traits/trait-impls is yet an unsolved problem and requires some epic research/design effort. I created an issue in bon
about that https://github.com/elastio/bon/issues/123. Eventually, there should be a solution.
In the meantime if want to support both sync and async versions of code, there is a macro to generate both impls
Protecting the long-lived git tradition 💪