Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unable to use Compact on generic T: HasCompact #218

Closed
kianenigma opened this issue Jul 23, 2020 · 7 comments
Closed

Unable to use Compact on generic T: HasCompact #218

kianenigma opened this issue Jul 23, 2020 · 7 comments

Comments

@kianenigma
Copy link
Contributor

The below code demonstrates the problem. It is an attempt at doing a custom Encode implementation of a complex type which takes advantage of the fact that the internals of the complex type are all compact-encodable.

use parity_scale_codec::{Encode, Decode, HasCompact, Compact, CompactAs};

#[derive(Default)]
struct Solution<V, T, W> {
	votes1: Vec<(V, T)>,
	votes2: Vec<(V, (T, W), T)>,
	votes3: Vec<(V, [(T, W); 2usize], T)>,
}

#[derive(Default)]
struct BareSolution {
	votes1: Vec<(u32, u16)>,
	votes2: Vec<(u32, (u16, u64), u16)>,
	votes3: Vec<(u32, [(u16, u64); 2usize], u16)>,
}

// This works fine
impl Encode for BareSolution {
	fn encode(&self) -> Vec<u8> {
		let mut r = vec![];
		self.votes1
			.iter()
			.map(|(v, t)| (<Compact<u32>>::from(*v), <Compact<u16>>::from(*t)))
			.for_each(|(v, t)| {
				v.encode_to(&mut r);
				t.encode_to(&mut r);
			});
		r
	}
}

// This will not work.
impl<V: HasCompact + CompactAs + Copy, T: HasCompact + CompactAs + Copy, W: HasCompact + CompactAs + Copy> Encode for Solution<V, T, W> {
	fn encode(&self) -> Vec<u8> {
		let mut r = vec![];
		self.votes1
			.iter()
			.map(|(v, t)| (Compact::from(*v), Compact::from(*t)))
			.for_each(|(v, t)| {
				// will not work because the compiler fails to identify `impl Encode` for `V`
				// v.encode_to(&mut r);
				// will trigger recursive derive error.
				// <Compact<T> as Encode>::encode_to(&t, &mut r);
			});
		r
	}
}

Looking at the code, it seems to be that <Comact<T> as Encode> should work fine as long as T: HasCompact + CompactAs, based on:

impl<T> Encode for Compact<T>
where
for<'a> CompactRef<'a, T>: Encode,
{

and

impl<'a, T> Encode for CompactRef<'a, T>
where
T: CompactAs,
for<'b> CompactRef<'b, T::As>: Encode,

Albeit I might be missing something.

@gui1117
Copy link
Contributor

gui1117 commented Jul 23, 2020

You can use HasCompact without requiring other traits:

#[derive(Default)]
struct Solution<V, T> {
	votes1: Vec<(V, T)>,
}

impl<V: HasCompact, T: HasCompact> Encode for Solution<V, T> {
	fn encode(&self) -> Vec<u8> {
		let mut r = vec![];
		self.votes1
			.iter()
			.for_each(|(v, t)| {
				let compact_v = <<V as HasCompact>::Type as EncodeAsRef<'_, V>>::RefType::from(v);
				let compact_t = <<T as HasCompact>::Type as EncodeAsRef<'_, T>>::RefType::from(t);
				compact_v.encode_to(&mut r);
				compact_t.encode_to(&mut r);
			});
		r
	}
}

#[test]
fn test_solution() {
	let s = Solution {
		votes1: vec![(0u128, 0u128)]
	};
	assert_eq!(s.encode(), (Compact::<u128>(0u128), Compact::<u128>(0u128)).encode());
}

@gui1117
Copy link
Contributor

gui1117 commented Jul 23, 2020

I know traits are confusing but HasCompact can be used as is, the associated type Type is the compact type corresponding,
I guess this answers the issue

@kianenigma
Copy link
Contributor Author

kianenigma commented Jul 23, 2020

I actually came to this, but EncodeAsRef is not public and thus cannot be used. EncodeAsRef is pub, but it is not re-exported from the main crate lib file.

Perhaps we either export this, or some other easier way in the upcoming release.

@kianenigma
Copy link
Contributor Author

Alternatively I was thinking, why not expose a fn compact_encode() in trait HasCompact? I would exactly do what you said, and is basically just a wrapper around Encode::encode, but much easier to use.

@gui1117
Copy link
Contributor

gui1117 commented Aug 13, 2020

Alternatively I was thinking, why not expose a fn compact_encode() in trait HasCompact? I would exactly do what you said, and is basically just a wrapper around Encode::encode, but much easier to use.

I think this would need more reorganization in the trait itself. Because the function compact_encode in HasCompact would be expected to be same logic as HasCompact::Type::EncodeAsRef.

I actually came to this, but EncodeAsRef is not public and thus cannot be used. EncodeAsRef is pub, but it is not re-exported from the main crate lib file.

Perhaps we either export this, or some other easier way in the upcoming release.

Maybe it has been changed since then, but as far as I see EncodeAsRef is public and reexported https://substrate.dev/rustdocs/v2.0.0-rc5/parity_scale_codec/trait.EncodeAsRef.html

@gui1117
Copy link
Contributor

gui1117 commented Aug 13, 2020

Actually the above example #218 (comment) compiles to me when I use parity-scale-codec has dependency, I think we can close this issue no ?

@gui1117 gui1117 closed this as completed Aug 13, 2020
@kianenigma
Copy link
Contributor Author

yeah your examples worked fine for me as well. It is just a bit unergonomic.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants