-
Notifications
You must be signed in to change notification settings - Fork 1k
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
feat: Add IntoRequest
and IntoStreamingRequest
traits
#66
Conversation
tonic/src/request.rs
Outdated
use futures_core::Stream; | ||
|
||
/// A marker trait that should be implemented for all input messages. | ||
pub trait RequestMessage {} |
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 don't think we need this marker trait since there is only ever one generic?
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.
You are right, it is not strictly necessary and a previous implementation did not have it. I decided to include it because:
- It sort of helps us constrain the other trait implementations. It just feels weird for IntoRequest or IntoStreamingRequest impl to exist for any type that is not an input to a rpc method.
- I am toying around with an implementation that would let us call the streaming methods with iterators and having this bound sort of helps. Not sure if this will work of if it even makes sense though.
- It makes code generation extremely simple, basically 1 line per input type.
- Triggered by the breakage in tonic-interop, I initially thought that it was going to be necessary to expose this marker for users to implement in certain scenarios, but that seems not to be necessary. I found the reason why interop is broken and has nothing to do with this.
None of these reasons hold much water though. In the end, however, this should be an internal implementation detail and as long as we are not changing the public api willy-nilly we should be able to iterate on the actual implementation until we find the optimal one.
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.
Yeah, those points make sense, my real motivation for removing it is that it's not actually required and just simplifies things. Happy either way.
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.
My 2¢ is that since the Request
type itself is unconstrained, this shouldn't be added; if we are going to constrain the Message
type on IntoRequest
, we should also constrain Request
, and I'd consider adding that in a separate branch.
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.
Also, I think it's potentially very surprising to generate implementations of a doc(hidden)
trait in a user's codebase — I'd find it weird to run cargo doc
on my project and see implementations of a trait that doesn't appear to exist.
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 removed this trait as suggested. IntoRequest for T is generated now instead, please let me know what you think.
I've fixed the issue with interop. This implementation seems to work well with different The only way I found to make it work without the I'm happy to make that change if you prefer, it's easy. |
@hawkw what do you think about having a |
and @alce sorry for the delay on this I just wanna see what eliza has to say since she came up with the original spec other wise this looks good! |
tonic/src/request.rs
Outdated
use futures_core::Stream; | ||
|
||
/// A marker trait that should be implemented for all input messages. | ||
pub trait RequestMessage {} |
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.
My 2¢ is that since the Request
type itself is unconstrained, this shouldn't be added; if we are going to constrain the Message
type on IntoRequest
, we should also constrain Request
, and I'd consider adding that in a separate branch.
tonic/src/request.rs
Outdated
use futures_core::Stream; | ||
|
||
/// A marker trait that should be implemented for all input messages. | ||
pub trait RequestMessage {} |
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.
Also, I think it's potentially very surprising to generate implementations of a doc(hidden)
trait in a user's codebase — I'd find it weird to run cargo doc
on my project and see implementations of a trait that doesn't appear to exist.
@alce can you resolve the conflicts? I think CI may not be running because of that? |
Goofed. Fixing... |
I am having second thoughts about this PR. On the one hand, as Carl and Eliza pointed out, it makes writing client calls a lot nicer. Every time I write client code I moan about having to write Request::new() explicitly. On the other hand, it just doesn't feel entirely right to me. I can't really put my finger on it but it makes the API sort of asymmetrical. Even if we were to implement something similar for Response::new() (which btw doesn't really bother me much). Maybe if we had to call let message = HelloRequest{name: "hello".into()}
let response = client.say_hello(message.into_request()) Thoughts? |
@alce I kinda agree with you, that actually might be better. What do you think @hawkw @carllerche ? |
I don't really see much of a difference between making users write out With that said, I don't think we should close this PR. I'd rather merge the code as-is, and call
I think we should just make the same change on the server side and add an Just my two cents. |
The comment about asymmetry was not so much about request vs response. The weirdness I get is more about client vs server. This client code let point = Point { lat: 1, lon: 2 };
let response = client.get_feature(point).await?; and this server's signature async fn get_feature(&self, request: Request<Point>) -> Result<Response<...>; are surprising, particularly when the client's function signature is not easily available, as is the case when following the default getting started. But then, as I was writing this, I realized that:
|
@alce do you think we can replace the generated |
I couldn't figure out how to do it without a bound. That's one of the reasons why I had the I will give it another go tomorrow to see if I can find another way. |
I'm still not convinced that this indirection is worth it honestly. I think the |
@LucioFranco I think I've found a more natural way to do it, I'll push the changes later today. With regards to the so-called asymmetry I speak of, I think I'm splitting hairs and considering only my own workflow and bias. In a larger, more realistic context, this is almost irrelevant. |
Here's a cleaner implementation that combines approaches discussed before. It introduces only one new trait and does not generate any code. |
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.
This is great. I like that the trait implementations are no longer generated; I think this will be much less surprising for users. Left a couple of minor suggestions.
tonic/src/request.rs
Outdated
fn into_streaming_request(self) -> Request<Self::Stream>; | ||
} | ||
|
||
/// Conversion into a unary `Request`. |
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.
Can we add a bit more docs here to explain how this works? I am thinking maybe an example that shows constructing a request and passing it and one with the message explaining the trait?
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.
Could use https://doc.rust-lang.org/stable/std/convert/trait.Into.html as inspiration
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.
Done. Docs are slightly better but I'm not sure they are clear enough.
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.
Wonderful! Thank you @alce for fighting through this! <3
/// use tonic::Request; | ||
/// | ||
/// client.get_feature(Point {}); | ||
/// client.get_feature(Request::new(Point {})); |
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.
😍
IntoRequest
and IntoStreamingRequest
traits
Motivation
This PR implements
IntoRequest
andIntoStreamingRequest
traits, as outlined in #34. At the moment, both unary and streaming requests work but interop crate needs to be updated for this PR to be complete. I'll wait for feedback and implement what is remaining if it's decided we should move forward with this.Solution
Implementation is almost exactly what is discussed in #34.