Some ideas to make command interface simpler and uniform #3330
Replies: 3 comments 4 replies
-
@Retamogordo thank you for that exploration. Please publish the branch - maybe in a Work In Progress - draft PR. I'll read and share thoughts soon. I'm about to push a bats test file, with some common usages, so that may help with some testing along the way. |
Beta Was this translation helpful? Give feedback.
-
Some initial thoughts:
RpcBuilder::new(ctx, opts, &nc.name)
.tcp(tcp).build()?
.request(api::project::create(&cmd)).await?.parse_response::<Project>()?.to_owned() Looking forward to that PR! |
Beta Was this translation helpful? Give feedback.
-
Hi !
Possibly it could amended for more ergonomics, but the idea behind is to take the ownership of the response just after the raw data is ready |
Beta Was this translation helpful? Give feedback.
-
Hi,
I would like to share some thoughts on how the command rpc interface could be further developed to make it simpler and uniform.
I'm afraid this will be a long read so hopefully you have time and patience for this.
Consider this typical use case:
The rpc request instance is type-parametherized by the type of the command, while the request method has additional parametherization of the return type of
api::project::show(&cmd)
.Yet another type-parametherization is applied on the response method.
Meanwhile the command type defines one-to-one relationship between the two latter types.
This allows to simplify the above code to roughly this:
Now, having type signatures uniform we can add a method that covers the common use case:
To do so a very simple trait can be introduced:
and the implementation example:
Let's now move on to another thing and consider the current implementation of the Rpc struct:
which holds references with a lifetime
'a
.On the other hand Rpc holds
buf
field which is referenced by an instance ofDecoder
, which is returned to a caller by an Rpc method (parse_response).The instance of Decoder is also marked with the lifetime
'a'
.I would think that those lifetimes are not necesarilly identical and some kind of coercion occurs there, namely in this signature:
This means that whenever a Decoder instance is somewhere in scope, the Rpc is borrowed and,
transitively, all the references it holds, too.
For the sake of example, let's mark the lifetime of &ctx by
'c
and the lifetime of the Decoder by'd
. So 'c is not necessarily narrower than 'd.This could be expressed as 'c: 'd.
I tried to play and split these lifetimes like so, and it compiles:
But after all, this was to little avail, because although more relaxed, the entanglement is still there.
So, the next step I tried was to yield the ownership to the caller, so the Rpc instance can peacefully die after its job of retrieving response is done and the parse job is completed by the RpcCaller implementor, after all it defines the type of the response and knows how to parse it.
To do so a very slim wrapper struct is introduced:
It just holds the raw data and implements all parse methods which are actually held by Rpc struct.
For instance:
This allows to end up with this variation of the original example:
resulting in a uniform interface for common use cases.
On the command implementation side things also get simpler.
I have a branch where I played around with these things, I implemented a couple of commands just to see it compiles, and even tested one of them.
For further testing I lack comprehension of the system, but if someone is interested in these things I could publish it.
Beta Was this translation helpful? Give feedback.
All reactions