-
Notifications
You must be signed in to change notification settings - Fork 222
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
Amenities and ergonomics #136
Comments
I am working with
I realize that working like this introduces performance penalty (One of the major selling points of capnp is having no encoding/decoding step). However, If I want to enjoy the no-encoding feature I will have to surrender to capnp and let it pollute my whole codebase with lifetimes and I am willing to give up on performance in favour of clear and easy to maintain code. (At the same time I realize that other people might not want to). To be more specific about what my problem is, I want to introduce an example from my code base:
struct FriendsRoute {
publicKeys @0: List(PublicKey);
# A list of public keys
}
pub struct FriendsRoute {
pub public_keys: Vec<PublicKey>,
}
pub fn ser_friends_route(
friends_route: &FriendsRoute,
friends_route_builder: &mut funder_capnp::friends_route::Builder,
) {
let public_keys_len = usize_to_u32(friends_route.public_keys.len()).unwrap();
let mut public_keys_builder = friends_route_builder
.reborrow()
.init_public_keys(public_keys_len);
for (index, public_key) in friends_route.public_keys.iter().enumerate() {
let mut public_key_builder = public_keys_builder
.reborrow()
.get(usize_to_u32(index).unwrap());
write_public_key(public_key, &mut public_key_builder);
}
} My plan is to have something like this: #[capnp_conv(funder_capnp::friends_route)]
pub struct FriendsRoute {
pub public_keys: Vec<PublicKey>,
} Which will automatically do all the magic of producing the middle serialization layer (2), and also verify during compile time that the Rust structure has the same fields as the capnp structs. I plan to write it as a procedural macro. Some inspiration for this plan is taken from the code I have seen in Facebook's Calibra, here: I will start by writing the code as separate procedural macro crate for the Offst project. If things work out well, maybe we can add this idea into this repository's automatic code generation. What do you think? |
@realcr sounds good to me! |
I have been using some of my own stuff under the hood, which boil down to basically the same thing. My setup is the following: /// Makes the type an internal Rust representation of a Capnp.
pub trait AsCapnp {
type CapnpRepr: for<'a> Owned<'a>;
fn write_into<'a>(&self, builder: <Self::CapnpRepr as Owned<'a>>::Builder);
fn read_from<'a>(reader: <Self::CapnpRepr as Owned<'a>>::Reader) -> Result<Self, capnp::Error>
where
Self: Sized;
} Maybe I should better split it in two traits, like Serde does, but it has been work well so far. I indeed have tried at first to force myself to use Cap'n Proto as natively as I could, but it always ended up being better to create an internal struct anyway. Besides, I have been working with the packed representation for most purposes (I am really in for the RPC part of the deal). Oh! I have also these two macros for stub module generation: #[macro_export]
macro_rules! stubs {
( $( $( $declaration:ident )* ; )* ) => {
$(
_stub! { $( $declaration )* }
)*
};
}
#[macro_export]
macro_rules! _stub {
(mod $module:ident) => {
pub mod $module {
#![allow(unused)]
include!(concat!(env!("OUT_DIR"), "/", stringify!($module) , ".rs"));
}
};
(pub mod $module:ident) => {
pub mod $module {
#![allow(unused)]
include!(concat!(env!("OUT_DIR"), "/", stringify!($module) , ".rs"));
}
};
}
stubs! {
pub mod foo_capnp;
mod bar_capnp;
} These could also be of some use. |
Hi, I made some progress with the suggested plan.
ExampleHere is a small snippet from one of the tests, to show the syntax. Capnp file: struct FloatStruct {
myFloat32 @0: Float32;
myFloat64 @1: Float64;
} Rust code: use std::convert::TryFrom;
use offst_capnp_conv::{
capnp_conv, CapnpConvError, CapnpResult, FromCapnpBytes, ReadCapnp, ToCapnpBytes, WriteCapnp,
};
#[capnp_conv(test_capnp::float_struct)]
#[derive(Debug, Clone)]
struct FloatStruct {
my_float32: f32,
my_float64: f64,
}
/// We test floats separately, because in Rust floats to not implement PartialEq
#[test]
fn capnp_serialize_floats() {
let float_struct = FloatStruct {
my_float32: -0.5f32,
my_float64: 0.5f64,
};
let data = float_struct.to_capnp_bytes().unwrap();
let float_struct2 = FloatStruct::from_capnp_bytes(&data).unwrap();
} Currently supported
Things left to do
I hope to complete some of those things in the following week. I also plan on trying to use Currently the code is part of the Offst project. I put the code inside the Offst repository because I can't afford to wait to have it available. @dwrensha : |
Any future progress on this? I feel that the macro approach is the best path forward. The existing approach from @realcr looks extremely promising, and imo should be an officially supported way to use capnp. |
The full code for the macros approach can be found here (Coded as two crates): You have full permission to add it into this repository. (It has exactly the same license as this repository, so no paperwork is required). I will be more than happy to have I think that it is not perfect, and the code probably needs some polishing, but it works great for me. Hopefully it will be useful for other people. |
The code generated by Edit: It's much better with the improved setter ergonomics from the 0.19 version. Thank you @dwrensha! |
So... I have been working with Cap'n Proto in Rust for some time by now and although I quite happy with it, I find myself writing a lot of boilerplate. How viable would it be for we to create abstractions based on Rust's macro system (thinking Serde, Rokcet, etc...) to make coding experience more fun?
Is this even the scope of any of the current crates?
I could help a bit on that.
The text was updated successfully, but these errors were encountered: