-
Notifications
You must be signed in to change notification settings - Fork 114
Move Builder to own module, allow for shared runtime reference.
#115
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
Conversation
d12726a to
f64288b
Compare
f64288b to
cd85df1
Compare
|
Now added a commit that re-introduces a paradigmatic I think it's preferable to have a clean Rust API here and have changed my mind regarding the necessary maintenance overhead (shouldn't be too bad now that we have the |
As `lib.rs` got quite big and `Builder` made up a good chunk of it, we now move it to a dedicated submodule.
Splitting the actual logic from the API method is needed as VSS in the future will need to share the Runtime, i.e., will need to hand in a `Arc<Rwlock<Runtime>>>` parameter that we'll set `Some` in `Node::start`.
This is required to allow the VSS `KVStore` to share a reference to the runtime. The runtime option will be set `Some` by `Node::start`.
a9c072f to
0e65429
Compare
|
Rebased on main. |
src/builder.rs
Outdated
| pub fn set_entropy_seed_path(&self, seed_path: String) { | ||
| *self.entropy_source_config.write().unwrap() = | ||
| Some(EntropySourceConfig::SeedFile(seed_path)); | ||
| pub fn set_entropy_seed_path(&mut self, seed_path: String) { |
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.
Any reason we can't use move semantics now (i.e., take self and return Self)? That way you can chain mutators.
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.
I think move semantics still wouldn't work seamlessly as the ArcedNodeBuilder wrapper needs to own the inner NoderBuilder and we couldn't move/modify the RwLock'ed value in place? I guess we could work around that by ArcedNodeBuilder holding an inner: RwLock<Option<NodeBuilder>> and taking and re-setting the value upon each call. Not sure this is preferable to taking &mut and returning &mut Self?
Generally, the lack of mutator chaining is a good point: I simply had forgotten to add the &mut Self return values, now added.
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.
I think move semantics still wouldn't work seamlessly as the
ArcedNodeBuilderwrapper needs to own the innerNoderBuilderand we couldn't move/modify theRwLock'ed value in place? I guess we could work around that byArcedNodeBuilderholding aninner: RwLock<Option<NodeBuilder>>and taking and re-setting the value upon each call. Not sure this is preferable to taking&mutand returning&mut Self?
Right, it would require using an Option, I believe.
Generally, the lack of mutator chaining is a good point: I simply had forgotten to add the
&mut Selfreturn values, now added.
One reason I'd prefer returning Self over &mut Self is the former makes for a cleaner way of chaining when you need to do intermediary calculations before using the builder again. The latter requires initializing a variable with the builder before doing any chaining. Otherwise, you'd return a reference to a temporary, which the compiler will complain about:
creates a temporary which is freed while still in use
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.
One reason I'd prefer returning
Selfover&mut Selfis the former makes for a cleaner way of chaining when you need to do intermediary calculations before using the builder again. The latter requires initializing a variable with the builder before doing any chaining. Otherwise, you'd return a reference to a temporary, which the compiler will complain about:creates a temporary which is freed while still in use
Mh, I now experimented with it and have it working in an experimental branch, see: tnull@3efa72e
Few thoughts:
- Move semantics indeed 'feel' a bit 'cleaner' too me as well.
- While moving indeed allow to 'quick safe' the builder state, it also requires the user to do so, i.e., if they don't want to do it all in a single line, they'd have to do
let builder = Builder::new().set_X()rather than just
let mut builder = Builder.new();
builder.set_X();This is a bit unfortunate also as the latter corresponds to the syntax they'd have to use in the bindings, i.e., our Rust docs would be a bit less transferable/relevant to the binding languages.
- Consuming restricts the use to a 1:1 relationship between
BuilderandNode. However, AFAIK there is no reason we shouldn't allow creation of multiple nodes with the same builder. Having theBuilderbe non-reusable also means that in bindings we would panic on a secondbuild()command for now (https://github.com/tnull/ldk-node/blob/3efa72e1354bda556518873358f452ae29b3af9b/src/builder.rs#L314) and would need to introduce a dedicatedBuildError::AlreadyBuilttype that's only returned for that case in Prefer returning errors over panicking where possible #119.
Not entirely convinced one way or the other yet tbh. Moving/consuming 'feels' more paradigmatic, but &mut self would allow us to keep the APIs closer together.
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.
Mh, I now experimented with it and have it working in an experimental branch, see: tnull@3efa72e
Few thoughts:
- Move semantics indeed 'feel' a bit 'cleaner' too me as well.
- While moving indeed allow to 'quick safe' the builder state, it also requires the user to do so, i.e., if they don't want to do it all in a single line, they'd have to do
let builder = Builder::new().set_X()rather than just
let mut builder = Builder.new(); builder.set_X();
Actually, this will cause a compilation error when the builder is later used since set_X consumed it but never reassigned it to builder. But maybe I'm misunderstanding what version each of the above is demonstrating...
This is a bit unfortunate also as the latter corresponds to the syntax they'd have to use in the bindings, i.e., our Rust docs would be a bit less transferable/relevant to the binding languages.
That's a fair point.
- Consuming restricts the use to a 1:1 relationship between
BuilderandNode. However, AFAIK there is no reason we shouldn't allow creation of multiple nodes with the same builder. Having theBuilderbe non-reusable also means that in bindings we would panic on a secondbuild()command for now (https://github.com/tnull/ldk-node/blob/3efa72e1354bda556518873358f452ae29b3af9b/src/builder.rs#L314) and would need to introduce a dedicatedBuildError::AlreadyBuilttype that's only returned for that case in Prefer returning errors over panicking where possible #119.
Alternatively, build functions don't necessarily need to consume if we want reuse. But if it consumes, then we could implement Clone to support building additional nodes. If not, then note that we are already internally cloning config fields to pass to the node. The consuming variation is a bit more efficient as those fields could be moved instead and only cloned when the user explicitly clones the builder.
The bindings version could internally clone the config to avoid needing a BuildError::AlreadyBuilt.
Not entirely convinced one way or the other yet tbh. Moving/consuming 'feels' more paradigmatic, but
&mut selfwould allow us to keep the APIs closer together.
I don't feel very strongly if you prefer to keep the APIs similar. The common case of building a node in one statement looks the same either way. So I suppose it's a matter of whether API similarity is more important than how uncommon cases are formulated.
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.
Actually, this will cause a compilation error when the builder is later used since
set_Xconsumed it but never reassigned it tobuilder. But maybe I'm misunderstanding what version each of the above is demonstrating...
Yes, this wouldn't work for the moving/consuming variant, which is exactly my point.
I don't feel very strongly if you prefer to keep the APIs similar. The common case of building a node in one statement looks the same either way.
Right the &mut variant is a bit more flexible as in that it allows for either the chaining pattern or the "setter" pattern. While the former would be mandatory for the moving variant, it doesn't work for the ArcedNodeBuilder exposed in Uniffi, and the "setter" pattern needed there doesn't work for NodeBuilder. This leads to an API incompatibility that's not only unfortunate, it for example also breaks the crate's doc test (see above linked commit), requiring a fix like: tnull@b0e2813. While the feature gating fix makes the doctest pass, it doesn't actually mask the docs, so they show both variants.. I'm not sure there is a good way working around this short of ignoreing this doctest, which I'd like to avoid as it functions as the main usage example.
However, I think I might take this example as a first sign that the slight Rust-only benefits of the moving variant might not be worth dealing with the increased complexity of API incompatibility going forward.
TLDR: I see moving/consuming could have some minor benefits and feels more paradigmatic, but I'm not sure it's worth having to deal with two API-incompatible builders going forward. At least with the &mut variant there is some common ground, also having the benefit that the Rust-only docs stay more relevant to the bindings.
jkczyz
left a comment
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.
LGTM. Please squash fix-ups.
While this generates a bunch of boilerplate, it's probably worth the maintainance effort to have a clean/paradigmatic Rust API. And, as we had previously introduced a `uniffi` feature, we're now able to easily switch out `Builder` exports based on it.
873fdc5 to
a25632c
Compare
Squashed without further changes. |
Firstly, as
lib.rsgot quite big andBuildermade up a good chunk of it, we now move it to a dedicated submodule.We then split the actual logic of
build_with_storefrom the API method, which is needed as VSS in thefuture will need to share the Runtime, i.e., will need to hand in a
Arc<Rwlock<Runtime>>>parameter that we'll setSomeinNode::start.Finally, we do exactly that: we allow
build_with_store_internalto take a runtime parameter.