-
Notifications
You must be signed in to change notification settings - Fork 86
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
Proposed API change: move to getters and setters #235
Comments
Hi @snproj ! I heavily use quick-protobuf in the embedded (no_std) project and like the approach with public fields in the generated structs. That approach is significantly less noisy than getters/setters one. I compared Google's C++ generated code vs quick-protobuf. Though I use only the Proto2. |
I find working with the struct fields very nice. The objects are just structs. I don't need to worry about a new API when passing the structs around. An added benefit is that I add I hope we can come up with a way to make this work with the existing struct fields without resorting to setters. |
Thanks for the feedback! One concern I have can be shown in the following example: syntax = "proto2";
message A {
optional int32 a = 1 [default=3];
} This generates some Rust struct with field With field presence, we should have some way of checking (usually through a generated hazzer method This affects serialization too; if This is summarized in the following chain of events:
Currently, I can't think of a good way to enable a user to do this without the use of getters and setters. (Also, 3 is just a random number, obviously; the point is just that it's a default value.) Let me know if I've got some concepts wrong above / if you have any ideas! There's a good chance I'm just missing something obvious. |
Hypothetical generated code using structs: In the protobuf runtime crate: enum OptionalField<T> {
Set(T), // T is the set value
Unset(T), // T is the default value
} Generated struct struct A {
pub a: OptionalField(i32),
} We could implement a good portion of the We could also add a |
That sounds like a good idea! I'm currently experimenting with implementing this or something similar for all singular fields (as It might not be ideal to allow the user to manipulate the What would really be nice is if this were currently possible: pub enum SettableField<T, const N: T> {
Set(T),
Unset(N), // should hold the default value
} but alas it seems pretty far away. Even if we make It does seem, that the only way to have an immutable default value for each field known to the code while still allowing the user to mutate the struct is to hardcode it with codegen somehow; as of now I can't seem to think of a way though. Do you think we should just trust the user not to touch the |
It might be a bit hacky but you can make a newtype that implements |
Just an update, over the course of thinking/testing I've found some problems that arise with these suggestions: Set/Unset
|
Another ProposalWe can actually pretty comfortably live without getters and setters; I think the most crucial thing they would do (that is hard to replace) is to ensure that reading a variable will yield the correct default if that variable is not set. So... I propose that we just put the user in charge of that 😁 it's not hard; we will provide an easy way to do so that should always work. But the user will need to remember to do this. Keep in mind that the following example is the most complicated case (Proto2 Something like: syntax = "proto2";
message Thing {
optional int32 a = 1 [default = 3];
} Will produce: struct Thing {
pub a: Option<i32>,
}
impl Thing {
const DEFAULT_A: i32 = 3;
} And using this in a Rust program: fn main() {
let thing = Thing::default();
assert_eq!(thing.a, None); // optional field initialized as None
do_something_with_a(thing.a.unwrap_or(Thing::DEFAULT_A)); // when we want to read from field a, we need to remember to supply the default as alternative
} Individual CasesProto2 requiredRepresented by plain type. Custom defaults will only influence the initial value of the field when the struct is initialized; it will not be "remembered" anywhere else in the API.
Technically, Google's own implementation does provide ways for hazzing and clearing. However, my assumption is that this is only meant to be used when a struct is just initialized, to give an initial value before the user starts assigning values, not during serialization (since these fields are REQUIRED in sound messages after all). Proto2 optionalRepresented by Custom defaults will generate a predictably-named associated constant in the
Proto3 defaultRepresented by plain type. Custom defaults not allowed in Proto3.
Proto3 optionalRepresented by Custom defaults not allowed in Proto3. Notably, the Proto3 defaults align with the Rust ones. Therefore, instead of using
Isn't this almost getters and setters?Yes, it would be very easy to transform this into a getter and setter API; this approach basically sidesteps it at the last moment to provide direct access to types instead. If people think it's a good idea, maybe a build flag or magic comment might be possible to additionally autogenerate them for convenience. |
I like this last proposal. I also think adding a flag or magic comment to generate getters that will return default values is also a good idea. |
I am also in favor of the last proposal. In addition to the constant, can we also generate a getter |
I think the getters is the wrong approach. I came to this library for performance and simplicity. I think if you need this more advanced api stuff using one of the other libraries should be fine. Since we are talking about things which are implicit to how protobuf is supposed to work, I think those can just be represented directly without extra fuss. Which is to say, i think I do not think that there has to be a way to tell "was this data the default or was it set in the data" in this library. |
Proposal
I'd like to hear if there are any opinions on moving to a generated getter/setter API for fields in messages, enums etc. rather than simply making fields public and accessing them directly.
Why?
Current implementation
I'm trying to implement a more compliant Proto2/Proto3 distinction (among other things, attempting to fix #219 and introduce field presence). These are concepts that (in my relatively inexperienced view) live mainly in how the user interacts with the field value, and thus might be implemented most intuitively/tidily through an abstraction layer there.
I'm not saying it's impossible to maintain the current interface while adding these concepts (I'm trying that out right now to see how it goes), but I think doing so is messier, and if we want to make this kind of API change, the earlier the better.
On a more minor point, I also feel that if I were a user, it would make things clearer. For example, if I have a Proto3 field
msg.x
with a default value of 0, setting that field to 0 should mean that it is not serialized.set_x(0)
would make me more aware of these possible side effects than justx = 0
.Also, if at some point we add codegen to generate other common protobuf methods (such as some kind of
clear_field()
, hazzers, etc.), getters and setters would fit right into this kind of paradigm.The text was updated successfully, but these errors were encountered: