Omittable properties in structs: any alternatives to Option<T> and builder pattern?

I'm coming to Rust from TypeScript. In TypeScript for a very large number of my functions (probably more than half), instead of taking multiple arguments, they just take a config object (typed using an interface).

For a lot of these config objects, there are many many optional properties that can be omitted altogether. Especially when it comes to things like generic helper functions that perform queries on SQL TABLEs + VIEWs, e.g. filtering on specific columns, or doing SQL UPDATEs on a small number of columns, but not all of them etc.

In Rust I know there's no optional properties in structs (that you can just omit when defining an instance), you need to use field: Option<T> in your struct properties, and then field: None for all the properties that are optional, not a big deal when you might only have about 10 properties.

But for stuff like the SQL TABLEs/VIEWs where there will be massive amounts of fields (for VIEWs maybe even 100s of fields per VIEW), it's just a massive amount of None values that need to be assigned everywhere.

I've been reading into this for a few weeks now, including in discussions where people are looking for solutions to the lack of 'named function arguments', and optional arguments/default values... and I see a lot of recommendations for the builder pattern... but that's an even more massive amount of extra code that needs to be written for every single optional property that you might have anywhere.

Having to write an extra function/method for literally 1000s of optional fields, and purely just to deal with nothing but the fact that they're optional, would probably end up being like 99% of my codebase. And while I do use a lot of codegen for generating the interfaces/structs for all these tables/views and all their fields, I think doing this for Rust might cause the heat death of the universe before it compiles for my project, haha. :slight_smile:

...not to mention that 99% of these methods will never even be called. I either codegen/macro all of them, or go back to writing all this code manually only for the sets of fields needed.

Are there really no other options in Rust here when it comes to dealing with large numbers of optional struct properties and/or functions that with lots optional properties/arguments?

I considered if maybe HashMaps could be used in some places, but given the lack of literal string unions, I get no typing/intellisense on all the field names, and it's just super messy and verbose compared to structs.

One of the big reasons I moved from PHP to TypeScript was the ability to use typed object literals, but all the recommendations I see for using the builder pattern in place of dealing with these missing features kind of feels like going back to PHP and using setters/getters everywhere instead of simple struct/interface literals.

I'm a few weeks into trying to figure this out, and I'm not sure if there is even really a feasible solution in Rust at all? It feels like my only options in Rust are:

  • Give up on a lot of typing on field names
  • Codegen/macro literally 100,000s of lines of code that will mostly never even be used
  • Give up on generic helper functions altogether and write 10x more manual code than I've needed to in TypeScript/Node

I know that I need to adjust my expectations and probably coding styles in dealing with a new language, and I hope this doesn't come off too ranty... but just want to explain my situation as well as I can. Maybe others have felt the same way and found some alternatives?

From quickly reading over your post, it looks like #[derive(Default)] and Foo { a: 1, b: 2, ..Default::default() } should work.

6 Likes

...well that was simple. Haha :slight_smile:

Thanks so much @jhpratt! I wish I'd figured this out earlier. You've helped me a lot, cheers!


In case anyone else comes across this with a similar requirement, here's the docs: https://doc.rust-lang.org/std/default/trait.Default.html

And you can set your own default for your custom enums too: https://doc.rust-lang.org/std/default/trait.Default.html#how-can-i-implement-default