Initial push

This commit is contained in:
Edsko de Vries 2023-03-17 09:22:04 +01:00
parent d1327c45f4
commit c46977354d
17 changed files with 1205 additions and 0 deletions

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
target/
Cargo.lock

6
Cargo.toml Normal file
View file

@ -0,0 +1,6 @@
[workspace]
members = [
"haskell-ffi",
"haskell-ffi-derive",
]

2
README.md Normal file
View file

@ -0,0 +1,2 @@
# Rust library for easy interop with Haskell

View file

@ -0,0 +1,12 @@
[package]
name = "haskell-ffi-derive"
version = "0.1.0"
edition = "2021"
[lib]
proc-macro = true
[dependencies]
syn = "1.0"
quote = "1.0"
proc-macro2 = "1.0"

View file

@ -0,0 +1,85 @@
//! Macro for deriving `HaskellSize` instances for structs
//!
//! Implementation is adapted from the `heapsize` example in the `syn` crate.
//! The implementation is not identical, however: `haskell_size` does not take
//! any value as input, but is entirely type-based.
use proc_macro2::TokenStream;
use quote::quote;
use syn::{
parse_macro_input, parse_quote, punctuated::Iter, Data, DeriveInput, Field, Fields,
GenericParam, Generics,
};
/// Derive `HaskellSize` instance
///
/// NOTE: Only structs are currently supported.
#[proc_macro_derive(HaskellSize)]
pub fn haskell_size_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
// Parse the input tokens into a syntax tree.
let input: DeriveInput = parse_macro_input!(input as DeriveInput);
// Used in the quasi-quotation below as `#name`.
let name = &input.ident;
// Add a bound `T: HaskellSize` to every type parameter T.
let without_tag: Generics = add_trait_bounds(input.generics);
// The instance itself must get an additional `Tag` argument
//
// NOTE: Things will go badly if one of the user's parameters is also named `Tag`.
let mut including_tag: Generics = without_tag.clone();
including_tag
.params
.push(GenericParam::Type(parse_quote!(Tag)));
let (including_tag_impl, _, _) = including_tag.split_for_impl();
let (_, without_tag_tys, without_tag_where) = without_tag.split_for_impl();
// Generate an expression to sum up the size of each field.
let sum = haskell_size_sum(&input.data);
let expanded = quote! {
impl #including_tag_impl HaskellSize<Tag> for #name #without_tag_tys #without_tag_where {
fn haskell_size(tag: PhantomData<Tag>) -> usize {
#sum
}
}
};
// Hand the output tokens back to the compiler.
proc_macro::TokenStream::from(expanded)
}
/// Add a bound `T: HaskellSize<Tag>` to every type parameter T.
fn add_trait_bounds(mut generics: Generics) -> Generics {
for param in &mut generics.params {
if let GenericParam::Type(ref mut type_param) = *param {
type_param.bounds.push(parse_quote!(HaskellSize<Tag>));
}
}
generics
}
/// Generate an expression to sum up the size of each field.
fn haskell_size_sum(data: &Data) -> TokenStream {
match data {
Data::Struct(ref data) => match &data.fields {
Fields::Named(fields) => haskell_size_fields(fields.named.iter()),
Fields::Unnamed(fields) => haskell_size_fields(fields.unnamed.iter()),
Fields::Unit => quote!(0),
},
Data::Enum(_) | Data::Union(_) => unimplemented!(),
}
}
/// Auxiliary to `haskell_size_sum`
fn haskell_size_fields(fields: Iter<Field>) -> TokenStream {
let recurse = fields.map(|f| {
let t = &f.ty;
quote! { <#t> :: haskell_size(tag) }
});
quote! {
0 #(+ #recurse)*
}
}

13
haskell-ffi/Cargo.toml Normal file
View file

@ -0,0 +1,13 @@
[package]
name = "haskell-ffi"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
bincode = "1.3"
borsh = "0.9"
haskell-ffi-derive = { path = "../haskell-ffi-derive" }
ref-cast = "1.0"
serde = "1.0"

View file

@ -0,0 +1,36 @@
use std::{
io::{Error, ErrorKind, Write},
marker::PhantomData,
};
/// Implement `to_haskell` using `bincode`
///
/// The result will be length-prefixed ("bincode-in-Borsh").
pub fn bincode_to_haskell<Tag, T, W>(
t: &T,
writer: &mut W,
_: PhantomData<Tag>,
) -> Result<(), Error>
where
T: serde::ser::Serialize,
W: Write,
{
match bincode::serialize(t) {
Ok(vec) => borsh::BorshSerialize::serialize(&vec, writer),
Err(e) => Err(Error::new(ErrorKind::InvalidData, e)),
}
}
/// Implement `from_haskell` using `bincode`
///
/// See als `bincode_to_haskell`
pub fn bincode_from_haskell<Tag, T>(buf: &mut &[u8], _: PhantomData<Tag>) -> Result<T, Error>
where
T: serde::de::DeserializeOwned,
{
let vec: Vec<u8> = borsh::BorshDeserialize::deserialize(buf)?;
match bincode::deserialize(vec.as_ref()) {
Ok(x) => Ok(x),
Err(e) => Err(Error::new(ErrorKind::InvalidData, e)),
}
}

View file

@ -0,0 +1,113 @@
use borsh::{BorshDeserialize, BorshSerialize};
use ref_cast::RefCast;
use std::{
cmp::Ordering,
fmt::Debug,
hash::{Hash, Hasher},
io::{Error, Write},
marker::PhantomData,
};
use crate::{from_haskell::FromHaskell, to_haskell::ToHaskell};
/*******************************************************************************
Deriving-via support
*******************************************************************************/
#[derive(RefCast)]
#[repr(transparent)]
/// Newtype for "deriving-via" instances
///
/// The purpose of this newtype is best illustrated through its instances:
///
/// ```ignore
/// impl<Tag, T: ToHaskell<Tag>> BorshSerialize for Haskell<Tag, T>
/// impl<Tag, T: FromHaskell<Tag>> BorshDeserialize for Haskell<Tag, T>
/// ```
///
/// This is primarily used internally: when deriving `ToHaskell`/`FromHaskell`
/// instances for standard types, we want to re-use the logic from `borsh`,
/// rather than re-implement everything here. We do this by turning say a
/// `Vec<T>` into a `Vec<Haskell<Tag, T>>`, and then call functions from
/// `borsh`. The use of the newtype wrapper then ensures that the constraint
/// on `T` will be in terms of `ToHaskell`/`FromHaskell` again.
pub struct Haskell<Tag, T>(pub T, PhantomData<Tag>);
pub fn tag_val<Tag, T>(t: T) -> Haskell<Tag, T> {
Haskell(t, PhantomData)
}
pub fn tag_ref<Tag, T>(t: &T) -> &Haskell<Tag, T> {
RefCast::ref_cast(t)
}
pub fn untag_val<Tag, T>(tagged: Haskell<Tag, T>) -> T {
tagged.0
}
pub fn untag_ref<Tag, T>(tagged: &Haskell<Tag, T>) -> &T {
&tagged.0
}
/*******************************************************************************
Standard instances
*******************************************************************************/
impl<Tag, T: Debug> Debug for Haskell<Tag, T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
}
}
impl<Tag, T: PartialEq> PartialEq for Haskell<Tag, T> {
fn eq(&self, other: &Self) -> bool {
self.0 == other.0
}
}
impl<Tag, T: Eq> Eq for Haskell<Tag, T> {}
impl<Tag, T: PartialOrd> PartialOrd for Haskell<Tag, T> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
self.0.partial_cmp(&other.0)
}
}
impl<Tag, T: Hash> Hash for Haskell<Tag, T> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.0.hash(state);
}
}
impl<Tag, T: Default> Default for Haskell<Tag, T> {
fn default() -> Self {
Self(Default::default(), PhantomData)
}
}
impl<Tag, T: Clone> Clone for Haskell<Tag, T> {
fn clone(&self) -> Self {
Self(self.0.clone(), PhantomData)
}
}
impl<Tag, T: Copy> Copy for Haskell<Tag, T> {}
/*******************************************************************************
Forwarding instances
NOTE: We do not expect _additional_ forwarding instances to be defined.
*******************************************************************************/
impl<Tag, T: ToHaskell<Tag>> BorshSerialize for Haskell<Tag, T> {
fn serialize<W: Write>(&self, writer: &mut W) -> Result<(), Error> {
self.0.to_haskell(writer, PhantomData)
}
}
impl<Tag, T: FromHaskell<Tag>> BorshDeserialize for Haskell<Tag, T> {
fn deserialize(buf: &mut &[u8]) -> std::io::Result<Self> {
let tag: PhantomData<Tag> = PhantomData;
T::from_haskell(buf, tag).map(tag_val)
}
}

View file

@ -0,0 +1,76 @@
use std::{
io::{Error, ErrorKind},
marker::PhantomData,
};
use crate::HaskellSize;
/*******************************************************************************
Main class definition
*******************************************************************************/
const ERROR_NOT_ALL_BYTES_READ: &str = "Not all bytes read";
pub trait FromHaskell<Tag>: Sized {
/// Deserialize data sent from Haskell
///
/// This is the analogue of `BorshDeserialize::deserialize`.
//
/// See `ToHaskell` for a detailed discussion of the `tag` argument.
fn from_haskell(buf: &mut &[u8], tag: PhantomData<Tag>) -> Result<Self, Error>;
fn from_haskell_slice(slice: &[u8], tag: PhantomData<Tag>) -> Result<Self, Error> {
let mut slice_mut = slice;
let result = Self::from_haskell(&mut slice_mut, tag)?;
if !slice_mut.is_empty() {
return Err(Error::new(ErrorKind::InvalidData, ERROR_NOT_ALL_BYTES_READ));
}
Ok(result)
}
}
/*******************************************************************************
Derived functionality
See comments in `to_haskell` for why these functions do not live inside the
trait.
*******************************************************************************/
/// Marshall value with variable-sized encoding
pub fn marshall_from_haskell_var<Tag, T>(inp: *const u8, len: usize, tag: PhantomData<Tag>) -> T
where
T: FromHaskell<Tag>,
{
let mut vec: Vec<u8> = vec![0; len];
unsafe {
std::ptr::copy(inp, vec.as_mut_ptr(), len);
}
match T::from_haskell_slice(vec.as_ref(), tag) {
Ok(t) => t,
Err(e) => panic!("{}", e),
}
}
/// Marshall value with fixed-size encoding
///
/// The `len` argument here is only to verify that the Haskell-side and
/// Rust-side agree on the size of the encoding.
pub fn marshall_from_haskell_fixed<Tag, T>(
inp: *const u8,
inp_len: usize,
tag: PhantomData<Tag>,
) -> T
where
T: FromHaskell<Tag> + HaskellSize<Tag>,
{
let expected_len = T::haskell_size(tag);
if inp_len != expected_len {
panic!(
"expected buffer of size {}, but got {}",
expected_len, inp_len
)
} else {
marshall_from_haskell_var(inp, inp_len, tag)
}
}

View file

@ -0,0 +1,223 @@
use std::marker::PhantomData;
use crate::{derive_size_tuple_instance, fold_types};
pub use haskell_ffi_derive::HaskellSize;
/*******************************************************************************
Main class definition
*******************************************************************************/
pub trait HaskellSize<Tag> {
/// Statically known size (in bytes)
fn haskell_size(tag: PhantomData<Tag>) -> usize;
}
/*******************************************************************************
Simple instances
Note: the following types in the Borsh spec do _not_ have statically known sizes:
- Vec<T>
- HashMap<K, V>
- HashSet<T>
- Option<T>
- String
*******************************************************************************/
impl<Tag> HaskellSize<Tag> for u8 {
fn haskell_size(_tag: PhantomData<Tag>) -> usize {
1
}
}
impl<Tag> HaskellSize<Tag> for u16 {
fn haskell_size(_tag: PhantomData<Tag>) -> usize {
2
}
}
impl<Tag> HaskellSize<Tag> for u32 {
fn haskell_size(_tag: PhantomData<Tag>) -> usize {
4
}
}
impl<Tag> HaskellSize<Tag> for u64 {
fn haskell_size(_tag: PhantomData<Tag>) -> usize {
8
}
}
impl<Tag> HaskellSize<Tag> for u128 {
fn haskell_size(_tag: PhantomData<Tag>) -> usize {
16
}
}
impl<Tag> HaskellSize<Tag> for i8 {
fn haskell_size(_tag: PhantomData<Tag>) -> usize {
1
}
}
impl<Tag> HaskellSize<Tag> for i16 {
fn haskell_size(_tag: PhantomData<Tag>) -> usize {
2
}
}
impl<Tag> HaskellSize<Tag> for i32 {
fn haskell_size(_tag: PhantomData<Tag>) -> usize {
4
}
}
impl<Tag> HaskellSize<Tag> for i64 {
fn haskell_size(_tag: PhantomData<Tag>) -> usize {
8
}
}
impl<Tag> HaskellSize<Tag> for i128 {
fn haskell_size(_tag: PhantomData<Tag>) -> usize {
16
}
}
impl<Tag> HaskellSize<Tag> for f32 {
fn haskell_size(_tag: PhantomData<Tag>) -> usize {
4
}
}
impl<Tag> HaskellSize<Tag> for f64 {
fn haskell_size(_tag: PhantomData<Tag>) -> usize {
8
}
}
impl<Tag> HaskellSize<Tag> for () {
fn haskell_size(_tag: PhantomData<Tag>) -> usize {
0
}
}
impl<Tag, T: HaskellSize<Tag>, const N: usize> HaskellSize<Tag> for [T; N] {
fn haskell_size(tag: PhantomData<Tag>) -> usize {
T::haskell_size(tag) * N
}
}
/*******************************************************************************
Tuples
We support the same sizes of tuples as `borsh` does.
*******************************************************************************/
derive_size_tuple_instance!(T0, T1);
derive_size_tuple_instance!(T0, T1, T2);
derive_size_tuple_instance!(T0, T1, T2, T3);
derive_size_tuple_instance!(T0, T1, T2, T3, T4);
derive_size_tuple_instance!(T0, T1, T2, T3, T4, T5);
derive_size_tuple_instance!(T0, T1, T2, T3, T4, T5, T6);
derive_size_tuple_instance!(T0, T1, T2, T3, T4, T5, T6, T7);
derive_size_tuple_instance!(T0, T1, T2, T3, T4, T5, T6, T7, T8);
derive_size_tuple_instance!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9);
derive_size_tuple_instance!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10);
derive_size_tuple_instance!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11);
derive_size_tuple_instance!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12);
derive_size_tuple_instance!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13);
derive_size_tuple_instance!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14);
derive_size_tuple_instance!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15);
derive_size_tuple_instance!(
T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16
);
derive_size_tuple_instance!(
T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17
);
derive_size_tuple_instance!(
T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18
);
derive_size_tuple_instance!(
T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19
);
/*******************************************************************************
Sanity checks
*******************************************************************************/
#[cfg(test)]
mod tests {
use std::io::Error;
use borsh::BorshSerialize;
use super::*;
enum ExampleTag {}
#[derive(HaskellSize, BorshSerialize)]
struct EmptyStruct;
#[derive(HaskellSize, BorshSerialize)]
struct UnnamedStruct(u16, (u8, u32));
#[derive(HaskellSize, BorshSerialize)]
struct NamedStruct {
a: u8,
b: u16,
c: (u32, u64),
}
#[derive(HaskellSize, BorshSerialize)]
struct ParamStruct<T> {
a: u8,
b: (T, T, T),
}
#[test]
fn empty() -> Result<(), Error> {
let tag: PhantomData<ExampleTag> = PhantomData;
assert_eq!(EmptyStruct::haskell_size(tag), 0);
let encoded = EmptyStruct.try_to_vec()?;
assert_eq!(encoded.len(), EmptyStruct::haskell_size(tag));
Ok(())
}
#[test]
fn unnamed() -> Result<(), Error> {
let tag: PhantomData<ExampleTag> = PhantomData;
assert_eq!(UnnamedStruct::haskell_size(tag), 7);
let encoded = UnnamedStruct(1, (2, 3)).try_to_vec()?;
assert_eq!(encoded.len(), UnnamedStruct::haskell_size(tag));
Ok(())
}
#[test]
fn named() -> Result<(), Error> {
let tag: PhantomData<ExampleTag> = PhantomData;
assert_eq!(NamedStruct::haskell_size(tag), 15);
let encoded = NamedStruct {
a: 1,
b: 2,
c: (3, 4),
}
.try_to_vec()?;
assert_eq!(encoded.len(), NamedStruct::haskell_size(tag));
Ok(())
}
#[test]
fn param() -> Result<(), Error> {
let tag: PhantomData<ExampleTag> = PhantomData;
assert_eq!(<ParamStruct<f64>>::haskell_size(tag), 25);
let encoded = ParamStruct {
a: 1,
b: (1.0, 2.0, 3.0),
}
.try_to_vec()?;
assert_eq!(encoded.len(), <ParamStruct<f64>>::haskell_size(tag));
Ok(())
}
}

View file

@ -0,0 +1,262 @@
//! ToHaskell and FromHaskell instances for the various standard types mandated
//! by the [Borsh spec](https://borsh.io/), piggy-backing on the implementation
//! in the `borsh` crate. The only spec-described types _not_ provided are
//! user-defined structs and enums.
use borsh::{BorshDeserialize, BorshSerialize};
use std::{
collections::{HashMap, HashSet},
hash::Hash,
io::{Error, ErrorKind, Write},
marker::PhantomData,
};
use crate::{
derive_array_instances, derive_simple_instances, derive_tuple_instances,
deriving_via::{tag_ref, untag_val, Haskell},
from_haskell::FromHaskell,
map_tuple, map_tuple_ref,
to_haskell::ToHaskell,
HaskellSize,
};
/*******************************************************************************
Simple (non-composite) instances
*******************************************************************************/
derive_simple_instances!(u8);
derive_simple_instances!(u16);
derive_simple_instances!(u32);
derive_simple_instances!(u64);
derive_simple_instances!(u128);
derive_simple_instances!(i8);
derive_simple_instances!(i16);
derive_simple_instances!(i32);
derive_simple_instances!(i64);
derive_simple_instances!(i128);
derive_simple_instances!(f32);
derive_simple_instances!(f64);
derive_simple_instances!(());
derive_simple_instances!(String);
/*******************************************************************************
Array instances
This is the same set of sizes as supported by borsh.
*******************************************************************************/
derive_array_instances!(0);
derive_array_instances!(1);
derive_array_instances!(2);
derive_array_instances!(3);
derive_array_instances!(4);
derive_array_instances!(5);
derive_array_instances!(6);
derive_array_instances!(7);
derive_array_instances!(8);
derive_array_instances!(9);
derive_array_instances!(10);
derive_array_instances!(11);
derive_array_instances!(12);
derive_array_instances!(13);
derive_array_instances!(14);
derive_array_instances!(15);
derive_array_instances!(16);
derive_array_instances!(17);
derive_array_instances!(18);
derive_array_instances!(19);
derive_array_instances!(20);
derive_array_instances!(21);
derive_array_instances!(22);
derive_array_instances!(23);
derive_array_instances!(24);
derive_array_instances!(25);
derive_array_instances!(26);
derive_array_instances!(27);
derive_array_instances!(28);
derive_array_instances!(29);
derive_array_instances!(30);
derive_array_instances!(31);
derive_array_instances!(32);
derive_array_instances!(64);
derive_array_instances!(65);
derive_array_instances!(128);
derive_array_instances!(256);
derive_array_instances!(512);
derive_array_instances!(1024);
derive_array_instances!(2048);
/*******************************************************************************
Composite instances
This is the same set of tuple sizes as supported by `borsh.`
*******************************************************************************/
derive_tuple_instances!(T0, T1);
derive_tuple_instances!(T0, T1, T2);
derive_tuple_instances!(T0, T1, T2, T3);
derive_tuple_instances!(T0, T1, T2, T3, T4);
derive_tuple_instances!(T0, T1, T2, T3, T4, T5);
derive_tuple_instances!(T0, T1, T2, T3, T4, T5, T6);
derive_tuple_instances!(T0, T1, T2, T3, T4, T5, T6, T7);
derive_tuple_instances!(T0, T1, T2, T3, T4, T5, T6, T7, T8);
derive_tuple_instances!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9);
derive_tuple_instances!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10);
derive_tuple_instances!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11);
derive_tuple_instances!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12);
derive_tuple_instances!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13);
derive_tuple_instances!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14);
derive_tuple_instances!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15);
derive_tuple_instances!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16);
derive_tuple_instances!(
T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17
);
derive_tuple_instances!(
T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18
);
derive_tuple_instances!(
T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19
);
/*******************************************************************************
Vec
*******************************************************************************/
impl<Tag, T: ToHaskell<Tag>> ToHaskell<Tag> for Vec<T> {
fn to_haskell<W: Write>(&self, writer: &mut W, _: PhantomData<Tag>) -> Result<(), Error> {
let tagged: Vec<&Haskell<Tag, T>> = self.iter().map(tag_ref).collect();
tagged.serialize(writer)
}
}
impl<Tag, T: FromHaskell<Tag>> FromHaskell<Tag> for Vec<T> {
fn from_haskell(buf: &mut &[u8], _: PhantomData<Tag>) -> Result<Self, Error> {
let tagged: Vec<Haskell<Tag, T>> = BorshDeserialize::deserialize(buf)?;
Ok(tagged.into_iter().map(untag_val).collect())
}
}
/*******************************************************************************
HashMap
*******************************************************************************/
impl<Tag, K, V> ToHaskell<Tag> for HashMap<K, V>
where
K: Eq + PartialOrd + Hash + ToHaskell<Tag>,
V: ToHaskell<Tag>,
{
fn to_haskell<W: Write>(&self, writer: &mut W, _: PhantomData<Tag>) -> Result<(), Error> {
let tagged: HashMap<&Haskell<Tag, K>, &Haskell<Tag, V>> =
self.iter().map(|(k, v)| (tag_ref(k), tag_ref(v))).collect();
tagged.serialize(writer)
}
}
impl<Tag, K, V> FromHaskell<Tag> for HashMap<K, V>
where
K: Eq + Hash + FromHaskell<Tag>,
V: FromHaskell<Tag>,
{
fn from_haskell(buf: &mut &[u8], _: PhantomData<Tag>) -> Result<Self, Error> {
let tagged: HashMap<Haskell<Tag, K>, Haskell<Tag, V>> = BorshDeserialize::deserialize(buf)?;
Ok(tagged
.into_iter()
.map(|(k, v)| (untag_val(k), untag_val(v)))
.collect())
}
}
/*******************************************************************************
HashSet
*******************************************************************************/
impl<Tag, T> ToHaskell<Tag> for HashSet<T>
where
T: Eq + PartialOrd + Hash + ToHaskell<Tag>,
{
fn to_haskell<W: Write>(&self, writer: &mut W, _: PhantomData<Tag>) -> Result<(), Error> {
let tagged: HashSet<&Haskell<Tag, T>> = self.iter().map(tag_ref).collect();
tagged.serialize(writer)
}
}
impl<Tag, T> FromHaskell<Tag> for HashSet<T>
where
T: Eq + Hash + FromHaskell<Tag>,
{
fn from_haskell(buf: &mut &[u8], _: PhantomData<Tag>) -> Result<Self, Error> {
let tagged: HashSet<Haskell<Tag, T>> = BorshDeserialize::deserialize(buf)?;
Ok(tagged.into_iter().map(untag_val).collect())
}
}
/*******************************************************************************
Option
*******************************************************************************/
impl<Tag, T: ToHaskell<Tag>> ToHaskell<Tag> for Option<T> {
fn to_haskell<W: Write>(&self, writer: &mut W, _: PhantomData<Tag>) -> Result<(), Error> {
let tagged: Option<&Haskell<Tag, T>> = self.as_ref().map(tag_ref);
tagged.serialize(writer)
}
}
impl<Tag, T: FromHaskell<Tag>> FromHaskell<Tag> for Option<T> {
fn from_haskell(buf: &mut &[u8], _: PhantomData<Tag>) -> Result<Self, Error> {
let tagged: Option<Haskell<Tag, T>> = BorshDeserialize::deserialize(buf)?;
Ok(tagged.map(untag_val))
}
}
/*******************************************************************************
Result
`Result` is not explicitly mentioned by the Borsh spec, but it's ubiquitous
and so we provide an instance for it, following the standard rule for enum.
There is no need for an instance of `FromHaskell`, since this is indicating
the result of some Rust-side operation.
*******************************************************************************/
impl<Tag, T: ToHaskell<Tag>, E: ToHaskell<Tag>> ToHaskell<Tag> for Result<T, E> {
fn to_haskell<W: Write>(&self, writer: &mut W, _: PhantomData<Tag>) -> Result<(), Error> {
let tagged: Result<&Haskell<Tag, T>, &Haskell<Tag, E>> = match self {
Ok(t) => Ok(tag_ref(t)),
Err(e) => Err(tag_ref(e)),
};
tagged.serialize(writer)
}
}
/*******************************************************************************
Bool
The Borsh spec does not mention Bool; we encode `true` as 1 and `false` as 0;
this matches what the Haskell `borsh` library does.
*******************************************************************************/
impl<Tag> HaskellSize<Tag> for bool {
fn haskell_size(tag: PhantomData<Tag>) -> usize {
u8::haskell_size(tag)
}
}
impl<Tag> ToHaskell<Tag> for bool {
fn to_haskell<W: Write>(&self, writer: &mut W, tag: PhantomData<Tag>) -> Result<(), Error> {
let as_u8: u8 = if *self { 1 } else { 0 };
as_u8.to_haskell(writer, tag)
}
}
impl<Tag> FromHaskell<Tag> for bool {
fn from_haskell(buf: &mut &[u8], tag: PhantomData<Tag>) -> Result<Self, Error> {
let as_u8 = u8::from_haskell(buf, tag)?;
match as_u8 {
0 => Ok(false),
1 => Ok(true),
_ => Err(Error::new(ErrorKind::InvalidData, "Invalid bool")),
}
}
}

16
haskell-ffi/src/lib.rs Normal file
View file

@ -0,0 +1,16 @@
#![feature(array_methods)]
#![feature(trace_macros)]
mod instances;
mod macros;
pub mod bincode;
pub mod deriving_via;
pub mod from_haskell;
pub mod haskell_size;
pub mod to_haskell;
pub mod use_borsh;
pub use from_haskell::FromHaskell;
pub use haskell_size::HaskellSize;
pub use to_haskell::ToHaskell;

194
haskell-ffi/src/macros.rs Normal file
View file

@ -0,0 +1,194 @@
/*******************************************************************************
Auxiliary general-purpose macros
The `map_tuple` macro is adapted from
https://stackoverflow.com/questions/66396814/generating-tuple-indices-based-on-macro-rules-repetition-expansion .
*******************************************************************************/
/// Map function across all elements of a tuple
///
/// ```ignore
/// map_tuple( [T0, T1], tuple, f )
/// ```
///
/// will become
///
/// ```ignore
/// ( f(tuple.0) , f(tuple.1) )
/// ```
///
/// See also `map_tuple_ref`.
#[macro_export]
macro_rules! map_tuple {
// Base-case: we are done. Return the accumulator
//
// We explicitly allow the list of indices to be non-empty (not all indices might be used)
( @, $tuple:ident, $fn:ident, [], [ $($ixs:tt)* ], [ $($acc:tt)* ] ) => {
( $($acc),* )
};
// Recursive-case: add entry to accumulator
( @, $tuple:ident, $fn:ident, [ $t:ident $(,$ts:ident)* ], [ ($ix:tt) $($ixs:tt)* ], [ $($acc:tt)* ] ) => {
map_tuple!(@, $tuple, $fn, [ $($ts),* ], [ $($ixs)* ], [ $($acc)* ($fn($tuple . $ix)) ])
};
// Entry-point into the macro
( [ $($ts:ident),* ], $tuple:ident, $fn:ident ) => {
map_tuple!(@, $tuple, $fn,
// Pass original list of identifiers (only used to determine tuple length)
[ $($ts),* ]
// Pre-defined list of tuple indices
, [(0) (1) (2) (3) (4) (5) (6) (7) (8) (9) (10) (11) (12) (13) (14) (15) (16) (17) (18) (19)]
// Empty accumulator
, []
)
}
}
/// Variation on `map_tuple` that uses a _reference_ to a tuple
///
/// TODO: It seems I cannot unify these two macros, because `&self.0` and `(&self).0` are not
/// equivalent expressions. Is that true..?
#[macro_export]
macro_rules! map_tuple_ref {
// Base-case: we are done. Return the accumulator
//
// We explicitly allow the list of indices to be non-empty (not all indices might be used)
( @, $tuple:ident, $fn:ident, [], [ $($ixs:tt)* ], [ $($acc:tt)* ] ) => {
( $($acc),* )
};
// Recursive-case: add entry to accumulator
( @, $tuple:ident, $fn:ident, [ $t:ident $(,$ts:ident)* ], [ ($ix:tt) $($ixs:tt)* ], [ $($acc:tt)* ] ) => {
map_tuple_ref!(@, $tuple, $fn, [ $($ts),* ], [ $($ixs)* ], [ $($acc)* ($fn(&$tuple . $ix)) ])
};
// Entry-point into the macro
( [ $($ts:ident),* ], $tuple:ident, $fn:ident ) => {
map_tuple_ref!(@, $tuple, $fn,
// Pass original list of identifiers (only used to determine tuple length)
[ $($ts),* ]
// Pre-defined list of tuple indices
, [(0) (1) (2) (3) (4) (5) (6) (7) (8) (9) (10) (11) (12) (13) (14) (15) (16) (17) (18) (19)]
// Empty accumulator
, []
)
}
}
/// Fold a list of types
///
/// ```ignore
/// fold_types!( [T0, T1], haskell_size, tag, +, 0);
/// ```
///
/// expands to
///
/// ```ignore
/// 0 + <T0>::haskell_size(tag) + <T1>::haskell_size(tag)
/// ```
#[macro_export]
macro_rules! fold_types {
// Base-case: we are done. Return the accumulator
( @, $f:ident, $arg:ident, $op:tt, [], $acc:tt ) => {
$acc
};
// Recursive-case: add entry to accumulator
( @, $f:ident, $arg:ident, $op:tt, [ $t:ty $(,$ts:ty)* ], $acc:tt ) => {
fold_types!(@, $f, $arg, $op, [ $($ts),* ], ( $acc $op (<$t> :: $f($arg)) ))
};
// Entry-point into the macro
( [ $($ts:ty),* ], $f:ident, $arg:ident, $op:tt, $e:tt ) => {
fold_types!(@, $f, $arg, $op, [ $($ts),* ], $e)
};
}
/*******************************************************************************
Macros for deriving specific kinds of instances
*******************************************************************************/
/// Derive `ToHaskell` and `FromHaskell` instances for simple types: types with
/// no type arguments.
#[macro_export]
macro_rules! derive_simple_instances {
($t:ty) => {
impl<Tag> ToHaskell<Tag> for $t {
fn to_haskell<W: Write>(
&self,
writer: &mut W,
_: PhantomData<Tag>,
) -> Result<(), Error> {
self.serialize(writer)
}
}
impl<Tag> FromHaskell<Tag> for $t {
fn from_haskell(buf: &mut &[u8], _tag: PhantomData<Tag>) -> Result<Self, Error> {
<$t>::deserialize(buf)
}
}
};
}
/// Derive `ToHaskell` and `FromHaskell` instances for arrays of the specified size.
#[macro_export]
macro_rules! derive_array_instances {
($sz : literal) => {
impl<Tag, T: ToHaskell<Tag>> ToHaskell<Tag> for [T; $sz] {
fn to_haskell<W: Write>(
&self,
writer: &mut W,
_: PhantomData<Tag>,
) -> Result<(), Error> {
let tagged: [&Haskell<Tag, T>; $sz] = self.each_ref().map(tag_ref);
tagged.serialize(writer)
}
}
impl<Tag, T: FromHaskell<Tag> + Default + Copy> FromHaskell<Tag> for [T; $sz] {
fn from_haskell(buf: &mut &[u8], _: PhantomData<Tag>) -> Result<Self, Error> {
let tagged: [Haskell<Tag, T>; $sz] = BorshDeserialize::deserialize(buf)?;
Ok(tagged.map(untag_val))
}
}
};
}
/// Derive `ToHaskell` and `FromHaskell` for tuples with the specified number of type arguments
/// (i.e., for tuples of the specified size).
#[macro_export]
macro_rules! derive_tuple_instances {
($($ts:ident),*) => {
impl<Tag, $($ts: ToHaskell<Tag> ),* > ToHaskell<Tag> for ( $($ts ),* ) {
fn to_haskell<W: Write>(&self, writer: &mut W,_: PhantomData<Tag>) -> Result<(), Error> {
let tagged: ( $(&Haskell<Tag, $ts> ),* ) = map_tuple_ref!( [ $($ts),* ], self, tag_ref );
tagged.serialize(writer)
}
}
impl<Tag, $($ts: FromHaskell<Tag> ),* > FromHaskell<Tag> for ( $($ts ),* ) {
fn from_haskell(buf: &mut &[u8], _: PhantomData<Tag>) -> Result<Self, Error> {
let tagged: ( $(Haskell<Tag, $ts> ),* ) = BorshDeserialize::deserialize(buf)?;
Ok( map_tuple!( [ $($ts),* ], tagged, untag_val ) )
}
}
};
}
/// Derive `HaskellSize` instance for tuple with the specified type arguments.
#[macro_export]
macro_rules! derive_size_tuple_instance {
($($ts:ident),*) => {
impl<Tag, $($ts: HaskellSize<Tag> ),* > HaskellSize<Tag> for ( $($ts),* ) {
fn haskell_size(tag: PhantomData<Tag>) -> usize {
fold_types!( [ $($ts),* ], haskell_size, tag, +, 0)
}
}
};
}

16
haskell-ffi/src/tagged.rs Normal file
View file

@ -0,0 +1,16 @@
pub mod borsh_instances;
mod macros;
use std::marker::PhantomData;
pub struct Tagged<Tag, T> {
pub value: T,
pub tag: PhantomData<Tag>,
}
pub fn tag<Tag, T>(t: T) -> Tagged<Tag, T> {
Tagged {
value: t,
tag: PhantomData,
}
}

View file

@ -0,0 +1,93 @@
use std::{
io::{Error, Write},
marker::PhantomData,
};
use crate::HaskellSize;
/*******************************************************************************
Main class definition
*******************************************************************************/
// Copied from `borsh`
const DEFAULT_SERIALIZER_CAPACITY: usize = 1024;
pub trait ToHaskell<Tag> {
/// Serialize data to be sent to Haskell
///
/// This is the analogue of `BorshSerialize::serialize`.
///
/// The `tag` argument allows client libraries to define additional
/// instances of `ToHaskell` for foreign (non-local) types. For example, the
/// `solana-sdk-haskell` library can define a `ToHaskell` instance for
/// `Keypair`, defined in `solana-sdk`, as long as it uses a tag `Solana`
/// defined locally in the `solana-haskell-sdk` package.
fn to_haskell<W: Write>(&self, writer: &mut W, tag: PhantomData<Tag>) -> Result<(), Error>;
fn to_haskell_vec(&self, tag: PhantomData<Tag>) -> Result<Vec<u8>, Error> {
let mut result = Vec::with_capacity(DEFAULT_SERIALIZER_CAPACITY);
self.to_haskell(&mut result, tag)?;
Ok(result)
}
}
impl<Tag, T: ToHaskell<Tag>> ToHaskell<Tag> for &T {
fn to_haskell<W: Write>(&self, writer: &mut W, tag: PhantomData<Tag>) -> Result<(), Error> {
(*self).to_haskell(writer, tag)
}
}
/*******************************************************************************
Derived functionality
These functions are not defined in the trait itself, to make it clear that
they only exist at top-level calls, and will not be recursively called
in various `ToHaskell` instances. This is important, because the `len`
parameter that gives the length of the buffer only applies to the _overall_
buffer.
*******************************************************************************/
/// Marshall value with fixed-sized encoding
///
/// The `out_len` parameter is only used to verify that the Haskell-side and
/// the Rust side agree on the length of the encoding.
pub fn marshall_to_haskell_fixed<Tag, T>(t: &T, out: *mut u8, out_len: usize, tag: PhantomData<Tag>)
where
T: HaskellSize<Tag> + ToHaskell<Tag>,
{
let expected_len: usize = T::haskell_size(tag);
if out_len != expected_len {
panic!(
"marshall_to_haskell_fixed: expected buffer of size {}, but got {}",
expected_len, out_len
)
} else {
let mut out_len_copy = out_len;
marshall_to_haskell_var(t, out, &mut out_len_copy, tag);
}
}
/// Marshall value with variable-sized encoding
pub fn marshall_to_haskell_var<Tag, T>(
t: &T,
out: *mut u8,
out_len: &mut usize,
tag: PhantomData<Tag>,
) where
T: ToHaskell<Tag>,
{
match t.to_haskell_vec(tag) {
Ok(vec) => {
let slice: &[u8] = vec.as_ref();
if slice.len() <= *out_len {
unsafe {
std::ptr::copy(slice.as_ptr(), out, slice.len());
}
}
*out_len = slice.len();
}
Err(e) => panic!("{}", e),
}
}

View file

@ -0,0 +1,54 @@
use borsh::{BorshDeserialize, BorshSerialize};
use std::{
io::{Error, Write},
marker::PhantomData,
};
use crate::{FromHaskell, ToHaskell};
/// Newtype wrapper for defaulting to `borsh` for `ToHaskell`/`FromHaskell`
///
/// `ToHaskell`/`FromHaskell` have instances for types such as `Vec<T>`, but
/// those instances depend on `ToHaskell`/`FromHaskell` for `T`. This
/// indirection is not always necessary, and may be expensive. The `UseBorsh`
/// newtype wrapper can be used to mark values where `ToHaskell`/`FromHaskell`
/// should just piggy-back on Borsh.
pub struct UseBorsh<T>(pub T);
pub fn unwrap_use_borsh<T>(use_borsh: UseBorsh<T>) -> T {
let UseBorsh(t) = use_borsh;
t
}
pub fn unwrap_use_borsh_ref<T>(use_borsh: &UseBorsh<T>) -> &T {
let UseBorsh(t) = use_borsh;
t
}
/*******************************************************************************
Forwarding instances
These instances _define_ the `UseBorsh` type
*******************************************************************************/
impl<Tag, T: BorshSerialize> ToHaskell<Tag> for UseBorsh<T> {
fn to_haskell<W: Write>(&self, writer: &mut W, _: PhantomData<Tag>) -> Result<(), Error> {
unwrap_use_borsh_ref(self).serialize(writer)
}
}
impl<Tag, T: BorshDeserialize> FromHaskell<Tag> for UseBorsh<T> {
fn from_haskell(buf: &mut &[u8], _: PhantomData<Tag>) -> Result<Self, Error> {
T::deserialize(buf).map(UseBorsh)
}
}
/*******************************************************************************
Additional standard instances
*******************************************************************************/
impl<T: AsRef<T>> AsRef<T> for UseBorsh<T> {
fn as_ref(&self) -> &T {
unwrap_use_borsh_ref(self).as_ref()
}
}

2
rust-toolchain.toml Normal file
View file

@ -0,0 +1,2 @@
[toolchain]
channel ="nightly"