-
Notifications
You must be signed in to change notification settings - Fork 526
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
Make it easy to write forward-compatible code: struct builders? #399
Comments
I don't agree that adding a field to a message being a backwards compatible wire change is sufficient motivation to also guarantee that it's a source-compatible change. There are plenty of other guaranteed wire-compatible changes that typically are not source compatible in any (typed) language bindings, e.g. widening an integer, moving a field into a I do think
This appears to work just fine: https://gist.github.com/a64fe9b1dd55d89ed8687381395f02d4 |
Maybe that's an artifact of the type being in the same crate? I haven't used |
Hi! Thanks for your response.
Agreed: not all wire-compatible changes are source-compatible. But Correspondingly, even though Go, Java, and C++ are all typed, their
Absolutely; I’m a huge fan of this workflow as well (“change one type,
The “change one type, fix all the errors, it works first try” flow is
Yeah, that’s right:
By the way, I played with sketching out an API for this, and was pleased |
That link appears to be to Google Cloud Client guidelines, that's not really relevant to the design of a general purpose protobuf library. I haven't seen any protobuf docs or guidelines that say anything about source compatibility of generated code in any context, and in practice there is no such guarantee as I pointed out. WRT
|
Also note that you could in theory attach a general purpose builder derive macro to |
Combination of I kinda like the idea of general-purpose builder although I'm not sure if there's a reason why prost-specific could be better. One thing that comes to my mind is avoiding naming conflicts (if someone funny names protobuf messages My use case is a library for quite large gRPC protocol that I don't have the time to write cleaner bindings for. (I wish I had.) |
https://lib.rs/crates/proto-builder-trait This seems to do the job? |
Consider repeated fields: message HelloRequest {
repeated string flags = 2;
} The builder for impl HelloRequestBuilder {
pub fn flags<T>(mut self, flags: impl IntoIterator<Item = T>) -> Self
where T: Into<String>,
{
self.inner.flags = flags.into_iter().map(Into::into).collect();
self
}
} Stylistically, I don't like the builder type becoming a part of the API that you have to use by name. It's better to add an associated fn to the message type, and have the builder type tucked away in a submodule: impl HelloRequest {
pub fn builder() -> hello_request::HelloRequestBuilder {
todo!()
}
} |
As realized in #901, a protobuf-specific generator can provide much more ergonomic API than a general purpose derive. |
In protobuf, adding a field to a message is considered a backward
compatible change. It’s compatible at the wire format level and also
at the source level for many sets of bindings, including the standard
bindings for Python, Go, C++, and Java.
Prost makes it easy to directly initialize structs of message type:
But then if a new type is added to the message, the Rust code fails to
compile, because it’s no longer initializing all the fields.
One way to achieve compatibility is to spread in
..Default::default()
:This works, but (a) you have to remember to do it every time, and (b) it
can get a bit spammy when there are multiple layers of nesting:
It would be nice to have a way to guarantee better compatibility. One
approach would be some kind of optional builder API:
I don’t know what exactly the right ergonomics are here, especially
around nested fields. (I half-wonder if the builder should take a
closure, but I think that that might get a bit hairy.)
A nice side-effect of such a builder pattern would be that the methods
could take
impl Into<FieldType>
, so you could pass&str
literalsrather than having to write
.to_string()
/.into()
everywhere.An option to set
#[non_exhaustive]
on structs doesn’t sound quiteright to me, since (a) that doesn’t do anything inside the same crate
and (b) then you can’t use functional record updates (
..default()
).It’s fine with me if the struct fields remain public; we can have a
style recommendation that says “always use builders when building
messages of a type that you don’t control”, or something. Though being
able to control that via a codegen option would be nice, too.
This is an important consideration for using Prost in shared or large
codebases, especially monorepos. It’s common to have a shared protobuf
definition with autogenerated bindings that many teams depend on.
Enabling the proto owners to make forward-compatible changes without
breaking other teams’ builds would be a requirement to use Prost.
The text was updated successfully, but these errors were encountered: