-
Notifications
You must be signed in to change notification settings - Fork 76
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
Ergonomics issue: RpcReplyPort with custom BytesConvertable impls #79
Comments
So in general I agree, and there's probably some blanket implementations I missed in
I agree there's probably bits missing here, but I'm not yet confident I found a good way to handle it uniformly. That being said, let me see if I can come up with something for the |
I'm happily open to suggestions here, especially as it doesn't really impact the core |
Thanks for the swift reply!
One option I can think of would be to write a blanket impl as The idea would be to "avoid" the other Vec impls that you've added for Vec over numerics i'd be happy to try something out if this is a direction you'd consider going in (assuming it works) |
Please feel free to put something together! Would be happy to iterate on it, but really open to anything here as |
I've spent some time playing with this, and managed to do a few things here:
impl<T> BytesConvertable for Vec<T>
where T: ToBytes<Bytes = <T as FromBytes>::Bytes> + FromBytes + Default + Sized + Clone, <T as FromBytes>::Bytes: Sized
{
fn into_bytes(self) -> Vec<u8> {
self.into_iter().flat_map(|val| val.to_be_bytes().as_ref().to_owned().into_iter()).collect()
}
fn from_bytes(bytes: Vec<u8>) -> Self {
let size: usize = <T as Default>::default().into_bytes().len();
let num_el: usize = bytes.len() / size;
let mut result = vec![<T as Default>::default(); num_el];
let mut data = T::to_be_bytes(&<T as Default>::default());
for offset in 0..num_el {
let offs: usize = offset * size;
data.as_mut().copy_from_slice(&bytes[offs..offs + size]);
result[offset] = T::from_be_bytes(&data);
}
result
}
}
impl<T> BytesConvertable for Vec<T>
where T: BytesConvertable + Default + Sized + Clone + !PrimInt
{
... ^ there were two problems with this that I failed to anticipate -- the first is that rust is limited on blanket impls -- you can't have two blanket impls for the same trait, even if there's a hard prevention of overlap (in fact you can't have blanket impls on !PrimInt at all). The second problem is that even if this did work, you'd still need a way to deserialise values of unknown size. So finally I wrote a working implementation by introducing a new "NullTerminated" trait. Maybe you can take a look here and let me know if you think this would be a useful contribution? The one other thing I would like to add is a custom derive macro for NullTerminated so that users can "opt-in", so the API would finally be something like this: use ractor::BytesConvertable;
#[derive(NullTerminatedBytesConvertible)]
struct MyCustomValue {}
impl BytesConvertable for MyCustomValue {
...
}
#[test]
fn vec_of_custom_value() {
let values = vec![MyCustomValue {}, MyCustomValue{}]
let bytes = values.into_bytes();
let result: Vec<MyCustomValue> = BytesConvertable::from_bytes(bytes);
// assert values == result
} There are some problems with using null-terminator, i.e. if the serialised version of your type has actual null bytes in it then they'll get incorrectly treated as deliminators during the from_bytes step. One improvement might be to use const generics, replacing |
Thanks for the follow-up, I'll take a look when I get a sec! |
OK I finally had a chance to look into this, and this is a really great exploration of the problem so first off thanks for that! Just a thought, since we kind of do it implicitly in some |
And I'm also not set-in-stone on |
Hi again!
I have implemented
BytesConvertable
for a custom type, something like this:This works well and fine on its own, but I wanted to express an
RpcReplyPort
in terms of a wrapper aroundValue
, i.e.Option<Value>
orVec<Value>
Unfortunately, there's no blanket
impl <T> BytesConvertable for Vec<T> where T: BytesConvertable
, so this doesn't work.Further, as a user, I can't
impl BytesConvertable for Vec<Value>
because it's not permitted to implement foreign traits for foreign typesI had a look at your existing
BytesConvertable
impls and noticed that you manually implement a family of serdes using macros.I did also try to build a blanket impl myself but it got very messy when I ran into a lack of numeric traits
For reference, I was trying to wrangle some code like this:
(and some other variations, e.g. trying to use const generics in place of
std::mem::size_of::<T>()
, etc)I came across this PR that I think would add the special sauce to do an appropriate blanket impl for numerics and therefore I think also blanket impl for
Vec<T> where T: BytesConvertable
.In summary:
RpcReplyPort<Collection<T>>
seems to be to wrapVec
with a custom struct and thenimpl BytesConvertable for MyVecWrapper<Value>
impl BytesConvertable for Vec<T> where T: BytesConvertable
BytesConvertable
feels clumsy compared to using serde -- it would be nice to have serde integration, perhaps behind a feature flag?The text was updated successfully, but these errors were encountered: