![[Show HN] Hsrs:Rust 向け类型安全な Haskell バインディング生成ツール](/_next/image?url=%2Fscreenshots%2F2026-05-19%2F1779174363350.webp&w=3840&q=75)
2026/05/19 13:06
[Show HN] Hsrs:Rust 向け类型安全な Haskell バインディング生成ツール
RSS: https://news.ycombinator.com/rss
要約▶
Japanese Translation:
hsrs クレイトは、手動の FFI グループコードの必要性を排除し、Haskell から Rust への安全で型安全な呼び出しを、自動的に生成されるバインディングを通じて可能にします。特定の Rust アノテーション——
#[hsrs::module], #[hsrs::function], #[hsrs::data_type], #[hsrs::value_type], および #[hsrs::enumeration]——を使用することで、開発者はコードジェネレーター(cargo install hsrs-codegen を介してインストール)をトリガーし、慣用的な Haskellラッパーを生成します。システムは不透明型にはForeignPtrを使用してメモリ管理を透明に行い、FFI 境界を越える構文、コレクション、および_enum_については Borsh シリアライゼーションプロトコルを活用します。標準的な Rust タイプ(Result<T, E> は Either へ、Option<T> は Maybe へ、Vec<T> は [T] へ)は自動的により安全な Haskell 同等物に変換され、Haskell 側で追加の依存関係なしにシリアライゼーションを担当するのは Borsh です(これは build-depends: hsrs >= 0.1 && < 0.2 を必要とします)。このアーキテクチャは x86_64 および aarch64 などの 64 ビットプラットフォームでは堅牢ですが、32 ビットシステムでは注意が必要です。32 ビットシステムでは基本データサイズのマップが Word64/Int64 となり、結果として截断(truncation)を招く可能性があります。セットアップには、Cargo.toml にクレイトを追加し、crate-type を ["lib", "staticlib"] に設定する、および Haskell で生成されたバインディングを取り込むことが含まれます。得られるエコシステムは、Rust のパフォーマンスと Haskell の安全性を組み合わせ、MIT OR Apache-2.0 ライセンスの下で、複雑なデータ構造の取り扱いとエラーハンドリングに特化した統合エコシステムを提供します。
概要: hsrs クレイトは、自動的に生成されたバインディングを使用して Haskell から直接 Rust への安全な呼び出しを可能にし、クロスラングージープログラミングを革新します。その最も重要な利点は、メモリ管理やデータシリアライゼーションといった複雑な技術的課題を透明に処理し、開発者を誤り易い手動のグルーコードの記述から解放することです。システムは
#[hsrs::module] などの特定の Rust アノテーションを通じてこの機能を達成し、これによりコードジェネレーターが慣用的な Haskell ラッパーを作成します。重要な点は、Borsh プロトコル——シリアライゼーションの標準規格——を利用して構造体やコレクションが境界を跨ぐ方法を管理しており、明示的なメモリ処理なしでデータの整合性を確保することです。Result などの標準的な Rust タイプは自動的にそのより安全な Haskell 同等物(例:Either)に変換され、全体を通して型安全性を維持します。このアプローチは x86_64 および aarch64 などの 64 ビットアーキテクチャでシームレスに動作します。始めるには、開発者は単にhsrs-codegenツールをインストールして、アノテーションされた Rust ファイルからバインディングを生成すればよいです。最終的に、このクレイトは企業の開発者が効率性の高い Rust と堅牢な Haskell を組み合わせてハイパフォーマンスなアプリケーションを構築することを可能にし、安全性を犠牲にすることなく低レベルの FFI 専門知識を深めることなく、複雑なデータ構造とエラーハンドリングのための統合エコシステムを創造します。
主要ポイント一覧:
- タイトル:
クレイトを使用して、型安全で自動的に生成された FFI バインディングを通じて Haskell から Rust を呼び出します。hsrs - コア機能: Rust のタイプと関数をアノテーションし、コードジェネレーターを実行して慣用的な Haskell を入手し、メモリ管理(
経由)、シリアライゼーション(ForeignPtr
経由)、および型変換を処理します。Borsh - クイックスタート ステップ 1:
,#[hsrs::module]
,#[hsrs::value_type]
,#[hsrs::data_type]
, および#[hsrs::enumeration]
などの属性を使用して Rust コードをアノテーションします。#[hsrs::function] - クイックスタート ステップ 2: ツールをインストール (
) し、cargo install hsrs-codegen
を実行して Haskell バインディングを生成します。hsrs-codegen src/lib.rs -o Bindings.hs - クイックスタート ステップ 3: 生成されたバインディング(
)を取り込んで Haskell で Rust 関数を使用し、メモリは自動的に管理され、タイプは Borsh を介してシリアライゼーションされます。import Bindings - Rust 側のセットアップ:
を依存関係として追加し、hsrs = "0.1"
をcrate-type
に設定します。["lib", "staticlib"] - Haskell 側のセットアップ:
でランタイムパッケージを追加し、これは Borsh シリアライゼーションを自動的に引き寄せ、追加の依存関係なしに処理します。build-depends: hsrs >= 0.1 && < 0.2 - アノテーションテーブルマッピング:
: ポインタ経由で渡される不透明な構造体を#[hsrs::data_type]
新規タイプとして生成し、自動クリーンアップ機能を備えます。ForeignPtr
: C と互換のある_enum_(repr(u8))を#[hsrs::enumeration]
新規タイプとして生成し、パターン同義語を備えます。Word8
: Borsh を介して値として構造体を渡り、#[hsrs::value_type]
および Borsh デリブリングの結果となります。data record
: FFI 上でメソッドをエクスポートし、型安全な Haskell ラッパーを作成します。#[hsrs::function]
: データタイプとそのメソッドをグループ化し、型のすべての FFI グループコードを生成します。#[hsrs::module]
- 型変換の詳細:
はResult<T, E>
へ、Either E T
はOption<T>
へ、Maybe T
はVec<T>
へ、[T]
はString
へ変換され、すべて Borsh を介して透明にシリアライゼーションされます。Text - サポートされているタイプテーブル:
- 基本型(
からi8
,u64
,bool
/usize
)はダイレクト(C FFI)転送を通じて Haskell 同等物に対応します。isize - Enum, 構造体、文字列、およびコレクション(
,Vec
,Option
)については、境界を跨るシリアライゼーションに Borsh を利用します。Result
- 基本型(
- プラットフォーム注記:
およびusize
はそれぞれisize
およびWord64
に対応し、64 ビットプラットフォーム(x86_64, aarch64)に一致します;32 ビットプラットフォームでは値の截断が発生する可能性があります。Int64 - 完全な例: Enum, 値タイプ、
, およびResult
の使用をデモンストレーションする小さな VM (Option
) が提供されており、生成された Haskell バインディングはquecto_vm
およびnew :: IO QuectoVm
を返すEither VmError Int64
などの符号を示しています。safeDiv - ライセンス: MIT OR Apache-2.0。
本文
Haskell から Rust を呼び出すには、型安全で自動的に生成された FFI バインディングをご利用ください。Rust の型や関数にアノテーションを追加し、コードジェネレータを実行することで、メモリ管理、シリアライゼーション、および型のコンバージョンを自動で行うイディオマティックな Haskell コードを手軽に得ることができます。
クイックスタート
-
Rust コードにアノテーションを加える
#[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 } } } -
Haskell バインディングを生成する
cargo install hsrs-codegen hsrs-codegen src/lib.rs -o Bindings.hs -
Haskell 側から利用する
import Bindings main :: IO () main = do c <- new addPoint c (Point 10 20) n <- count c print n -- 1これで完了です。メモリ管理は
を介して自動的に実行され、複雑な型(例:ForeignPtr
)の境界を越えたシリアライゼーションは Borsh を使用して行われます。Point
セットアップ手順
-
Rust 側 — クレートに
を追加します:hsrs[lib] crate-type = ["lib", "staticlib"] [dependencies] hsrs = "0.1" -
Haskell 側 —
ランタイムパッケージを追加します:hsrsbuild-depends: hsrs >= 0.1 && < 0.2これにより、Borsh シリアライゼーションが自動的に依存関係として導入されるため、追加の設定は不要です。
アノテーション可能な項目
| アノテーション | 機能内容 | Haskell 側の生成結果 |
|---|---|---|
| ポインタで渡される不透明な構造体 | 自動クリーンアップ機能を備えた ForeignPtr の newtype |
| C 互換の列挙型(repr(u8) | パターン合成を備えた Word8 の newtype |
| Borsh を介して値として渡される構造体 | Borsh を継承する data record |
| FFI でエクスポートされるメソッド | 型安全な Haskell ラッパー関数 |
| データ型とそのメソッドをグループ化 | 型のすべての FFI ガードコードを生成 |
Rust の
Result<T, E> は Either E T、Option<T> は Maybe T、Vec<T> は [T]、String は Text へと変換されます。これらはいずれも Borsh を通じて透過的にシリアライゼーションされます。
サポートされる型
| Rust | Haskell | データ転送方法 |
|---|---|---|
| i8, i16, i32, i64 | Int8, Int16, Int32, Int64 | 直接 (C FFI) |
| u8, u16, u32, u64 | Word8, Word16, Word32, Word64 | 直接 (C FFI) |
| bool | CBool | 直接 (C FFI) |
| usize / isize | Word64 / Int64 | 直接 (C FFI) |
列挙型 | Word8 の newtype + パターン | 直接 (C FFI) |
構造体 | data record | Borsh |
| String | Text | Borsh |
| Vec | [T] | Borsh |
| Option | Maybe T | Borsh |
| Result<T, E> | Either E T | Borsh |
プラットフォームに関する注意事項
usize と isize はそれぞれ Word64 および Int64 にマッピングされます。これは 64 ビットプラットフォーム(x86_64、aarch64)と整合しています。32 ビットプラットフォームをターゲットとする場合、値が切り捨てられる可能性にご注意ください。
完全な例 列挙型、value type、Result、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> { /* ... */ } } }
生成された 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 = 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)
ライセンス MIT または Apache-2.0