bitcode icon indicating copy to clipboard operation
bitcode copied to clipboard

Add `#[bitcode(skip)]` field attribute

Open Grimeh opened this issue 8 months ago • 3 comments

This adds an attribute for skipping fields when encoding/decoding. When decoding it uses the Default trait. If the field type doesn't implement Default we get a handy compile error and fix suggestion out-of-the-box.

This (sort of) addresses #29 in that it adds an unconditional skip for bitcode::{encode, decode}.

I hacked this together for my own use so it's probably not as clean as it could be. I'm happy to rework it a bit if you have requests/suggestions.

~Edit: still working on the unused variables warnings :)~

Grimeh avatar Apr 27 '25 07:04 Grimeh

Have added #[allow(unused_variables)] in a couple spots to silence compiler warnings from Encode output.

Since destructure_fields is used for binding into and out of structs (so to speak) this is the easiest way to fix the warnings off the top of my head. The alternative I considered was to split up destructure_fields into "in" and "out" variants to use _ and .. for skipped fields where appropriate. Seems like more complexity than it's ultimately worth.

Grimeh avatar Apr 27 '25 08:04 Grimeh

Please adjust your tests so the skipped field's type doesn't implement Encode or Decode, to ensure those bounds aren't generated. E.g. #[derive(Default)] struct Skipped(u32);.

finnbear avatar Apr 27 '25 20:04 finnbear

Ah good catch. Will do that later today and update the PR.

Grimeh avatar Apr 27 '25 22:04 Grimeh

Sorry for the delay, I've been knocked out with the flu this week.

I've adjusted one of the tests with a non-Encode/Decode struct field, no issues with bounds encountered.

Grimeh avatar May 04 '25 02:05 Grimeh

No problem and thanks! Could you please also add a test with a generic skipped field? This would confirm the correct generic bounds are generated.

#[derive(Encode, Decode)]
struct Generic<A, B> {
   present: A,
   #[bitcode(skip)]
   skipped: B
}

#[derive(Encode, Decode, Debug)] // not Default
struct NoDefault(u8);

#[derive(Default, Debug)] // not Encode or Decode
struct Skipped(u8);

bitcode::decode::<Generic<NoDefault, Skipped>>(&bitcode::encode(&Generic{present: NoDefault(0), skipped: Skipped(0)}).unwrap()).unwrap();

// the macro needs to have generated something like this:
impl<A: Encode, B: Default> Encode for Generic<A, B> { ... }
impl<A: Decode, B: Default> Decode for Generic<A, B> { ... }

finnbear avatar May 04 '25 17:05 finnbear

Ah dang, didn't test generic bounds.

It does generate Encode & Decode bounds for generic type params, I'll need to adjust that to be skip-aware.

Additionally, the generated decoder struct has all the generic type params of the struct to be decoded and we get an unused type parameter compile error for any types that we skip completely. Will need to filter out unused type params or generate PhantomData<GenericType> fields in the decoder struct.

I'll set aside some time later this week to work on the above.

Grimeh avatar May 11 '25 07:05 Grimeh

I've come to a solution that I don't entirely dislike, it also somewhat simplifies the PR since the Item interface no longer needs to change.

Skipped fields are no longer excluded from the generated XyzEncoder/XyzDecoder structs, instead the field type is wrapped with PhantomData<T> so that EmptyCoder is used for those fields.

Grimeh avatar May 22 '25 03:05 Grimeh

There's an alternate solution that I'm not as fond of, but I've pushed it to a branch for reference.

On it the unused generic type params are stripped from the generated encoder/decoder. The implementation is more complex but it does mean the generated encoder/decoder don't have the skipped fields and type params.

Here's the specific commit: https://github.com/Grimeh/bitcode/commit/b02c89005f141ad58ff700d75b6b7d7aa70fee06

Here's the diff against main: https://github.com/SoftbearStudios/bitcode/compare/main...Grimeh:bitcode:tmp/remove_skipped_generic_args

If you prefer it to the version in this PR I can clean it up and push it to this PR, I'm not fussed either way.

Not sure if there's any perf implications of having the dummy coder fields vs stripping them. I assume they're more or less stripped out in an optimised build, but I might add a bench with skipped fields to compare the branches.

Grimeh avatar May 22 '25 03:05 Grimeh

Thanks for finishing this off! Glad I could help to get this in.

Grimeh avatar Aug 09 '25 00:08 Grimeh