-
Notifications
You must be signed in to change notification settings - Fork 19
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
Assign Return Value Of Delegated Calls To Delegated Field #50
Comments
Hi :) If it was only required to return To solve your specific use-case: I assume that |
@Kobzol Isn't the macro aware of the delegated field?
Using // ...
let mut builder = APIClient::builder();
builder // builder instance is effectively "empty" here
.api_key("SOME API KEY") // builder.api_key is now Some("SOME API KEY")
.some_delegated_method() // what happens to the value of builder.api_key here?
.build()
} Assuming the macro is aware of which field a given method is being delegated to, the "capture" behavior could be implemented either with an explicit directive (like you said), or implicitly by examining the arguments to the delegated method. The logic would be something like "if the delegated method takes (mutable?) ownership of |
It is. It's not a problem to implement it, I'd just like to avoid special casing too many specific use-cases, and would rather come up with a more generic approach. Guessing what the user wanted to do from the We cannot also just return struct Tree { left: Box<Tree>, right: Box<Tree> }
impl Tree {
delegate! {
to self.left {
#[call(foo)]
fn foo_left(&self) -> Self;
}
}
fn foo(&self) -> Self { Tree { ... } }
} In this case the user probably wants fn foo_left(&self) -> Self {
self.left.foo()
} and not fn foo_left(&self) -> Self {
self.left.foo();
self
} I was thinking about adding some I wonder if there are other builder patterns. If the inner builder used #[inline(always)]
pub fn cookie_store(mut self, enable: bool) -> Self {
self.builder.cookie_store(enable);
self
} This case could be solved just with the return I would propose adding two new attributes:
Your code would then have to look like this: delegate! {
to self.builder {
#[return(self)]
#[store]
pub fn timeout(mut self, timeout: Duration) -> APIClientBuilder;
#[return(self)]
#[store]
pub fn cookie_store(mut self, enable: bool) -> APIClientBuilder;
#[return(self)]
#[store]
pub fn connect_timeout(mut self, timeout: Duration) -> APIClientBuilder;
}
} which seems a bit verbose. I wonder if the If the answers are yes, then I will try to implement these attributes and see how practical it is to use them, and combine them with other, existing attributes. But probably even before that I should make "block" attributes work to remove all this duplication for each method. It would be a nice use-case for "block" attributes that are effective for all delegated methods, so that it does not need to be repeated needlessly. delegate! {
#[return(self)]
#[store]
to self.builder {
pub fn timeout(mut self, timeout: Duration) -> APIClientBuilder;
pub fn cookie_store(mut self, enable: bool) -> APIClientBuilder;
pub fn connect_timeout(mut self, timeout: Duration) -> APIClientBuilder;
}
} Something like this. |
@Kobzol Those are all excellent and salient points. I hadn't considered whether For clarity's sake, I had figured that a generic approach would be best and was ~90% certain that guessing user intentions is pretty counter to the Rust mindset. I had imagined that something like a Honestly, it's entirely possible that I'm approaching the problem with the wrong mindset. I'm a shameless Rust convert but I am nevertheless a convert, having come to Rust from Python. My impulse to just wrap the underlying type and add the required functionality comes from Python's inheritance being the goto for these types of things. I've read up on Rust's opinion of inheritance, and while I don't disagree with the decision, I'm absolutely having a hard time figuring out the idiomatic Rust way of expressing: import httpx
class APIClient(httpx.AsyncClient):
"""A custom `httpx`-based API client."""
async def get_some_resource(self, resource: str) -> Resource:
"""Get the specified resource from the upstream API."""
return await self.get(f"resources/{resource}") The newtype pattern + |
I scarcely have the need to actually delegate methods like this (which might seem ironic since I maintain this crate, but I got to it by chance mostly, I'm not the original author :D ). Usually when I wrap things, I either provide the original interface using the Regarding the client: do you really want to just add new methods and allow using all existing methods of the Example: Regarding the builder: wouldn't it be enough to simply allow users to build your custom builder from the struct MyBuilder { builder: reqwest::Builder, api_key: String }
impl MyBuilder {
fn from_builder(builder: reqwest::Builder, api_key: String) -> Self {
Self { builder, api_key }
}
} If you only set the API key and nothing else in your builder, then this should be enough. And it has the added benefit that you cannot forget to set the API key (or set it multiple times by accident), otherwise you would not even be able to create the builder. This can be extended for more complex scenarios with the typestate pattern. If you need to set multiple builder attributes, I still think that it's fine to provide the constructor. For builders, you usually just set all the attributes and then call let builder = reqwest::Builder()
.set_a(1)
.set_b(2)
.set_c(3);
let builder = MyBuilder::from(builder)
.api_key("...")
.my_url("...")
.build(); You could even implement a custom trait method to add a method to let result = reqwest::Builder()
.set_a(1)
.set_b(2)
.set_c(3)
.into_my_builder()
.api_key("...")
.my_url("...")
.build(); |
This could be very useful: delegate! {
#[return(self)]
#[store]
to self.builder {
pub fn timeout(&mut self, timeout: Duration) -> APIClientBuilder;
pub fn cookie_store(&mut self, enable: bool) -> APIClientBuilder;
pub fn connect_timeout(&mut self, timeout: Duration) -> APIClientBuilder;
}
} Imagine a scenario where you have a builder composed of two other builders. pub struct ClientBuilder {
client: Client,
request: ApiRequestBuilder, // original api
extension: ApiExtensionBuilder, // extensions to the original api maintained separately
}
impl ClientBuilder {
pub async fn execute(&self) -> Result<Response> {
let request = self.request.build()?
let ext = self.extension.build()?
let request = ActualRequest { request, ext }
let response = client.execute(request).await?;
Ok(response)
}
} Perhaps there is a more elegant way of doing this. Or perhaps a procedural macro to auto generate builders setter methods, i.e. any method that uses |
I probably didn't exactly understand the use-case. I think that builders are better left for specialized crates, like |
I'm working on an API client that's implemented (essentially) as a newtype wrapper around a
reqwest::Client
. For convenience, I'd like to mirrorreqwest
's API as much as possible (no point in reinventing the wheel, right?) so I've also included a newtype wrapper forreqwest::ClientBuilder
as well.The client and builder wrappers basically look like this:
ClientBuilder
APIClient
I've annotated the code block above to illustrate the issue - the return type of the builder methods changes depending on whether or not they're "overridden" by the wrapper type.
My question is - would it be possible to instruct
delegate
to "capture" (re-assign) the return value of delegated calls back to the delegated field? At the moment, doing:causes
delegate
to generate:Ideally (and with the correct attribute / annotation presumably) though, it would generate:
The text was updated successfully, but these errors were encountered: