djinni
djinni copied to clipboard
Allow default constructor for generated classes
Add new option for records: xxx = record { a: string; b: string; } deriving (init)
which will generate default constructor i.e. xxx() = default; //C++ xxx() {} //java
The reason is sometimes when records contain lot of members it's impractical to use generated constructor with many arguments. And often many of them have sensible defaults anyway (empty strings, vectors, optionals). But because the generated class already contains 1 generated constructor the default constructor is not created.
If you don't like default constructor for some reason f.e. because it doesn't initialize scalar values to 0 like in java, generate constructor which will value initialize all members i.e. xxx() : a(), b() {}
This would be very useful addition
In C++ a record
is a struct
for which brace-initialization already does the same as the generated constructor, so in theory we could even drop the generated constructor. Then the struct has an implicitly generated default constructor if all members are default-initialized and users have the option to use xxx{}
for zero-initialization and xxx()
for default-initialization. However that might be a breaking change because the types can no longer be initialized with parens, meaning xxx(a, b)
suddenly becomes invalid.
The reason is sometimes when records contain lot of members it's impractical to use generated constructor with many arguments.
I'd say don't use records with too many fields. in such cases I try to group fields together into other records where it makes logical sense and nest them. This doesn't of course work all the time.
Not really sure why structs are even being used. Records should just be classes. Setters and getters should also be predefined for all fields. The defaults for copy and move constructors/assignment-ops should also be generated.
For reference #216
I like this idea, though I'm not a fan of the "= default" version of this which doesn't guarantee any initialization at all. I think it would be best to have well defined default initial values for all types (false, zero, empty), and generate initializers for them in all languages. Alternately, we could consider @mknejp's suggestion of dropping the ctor in C++ entirely and relying on default braced initialization entirely, though that doesn't address the question for other languages.
I also think it would be cool to go further and allow constants to be used as "initializers" for fields which want non-zero initial values (like what we can do in C++11 now), but that goes well beyond the minimum needed here. :)
Yea, the generated ctors (and lack of a default) can definitely get annoying quickly.
@artwyman what about including this option so that it's not breaking, either in the record def or as a djinni flag? (--cpp-with-record-default-ctors
?). I might start using a fork of djinni with https://github.com/dropbox/djinni/pull/216 included otherwise.
The ctors as they are now are there in order to ensure consistency across languages? It seems not too bad to offer an inconsistent but convenient and performant mode as an option.
I think my comments #216 cover this. I like making it configurable, though I'd prefer making it per-record-type via "deriving" rather than a command-line option. However, I feel strongly that the default constructor should give all fields a well-defined value, which a simple "= default" definition won't (depending on how it's used, it will sometimes leave numeric fields in an undefined state). It should be easy for the generator to provide explicit default values, either via initializers in the ctor, or via "= 0" after the field declarations. I would merge a PR which did those two things.
I think the expected behavior of default ctor is a function of what language shapes your perspective. C++ and Obj-C expect allocation without initialization; Java does not. Could we add the change proposed in https://github.com/dropbox/djinni/pull/216/files and add simply the proposed deriving
tag to use default ctors? "Zero-initialization" is not super trivial ... to do it properly encompasses support for user-defined types.
In most cases, I consider creating objects in non-initialized states to be a bug not a feature in the languages which have it. I can see the argument for it in high-performance scenarios, but I think the likelihood of that being relevant to Djinni types is low. On the flip side, Djinni's bridging abilities will expose those semantics to other languages which have different expectations, which I think has the potential to cause a lot of confusion. I'd strongly prefer semantics which initialize everything by default, though I wouldn't object to an opt-in ability to avoid that if someone wants it for high performance.
My use case is performance-sensitive, and the maintainers of my code will have an understanding of multi-language initialization behavior. (And oblivious users will be isolated from the issue through an interface).
Shall we call it "deriving (default-ctor)" ? Then perhaps "deriving (init)" could be reserved for the zero-initialization feature.
I'd be okay with default constructor as a separate option, either in deriving, or on the command-line. It's a bit cpp-specific. Would you create constructors with the same minimal behavior in every language? I guess ObjC can have default field values just as C++ can, while Java will default-initialize fields to zero. Python fields can't exist if they aren't given some explicit value, which could be None, or zero for numbers.
An alternative, at least for the C++ side, would be a subtractive feature which would inhibit the generation of the current all-arguments constructor. That should leave every struct with the auto-generated default constructor, as well as allowing braced initializers for any number of arguments. Honestly, if I were implementing Djinni from scratch today I think that's what I would make the default behavior, since the explicit all-arguments constructor has always felt awkward. That behavior wouldn't even be incompatible with providing full-initialization of all fields as an option too, since the generator could simply declare fields as "int32_t x;" for uninitialized or "int32_t x = 0;" for initialized, without ever having to define a constructor. As it is, since there are backward-compatibility concerns, it probably can't be the out-of-box default, but it could be behavior controlled by global command-line flag, or a per-record "anti-deriving" declaration.