Skip to content
HN On Hacker News ↗

GitHub - harmont-dev/hsrs: Type-safe Haskell Rust Bindings

▲ 53 points 8 comments by suis_siva 5d ago HN discussion ↗

Pangram verdict · v3.3

We believe that this document is a mix of AI-generated, AI-assisted, and human-written content

53 %

AI likelihood · overall

Mixed
41% human-written 38% AI-generated
SEGMENTS · HUMAN 2 of 4
SEGMENTS · AI 1 of 4
WORD COUNT 660
PEAK AI % 100% · §1
Analyzed
May 19
backend: pangram/v3.3
Segments scanned
4 windows
avg 165 words each
Distribution
41 / 38%
human / AI fraction
Verdict
Mixed
Pangram v3.3

Article text · 660 words · 4 segments analyzed

Human AI-generated
§1 AI · 100%

Call Rust from Haskell with type-safe, automatically generated FFI bindings. Annotate your Rust types and functions, run the code generator, and get idiomatic Haskell that handles memory management, serialization, and type conversions for you. Quick start 1. Annotate your Rust code #[hsrs::module] mod canvas { #[hsrs::value_type] pub struct Point { pub x: i32, pub y: i32, }

#[hsrs::data_type] pub struct Canvas { points: Vec<Point>, }

impl Canvas { #[hsrs::function] pub fn new() -> Self { Self { points: vec![] } }

#[hsrs::function] pub fn add_point(&mut self, p: Point) { self.points.push(p); }

#[hsrs::function] pub fn count(&self) -> u64 { self.points.len() as u64 } } } 2. Generate Haskell bindings cargo install hsrs-codegen hsrs-codegen src/lib.rs -o Bindings.hs 3. Use from Haskell import Bindings

main :: IO () main = do c <- new addPoint c (Point 10 20) n <- count c print n -- 1 That's it. Memory is managed automatically via ForeignPtr, and complex types like Point are serialized across the boundary with Borsh. Setup Rust side — add hsrs to your crate: [lib] crate-type = ["lib", "staticlib"]

[dependencies] hsrs = "0.1" Haskell side — add the hsrs runtime package: build-depends: hsrs >= 0.1 && < 0.2 This pulls in Borsh serialization automatically — no extra dependencies needed. What you can annotate

Annotation What it does Haskell result

#[hsrs::data_type] Opaque struct passed by pointer ForeignPtr newtype with automatic cleanup

#[hsrs::enumeration] C-compatible enum (repr(u8)) Word8 newtype with pattern synonyms

#[hsrs::value_type] Struct passed by value via Borsh data record with

§2 Mixed · 66%

Borsh deriving

#[hsrs::function] Method exported over FFI Type-safe Haskell wrapper

#[hsrs::module] Groups a data type with its methods Generates all FFI glue for the type

Result<T, E> becomes Either E T, Option<T> becomes Maybe T, Vec<T> becomes [T], and String becomes Text — all serialized transparently via Borsh. Supported types

Rust Haskell Transfer

i8, i16, i32, i64 Int8, Int16, Int32, Int64 Direct (C FFI)

u8, u16, u32, u64 Word8, Word16, Word32, Word64 Direct (C FFI)

bool CBool Direct (C FFI)

usize / isize Word64 / Int64 Direct (C FFI)

#[hsrs::enumeration] enum Word8 newtype + patterns Direct (C FFI)

#[hsrs::value_type] struct data record Borsh

String Text Borsh

Vec<T> [T] Borsh

Option<T> Maybe T Borsh

Result<T, E> Either E T Borsh

Platform notes usize and isize are mapped to Word64 and Int64 respectively. This matches 64-bit platforms (x86_64, aarch64). If you target 32-bit platforms, be aware that values may be truncated.

§3 Human · 8%

Full example

A small VM with enums, value types, Result, and Option Rust #[hsrs::module] mod quecto_vm { #[derive(Debug, PartialEq, Eq)] #[hsrs::enumeration] pub enum Register { Reg0, Reg1, Count }

#[derive(Debug, PartialEq, Eq)] #[hsrs::value_type] pub struct Point { pub x: i32, pub y: i32 }

#[derive(Debug, PartialEq, Eq)] #[hsrs::value_type] pub struct VmError { pub code: u32 }

#[hsrs::data_type] pub struct QuectoVm { registers: [i64; Register::Count as usize], clock: usize, }

impl QuectoVm { #[hsrs::function] pub fn new() -> Self { /* ... */ }

#[hsrs::function] pub fn store(&mut self, r: Register, v: i64) { /* ... */ }

#[hsrs::function] pub fn snapshot(&self) -> Point { /* ... */ }

#[hsrs::function] pub fn safe_div(&mut self, a: Register, b: Register) -> Result<i64, VmError> { /* ... */ }

#[hsrs::function] pub fn nonzero(&self, r: Register) -> Option<i64> { /* ... */ } } } Generated Haskell newtype Register = Register Word8 deriving (Eq, Show, Storable) deriving (BorshSize, ToBorsh, FromBorsh) via Word8

pattern Reg0 :: Register pattern Reg0 = Register 0

data Point = Point { pointX :: Int32 , pointY :: Int32 } deriving (Generic, Eq, Show) deriving (BorshSize, ToBorsh, FromBorsh) via AsStruct Point

data VmError =

§4 Human · 1%

VmError { vmErrorCode :: Word32 } deriving (Generic, Eq, Show) deriving (BorshSize, ToBorsh, FromBorsh) via AsStruct VmError

data QuectoVmRaw newtype QuectoVm = QuectoVm (ForeignPtr QuectoVmRaw)

new :: IO QuectoVm store :: QuectoVm -> Register -> Int64 -> IO () snapshot :: QuectoVm -> IO Point safeDiv :: QuectoVm -> Register -> Register -> IO (Either VmError Int64) nonzero :: QuectoVm -> Register -> IO (Maybe Int64)

License MIT OR Apache-2.0