DEVELOPMENT ENVIRONMENT

~liljamo/hare-rust-ffi-test

b76de4b1f83c99c225c83e958636fd6dca09eedf — Jonni Liljamo 3 days ago master
feat: init
11 files changed, 163 insertions(+), 0 deletions(-)

A .envrc
A .gitignore
A Cargo.lock
A Cargo.toml
A README.md
A flake.lock
A flake.nix
A hello/Cargo.toml
A hello/src/lib.rs
A justfile
A main.ha
A  => .envrc +1 -0
@@ 1,1 @@
use_flake

A  => .gitignore +3 -0
@@ 1,3 @@
/.direnv/
/build/
/target/

A  => Cargo.lock +7 -0
@@ 1,7 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4

[[package]]
name = "hello"
version = "0.1.0"

A  => Cargo.toml +3 -0
@@ 1,3 @@
[workspace]
resolver = "3"
members = ["hello"]

A  => README.md +14 -0
@@ 1,14 @@
# hare-rust-ffi-test

Just a quick test on Hare <-> Rust FFI.

Uses `cc` to link the final binary, since Rust's static libraries produce `.a`
archives which `hare build` doesn't pick up automagically (unlike plain `.o`s).

Although, the above only matters if you want the Rust standard library. Without
it, `rustc` can build a simple `.o` file that works on its own and
automagically with `hare`s module system (assuming glibc is present, I think).

## TODO:
### Automate the hare stdlib builds
Parse `hare deps` for that.

A  => flake.lock +27 -0
@@ 1,27 @@
{
  "nodes": {
    "nixpkgs": {
      "locked": {
        "lastModified": 1752517496,
        "narHash": "sha256-nHo/Gle6z6lMZc5kGtMOBKkVw1/Z9ilYdV2e14+Q6bU=",
        "owner": "liljamo",
        "repo": "nixpkgs",
        "rev": "98bc1dc9a61dc6350ab81625410bab51f28d36ac",
        "type": "github"
      },
      "original": {
        "owner": "liljamo",
        "ref": "hare-0.25.2",
        "repo": "nixpkgs",
        "type": "github"
      }
    },
    "root": {
      "inputs": {
        "nixpkgs": "nixpkgs"
      }
    }
  },
  "root": "root",
  "version": 7
}

A  => flake.nix +20 -0
@@ 1,20 @@
{
  inputs = {
    #nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
    nixpkgs.url = "github:liljamo/nixpkgs/hare-0.25.2";
  };

  outputs = inputs @ {...}: let
    pkgs = inputs.nixpkgs.legacyPackages."x86_64-linux";
  in {
      devShells.x86_64-linux.default = pkgs.mkShell {
        buildInputs = with pkgs;
          [
            hare
            cargo

            just
          ];
      };
    };
}

A  => hello/Cargo.toml +10 -0
@@ 1,10 @@
[package]
name = "hello"
version = "0.1.0"
edition = "2024"

[lib]
name = "hello"
crate-type = ["staticlib"]

[dependencies]

A  => hello/src/lib.rs +21 -0
@@ 1,21 @@
use std::ffi::{CString, CStr};
use std::os::raw::c_char;

#[unsafe(no_mangle)]
pub extern "C" fn hello(s_ptr: *const c_char) -> *const c_char {
    let s = unsafe { CStr::from_ptr(s_ptr) }.to_str().expect("invalid UTF-8");
    CString::new(format!("Hello {}!", s)).unwrap().into_raw()
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn it_works() {
        let ptr = hello(CStr::from_bytes_with_nul(b"Rust\0").unwrap().as_ptr());
        let out = unsafe { CStr::from_ptr(ptr) };
        let expected = CStr::from_bytes_with_nul(b"Hello Rust!\0").unwrap();
        assert_eq!(out, expected);
    }
}

A  => justfile +47 -0
@@ 1,47 @@
build:
    #!/usr/bin/env sh
    HAREPATH=""
    if [[ -z "${HAREPATH}" ]]; then
      HAREPATH="$(hare version -v | sed -n '/HAREPATH/{n;p;}' | tr -d '[:space:]')"
      echo "HAREPATH not set, using default: $HAREPATH"
    else
      HAREPATH="${HAREPATH}"
    fi

    # setup build dir
    rm -rf build/
    mkdir -p build/bin/
    mkdir -p build/o/

    # build rust staticlib
    cargo build --release

    # build hare objects
    hare build -F -lc -t o -o build/o/main.o main.ha
    hare build -F -lc -t o -o build/o/ascii.o "$HAREPATH/ascii/"
    hare build -F -lc -t o -o build/o/bufio.o "$HAREPATH/bufio/"
    hare build -F -lc -t o -o build/o/bytes.o "$HAREPATH/bytes/"
    hare build -F -lc -t o -o build/o/encoding.o "$HAREPATH/encoding/"
    hare build -F -lc -t o -o build/o/encoding_utf8.o "$HAREPATH/encoding/utf8/"
    hare build -F -lc -t o -o build/o/errors.o "$HAREPATH/errors/"
    hare build -F -lc -t o -o build/o/fmt.o "$HAREPATH/fmt/"
    hare build -F -lc -t o -o build/o/fs.o "$HAREPATH/fs/"
    hare build -F -lc -t o -o build/o/io.o "$HAREPATH/io/"
    hare build -F -lc -t o -o build/o/linux.o "$HAREPATH/linux/"
    hare build -F -lc -t o -o build/o/linux_vdso.o "$HAREPATH/linux/vdso/"
    hare build -F -lc -t o -o build/o/math.o "$HAREPATH/math/"
    hare build -F -lc -t o -o build/o/memio.o "$HAREPATH/memio/"
    hare build -F -lc -t o -o build/o/os.o "$HAREPATH/os/"
    hare build -F -lc -t o -o build/o/path.o "$HAREPATH/path/"
    hare build -F -lc -t o -o build/o/rt.o "$HAREPATH/rt/"
    hare build -F -lc -t o -o build/o/sort.o "$HAREPATH/sort/"
    hare build -F -lc -t o -o build/o/sort_cmp.o "$HAREPATH/sort/cmp/"
    hare build -F -lc -t o -o build/o/strconv.o "$HAREPATH/strconv/"
    hare build -F -lc -t o -o build/o/strings.o "$HAREPATH/strings/"
    hare build -F -lc -t o -o build/o/time.o "$HAREPATH/time/"
    hare build -F -lc -t o -o build/o/types.o "$HAREPATH/types/"
    hare build -F -lc -t o -o build/o/types_c.o "$HAREPATH/types/c/"

    # link hare objects and rust staticlib together, into an executable
    # ignore startfiles (crt1.o and friends)
    cc -nostartfiles -lc build/o/*.o target/release/libhello.a -o build/bin/out

A  => main.ha +10 -0
@@ 1,10 @@
use fmt;
use types::c;

export @symbol("hello") fn hello(s: const *c::char) const *c::char;

export fn main() void = {
	fmt::println("Hello Hare!")!;
	let rust_hello_ptr = hello(c::nulstr("Rust\0"));
	fmt::println(c::tostr(rust_hello_ptr)!)!;
};