世界を動かす技術を、日本語で。

Show HN: RustだけどLisp

2026年5月10日原文(github.com)

概要

rlisp は、 LISP風S式Rustコード を書くための週末プロジェクト。 Rustの型・所有権システム を維持しつつ、 LISPのマクロ や構文を利用可能。 rlisp は構文変換のみ担当し、 rustc が型・所有権・最適化を担当。 Ariadne によるエラーダイアグノスティクス、 インストール・利用方法 も簡単。 SYNTAX.md で全機能を網羅、 MITライセンス で公開。

rlisp:RustセマンティクスをLISP構文で

  • rlisp はLISPの S式 でRustプログラムを記述し、Rustソースコードへ変換するツール
    • 例:(struct Point (x f64) (y f64)) → struct Point { x: f64, y: f64 }
  • 所有権・借用・ライフタイム・ジェネリクス・トレイト・パターンマッチ など、Rustの主要機能をS式で表現
  • rustc による型チェック・借用チェック・最適化を活用、 rlisp は構文変換に特化
  • Ariadne による分かりやすいエラー出力

インストール・使い方

  • インストール手順
    • git clone https://github.com/ThatXliner/rlisp.git
    • cd rlisp
    • cargo install --path .
  • コマンド例
    • rlisp compile file.lisp # Rustソース生成
    • rlisp build file.lisp  # Rustソース生成&コンパイル
    • rlisp run file.lisp   # 生成・コンパイル・実行

LISP→Rust 構文変換クイックリファレンス

  • 関数定義:(fn add ((x i32) (y i32)) i32 (+ x y)) ⇒ fn add(x: i32, y: i32) -> i32 { x + y }
  • 変数定義:(let x i32 42) ⇒ let x: i32 = 42;
  • 構造体:(struct Point (x f64) (y f64)) ⇒ struct Point { x: f64, y: f64 }
  • 列挙型:(enum Option (generic T) (Some T) None) ⇒ enum Option<T> { Some(T), None }
  • パターンマッチ:(match val ((Some x) (handle x)) (None ())) ⇒ match val { Some(x) => handle(x), None => {} }
  • if文:(if (> x 0) (println! "yes") (println! "no")) ⇒ if x > 0 { println!("yes") } else { println!("no") }
  • 実装ブロック:(impl Point (fn new (...) ...)) ⇒ impl Point { fn new(...) ... }
  • トレイト:(trait Display (fn fmt (...) Result)) ⇒ trait Display { fn fmt(...) -> Result; }
  • フィールド・メソッドアクセス:(. obj field) / (. obj method arg) ⇒ obj.field / obj.method(arg)
  • 構造体初期化:(new Point (x 1.0) (y 2.0)) ⇒ Point { x: 1.0, y: 2.0 }
  • ループ:(loop (body)) / (while cond (body)) / (for x in iter (body)) ⇒ loop { body } / while cond { body } / for x in iter { body }
  • クロージャ:(lambda (x y) (+ x y)) ⇒ |x, y| { x + y }
  • マクロ・プリント:(foo! args) / (println! "{}" x) ⇒ foo!(args) / println!("{}", x)
  • 公開関数:(pub fn foo () i32 42) ⇒ pub fn foo() -> i32 { 42 }
  • インラインRust:(rust "let x: i32 = 42; x") ⇒ let x: i32 = 42; x

詳細仕様と注意点

  • SYNTAX.md にて、 ジェネリクス・ライフタイム・可視性・モジュール・turbofish・if-let・unsafe など全構文を解説
  • 二項演算子 は自動で中置記法に変換:(+ a b) → (a + b)
  • ケバブケース識別子 はRust用に自動変換:page-header → page__header
    • 名前衝突時はコンパイル警告

マクロ機能

  • rlispマクロ は、 S式→S式 のコンパイル時変換関数
    • Rustの proc_macrosyn/quote 不要
  • マクロ定義はLISP同様、 quasiquote/unquote/unquote-splicing を使用
    • (quasiquote template):テンプレート全体をクオート、unquote箇所のみ展開
    • (unquote name):テンプレート内の穴埋め
    • (unquote-splicing name):リストを展開して挿入
  • 可変長引数 は&restで受け取り、unquote-splicingで展開
  • 例:(defmacro when (condition &rest body) (quasiquote (if (unquote condition) (do (unquote-splicing body)))))
    • (when (> x 10) (print "big") (print "huge")) ⇒ if x > 10 { print("big"); print("huge") }

ループ・クロージャ・モジュール

  • ループ例
    • (while (> x 0) (println! "{}" x) (-= x 1))
    • (loop (println! "tick"))
    • (for x in 0..10 (println! "{}" x))
  • クロージャ例
    • 型なし:(let add (lambda (x y) (+ x y)))
    • 型あり:(let mul (lambda ((x i32) (y i32)) i32 (* x y)))
    • move:(let s "hello") (let greet (lambda move () (println! "{}" s)))
  • モジュール・可視性・import
    • (pub fn public_api () i32 42)
    • (pub (crate) fn internal () i32 0)
    • (pub struct Config (pub host String) (port u16))
    • (mod external_lib)
    • (use std::collections::HashMap)

インラインRust・高度な構文

  • rlispで表現困難な場合、(rust "...")で生のRustコード挿入可能
  • ライフタイム・turbofish・制御フローもS式で表現
    • (fn longest (generic 'a) ((x &'a str) (y &'a str)) (&'a str) ...)
    • (let nums ((:: (. (0..10) collect) Vec<i32>)))
    • (if-let (Some v) (. map get key) (println! "found: {}" v) (println! "missing"))
    • (unsafe (rust "let ptr: *const i32 = &42;") (rust "*ptr"))

rlispの目的とメリット

  • 目的 :Rustの型・所有権システムを維持しつつ、LISPの 柔軟なマクロ均一なS式構文 を楽しむ
  • LISPマクロ の強み:proc_macro不要、S式を返すだけで強力な構文拡張が可能
  • 構造的編集 :S式は括弧が常に対応し、構造化編集が容易
  • 統一構文 :関数シグネチャもmatchアームも同じS式で記述、理解負荷が低減

ライセンス

  • MITライセンス で公開、誰でも自由に利用・改変可能

Hackerたちの意見

これって、Rustをs式の構文で書く感じで、ちゃんとしたLispの方言がRustにコンパイルされるわけじゃないみたいだね。クールではあるけど、あんまり面白くはないかな。Lispプログラミングをちょっとでもやったことがある人には、かなり変に見えると思う。

表現のスコープを超えるライフタイムを持つ変数を定義するlet?うん、それは本当に珍しいね。しかも、最初のコード例から見ても、これが一番変なものってわけでもないし。

Rustの意味論をLISP構文で。ランタイムもGCもなしで、Rustに直接コンパイルする透明なs式フロントエンド。最初の段落にそのまま書いてあるよ。

そうだね、いくつかのLispマシンのマイクロコードアセンブリを思い出すよ。s式ではあったけど、明らかにLispそのものではなかった。でも、Lispマクロの面白いターゲットになるかもしれないね。

いくつかのコメントは、まさにRustであることの利点を見逃してる気がする。新しい意味論がないなら、機械語にコンパイルできるLispならCommon Lispがかなり効率的になるよ。Rustを持ち込む目的は、Rust特有の意味論を引き出すことだから、多くの人がそれを結構好きなんだよね!

ってことは、Fennelみたいな感じなのかな?FennelはLuaのランタイムを完全には隠さないって意識してるし。https://fennel-lang.org/

すべての構文がカバーされてるって言ってるけど、ライフタイムやターボフィッシュを指定する例が一つもないのは、ちょっと残念だね。これらはRustの中でも特に難しい構文の一部なんだから。

なんか、バイブコーディングされたパーサーだね…

Rustでオプション指定の型(例えば、変数宣言)を表現する能力があるなら、ライフタイムやターボフィッシュも表現できるはずだよ。ターボフィッシュは特定の型パラメータを持つジェネリック関数を呼ぶためのちょっと変わった呼び方だけど。唯一変なところは、LispがアポストロフィをRustとは全然違う用途で使うことだけど、ライフタイムを示すために他の方法を選ぶこともできるよ。

HRTBはライフタイムを指定するのに一番難しい構文かも。こんな感じに見えるよ:for F: Fn(&'a (u8, u16)) -> &'a u8

もし何か実装し忘れたら、(rust "...")マクロを使って直接Rustに入れるよ。

読者のみんな、僕のリスプ「Loon」を楽しんでくれるかも。これはRustからの影響をたっぷり受けてるよ。 https://loonlang.com/guide/ownership

Hacker Newsで議論の続きを見る