-
Notifications
You must be signed in to change notification settings - Fork 72
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
Move Builder
to own module, allow for shared runtime reference.
#115
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
ArcedNodeBuilder
wrapper needs to own the innerNoderBuilder
and we couldn't move/modify theRwLock
'ed value in place? I guess we could work around that byArcedNodeBuilder
holding aninner: 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
?
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 Self
return 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
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
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
Builder
andNode
. However, AFAIK there is no reason we shouldn't allow creation of multiple nodes with the same builder. Having theBuilder
be 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::AlreadyBuilt
type 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
Builder
andNode
. However, AFAIK there is no reason we shouldn't allow creation of multiple nodes with the same builder. Having theBuilder
be 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::AlreadyBuilt
type 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 self
would 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_X
consumed 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 ignore
ing 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.
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.rs
got quite big andBuilder
made up a good chunk of it, we now move it to a dedicated submodule.We then split the actual logic of
build_with_store
from 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 setSome
inNode::start
.Finally, we do exactly that: we allow
build_with_store_internal
to take a runtime parameter.