Adding support for bincode Encode/Decode for a struct defined elsewhere

I have a struct that I want to use. I want to be able to Encode/Decode it using bincode. How can I add a custom function to do that?

I know for Serialize/Deserialize I can add an attribute #[serde(with = "my_serde")] and define a module with the function serialize and deserialize . I found a great source here Custom serialization · Serde .

However, for bincode Encode/Decode I can't find what to do.

Specifically ibe/src/kem/kiltz_vahlis_one.rs at main · encryption4all/ibe · GitHub

Any help?

bincode supports a subset of serde. Isn't the compatibility layer between serde and bincode sufficient for your type to implement Encode and Decode out of the box?

I'm sorry, I'm new to rust. So I'm still learning what the terms "subset" and "compatibility layer" are. My struct that I want to add support to is not owned by me and does not support serde. So I added an attribute to support it myself

#[serde(with = "ciphertext_serde")]
cipher_text: CipherText

mod ciphertext_serde { ... }

Now I can derive Serialize/Deserialize from my struct that holds it. However, I cannot simply write

#[serde(with = "ciphertext_serde")]
#[bincode(with_serde)]
cipher_text: CipherText

Because when adding derive Encode/Decode I get the error the trait bound 'CipherText: Serialize' is not satisfied.

I don't understand the error, so I thought it would be easiest for me to implement support myself, like I did with ciphertext_serde.

If I try to add something like
#[bincode(with = "ciphertext_bincode")]
I get the error (pointing to with) saying Unknown field attribute. What does that mean?

Please post a complete MRE and full error messages.

Cargo.toml:

[package]
name = "test"
version = "0.1.0"
edition = "2024"

[lib]
path = "src/lib.rs"

[dependencies]
serde = { version = "1.0.219", features = ["derive"] }
bincode = { version = "2.0.1", features = ["derive", "serde"] }
ibe = { version = "0.3.0", features = ["kv1"] }

lib.rs:

use bincode::{Decode, Encode};
use serde::{Deserialize, Serialize};
use ibe::kem::kiltz_vahlis_one::CipherText;

#[derive(Serialize, Deserialize, Debug, Encode, Decode)]
pub enum Enum {
	Case {
		#[serde(with = "ciphertext_serde")]
		#[bincode(with = "ciphertext_bincode")]
		cipher_text: CipherText},
}

mod ciphertext_serde {
	use ibe::Compress;
	use super::CipherText;
	use serde::{Serializer, Deserializer};
	use serde::de::Error as DeError;

	pub fn serialize<S>(ct: &CipherText, s: S) -> Result<S::Ok, S::Error>
	where
		S: Serializer,
	{
		let bytes = ct.to_bytes();
		s.serialize_bytes(&bytes)
	}

	pub fn deserialize<'de, D>(d: D) -> Result<CipherText, D::Error>
	where
		D: Deserializer<'de>,
	{
		let bytes: &[u8] = serde::Deserialize::deserialize(d)?;
		let arr: &[u8; CipherText::OUTPUT_SIZE] =
			bytes.try_into()
				.map_err(|_| D::Error::custom("invalid ciphertext length"))?;
		let ct_opt = CipherText::from_bytes(arr);
		ct_opt
			.into_option()
			.ok_or_else(|| D::Error::custom("ciphertext decompression failed"))
	}
}

build message:

error: Unknown field attribute
 --> test/src/lib.rs:9:13
  |
9 |         #[bincode(with = "ciphertext_bincode")]
  |                   ^^^^

Hmm, maybe if you wrap CipherText in a newtype?

#[derive(Deserialize, Serialize)]
struct MyCipherText(#[serde_with = "ciphertext_serde")] CipherText);

and then

#[derive(Decode, Encode)]
struct Foo {
    #[bincode(with_serde)]
    cipher_text: MyCipherText,
}

presumably I could. I will if I have to. However, what is the reason that both #[bincode(with = "ciphertext_bincode")] and #[bincode(with_serde)] don't work?

serde_with = "..." doesn't implement Serialize or Deserialize for the type of the field, but rather is an ad-hoc way to insert alternative (de)serialisation logic into serde's processing. This is incompatible with the code generated by the bincode::Encode/bincode::Decode macros, which relies on Serialize/Deserialize actually being implemented for the type.

thanks. I guess I'll have to use another type (or not support Encode, Decode)

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.