serde icon indicating copy to clipboard operation
serde copied to clipboard

The Stunt Double Problem

Open Kyuuhachi opened this issue 2 years ago • 2 comments

It is very common that a derived De/Serialize implementation is almost what you want, but not quite — for example doing manual processing if the input is a string, but use the derived if it's a map/struct; or doing post-processing to fill in a skipped field based some other fields. In cases like these, you want the derived functions so that you can call them, but you do not want the derived trait impls because you want to implement them yourself.

Remote derives are useful for this. In particular, #[serde(remote = "Self")] places the functions as inherent on the type instead of in the trait impl, which sounds like exactly what we want... Except they're pub, and they shadow the functions from the trait. This means that calling MyType::deserialize(des) will call the inherent (derived) function instead of the trait (customized) one. Oops!

The common solution, as far as I know, is to create an exact copy of the type, which I like to call a "stunt double", which you derive the traits on (tagged with remote = "MyType" to skip manual conversion). This causes a burden on maintenance, since the two types need to be kept in sync[^sync], in addition to being a pain to write and read.

<flawed proposal> All it would take to solve this is some customization over where the remote functions are placed. My suggestion would be that in addition to allowing remote to take a type, it would also accept mod module_name, with an optional visibility specifier. This would behave similarly to remote = "Self", except the functions are placed inside a newly created module instead of an inherent impl on the type itself. </flawed proposal>

As I was writing the above paragraph, I realized that it won't be that easy — the serialize and deserialize derives are run separately, so it'd try to create the module twice. Gonna need a more complex solution, I guess.

[^sync]: The compiler will catch you if you forget to sync a struct field, but adding an enum variant without updating the stunt double is a bug that can easily go uncaught for a while.

Kyuuhachi avatar Nov 19 '23 00:11 Kyuuhachi

Why you want to specify module? It seems that just customization of visibility will be enough for your use case. Maybe also add an ability to customize generated name.

Mingun avatar Nov 19 '23 11:11 Mingun

Non-pub would still shadow the trait functions inside the local, though. Not as bad as having it public, but still a bit of an unnecessary risk. Customizing name plus visibility would probably be sufficient, yeah.

Nov 19, 2023 12:27:12 Mingun @.***>:

Why you want to specify module? It seems that just customization of visibility will be enough for your use case. Maybe also add an ability to customize generated name.

— Reply to this email directly, view it on GitHub[https://github.com/serde-rs/serde/issues/2651#issuecomment-1817826075], or unsubscribe[https://github.com/notifications/unsubscribe-auth/AALZWNXEV5PACJR35EC7PNLYFHUI7AVCNFSM6AAAAAA7RJX6S2VHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMYTQMJXHAZDMMBXGU]. You are receiving this because you authored the thread. [Tracking image][https://github.com/notifications/beacon/AALZWNVTKQZE7IEMINYI643YFHUI7A5CNFSM6AAAAAA7RJX6S2WGG33NNVSW45C7OR4XAZNMJFZXG5LFINXW23LFNZ2KUY3PNVWWK3TUL5UWJTTMLHJRW.gif]

Kyuuhachi avatar Nov 19 '23 11:11 Kyuuhachi