ハクソク

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

Zen-C: 高水準言語のように書き、Cのように実行する

概要

  • Zen Cは現代的なシステムプログラミング言語で、C11互換のCコードに変換
  • 型推論パターンマッチジェネリクスRAIIなど高機能を実装
  • C ABI 100%互換で、手動メモリ管理やasync/awaitもサポート
  • GCC/Clang/Zigを利用した高い互換性とクロスコンパイル対応
  • 簡単な導入・拡張性・豊富なテスト・貢献歓迎

Zen C モダンエルゴノミクス:ゼロオーバーヘッド・ピュアC

  • Zen Cは、人間が読めるC11コードへコンパイルされるモダンなシステム言語
  • 型推論パターンマッチジェネリクストレイトasync/awaitRAIIによる手動メモリ管理などを搭載
  • C ABI完全互換を維持しつつ、高級言語のような記述性とC並みの実行速度を両立

クイックスタート

  • インストール手順
    • git clone https://github.com/z-libs/Zen-C.git
    • cd Zen-C
    • make
    • sudo make install
  • 基本コマンド
    • zc run hello.zc:コンパイル&実行
    • zc build hello.zc -o hello:実行ファイル生成
    • zc repl:インタラクティブシェル
  • 環境変数
    • ZC_ROOTで標準ライブラリのパス指定可能
    • 任意ディレクトリから標準インポートが利用可能

Zen C 言語リファレンス

1. 変数・定数

  • 型推論が標準、明示型も指定可能
  • var x = 42;:int型と推論
  • const PI = 3.14159;:コンパイル時定数
  • var explicit: float = 1.0;:型明示
  • ミュータビリティ
    • デフォルトは可変
    • //> immutable-by-defaultで不変が標準
    • var mut y = 10;で明示的可変

2. プリミティブ型

  • C相当型を網羅
    • int, uint:プラットフォーム標準整数
    • I8..I128, U8..U128:固定幅整数
    • isize, usize:ポインタサイズ整数
    • byte:U8エイリアス
    • F32, F64:浮動小数点
    • bool, char, string, void:真偽値・文字・C文字列・空型

3. 集合型

  • 配列:値セマンティクス、固定長
    • var ints: int[5] = {1, 2, 3, 4, 5};
  • タプル:複数値のグループ化
    • (1, "Hello")のように定義
  • 構造体:ビットフィールド対応
    • struct Point { x: int; y: int; }
  • 列挙型:タグ付きユニオン(Sum型)
    • enum Shape { Circle(float), Rect(float, float), Point }
  • ユニオン:C標準のunsafeアクセス

4. 関数・ラムダ

  • 関数定義
    • fn add(a: int, b: int) -> int { return a + b; }
    • 名前付き引数対応
  • ラムダ式(クロージャ)
    • var double = x -> x * factor;
    • ブロック構文も利用可能

5. 制御構文

  • 条件分岐
    • if x > 10 { ... } else if ... else { ... }
    • 三項演算子var y = if x > 10 ? 1 : 0;
  • パターンマッチ
    • match val { 1 => ... 2 | 3 => ... _ => ... }
    • 列挙型の分解も可能
  • ループ
    • 範囲for:for i in 0..10 { ... }
    • コレクションfor、while、無限ループ、repeat
  • 高度な制御
    • guard/unlessによる早期return

6. 演算子

  • 算術・比較・インデックスなどC相当
  • **Null合体(??)・Null代入(??=)・安全ナビゲーション(?.)・try演算子(?)**をサポート

7. メモリ管理

  • defer:スコープ終了時に処理実行
  • autofree:変数スコープ終了時に自動解放
  • RAII/Dropトレイト:自動クリーンアップロジック実装

8. オブジェクト指向

  • メソッド定義:implブロックで静的/インスタンスメソッド
  • トレイト:共通動作定義と実装
  • コンポジションuseで他構造体のフィールドをmixin

9. ジェネリクス

  • 型安全なテンプレート
    • 構造体・関数の型パラメータ化

10. 並行処理(Async/Await)

  • pthreadベースの非同期関数
    • async fn fetch_data() -> string { ... }
    • awaitで結果取得

11. メタプログラミング

  • comptime:コンパイル時処理
  • embed:ファイル埋め込み
  • プラグイン:構文拡張
  • Cマクロ:プリプロセッサマクロ透過

12. アトリビュート

  • 関数・構造体の装飾
    • @must_use@deprecatedなどC11拡張相当の多彩なアノテーション

13. インラインアセンブリ

  • GCCスタイルasmを直接記述可能
  • volatile名前付き制約で安全なアセンブリ挿入
  • Intel構文も一部対応(TCC非対応)

14. ビルドディレクティブ

  • ソース冒頭コメントでビルド設定
    • ライブラリリンク・インクルードパス・CFLAGS・pkg-config・シェルコマンド・immutableモード等
    • //> include: ./include
    • //> link: -lraylib -lm
    • //> cflags: -Ofast
    • //> pkg-config: gtk+-3.0

コンパイラ互換性・推奨環境

  • GCC/Clang/Zig:全機能対応・100%合格
  • TCC:高速だが一部機能未対応(__auto_type、Intel ASM、ネスト関数不可)
  • Zig cc:クロスコンパイルに最適
  • --ccフラグでバックエンド指定

テスト・貢献ガイド

  • テストスイート
    • make testで全テスト実行
    • ./zc run tests/test_match.zcで個別テスト
    • ./tests/run_tests.sh --cc clang等で各コンパイラ確認
  • コントリビュート方法
    • GitHubでFork&ブランチ作成
    • Cスタイル遵守、テスト必須
    • Pull Requestで変更点明記
  • コンパイラ拡張
    • パーサ:src/parser/
    • コード生成:src/codegen/
    • 標準ライブラリ:std/

Zen Cは、Cの資産を最大限活かしつつ、現代的な開発体験を提供する新世代言語。高機能・高互換・高拡張性を備え、Cエコシステムの未来を切り拓く選択肢。

Hackerたちの意見

初めのコミットから24時間で、363スター、20フォークもあるんだ。早いな、これ。
ボットの可能性もあるね。
初めのコミットの前に、彼が自分のライブラリについてたくさん投稿してたんだ。LinkedInでその人をフォローしてるよ。
文法は置いといて、Nimと比べてどうなんだろう?Nimも似たようなことができると思うし、Crystalもそうだよね?正直、Crystalについてはあんまり自信ないけど。NimとValaは、どちらもCにトランスパイルするから、実際には「Cっぽい」出力が得られると思う。
いや、Valaのことはしばらく聞いてないな。今もアクティブに開発されてるの?どうなってるの?
これ、Valaを思い出させるな。10年以上見たり聞いたりしてないけど。
CrystalはLLVMを使ってオブジェクトコードに直接コンパイルされるんだ。Cコードとの相互運用もできるよ;例えば、ncurseswの関数をCrystalから呼び出すのにこの機能を使ってる。
私が見る限り、Zen-Cは「スーパーパワーを持つC」を目指してるみたい。配列や文字列にはCのポインタを使ってるし、シンボルのマングリングなしで人間が読めるCファイルにトランスパイルされる。安全性はなし。ポータブルじゃない(まだ?)。NimはCをバックエンドの一つとして使う、完全で独立した現代の言語だよ。独自のランタイム、オプションのGC、Unicode文字列、境界チェック、大規模な標準ライブラリを持ってる。高レベルのNimコードを書けば、通常触れない最適化されたCが出力される。ここにREADMEやコードから集めたちょっとした比較をまとめたよ: 比較 ZenC Nim Cで書かれた 自己ホスト対象 C C, C++, ObjC, JS, LLVM(nlvm経由)、ネイティブ(進行中) プラットフォーム POSIX Linux, Windows, MacOS, POSIX, ベアメタル メモリ戦略 マニュアル/RAII ARC, ORC(サイクルコレクタ付きARC)、複数GC、マニュアル生成コード 人間が読める 最適化 マングリング あり なし 標準ライブラリ ほとんどなし 広範囲/バッテリー込み コンパイル時コード あり あり マクロ コンパイル時? AST操作 配列 Cの配列 型とサイズは常に保持 文字列 Cの文字列は容量と長さを持ち、Unicodeをサポート 境界チェック なし あり(オプション)
面白いのは、これが(どうやら)読みやすいCにコンパイルされるってことなんだけど、それをどう活用するのかはよくわからない。Cにはあまり詳しくないから、要はこの言語でコードの一部を段階的に書ける方が楽ってことなのかな?残りの部分は普通のCで。
そう思う。新しい言語の一番の壁は、サードパーティのライブラリエコシステムから切り離されることだよね。Cのサードパーティライブラリと互換性があるのは大きな利点だ。
一つの利点は、検証などのためのツールがCを基にして作られていること。もう一つは、Cのランタイム要件だけがあるから、変なランタイムの実装をしなくて済むってこと。例えば、ベアメタルで動かしたいなら、Cコードを出力してターゲットにコンパイルできる。
> 変異性 > デフォルトでは、変数はミュータブルです。ディレクティブを使ってImmutable by Defaultモードを有効にできます。 > //> immutable-by-default > var x = 10; > // x = 20; // エラー: xは不変です > var mut y = 10; > y = 20; // OK ちょっと待って、これって誰かのコードを読んでるとき、変数がミュータブルかどうかわからないってことだよね。全ファイルを読んでそのディレクティブを探さないといけないのか。もし誰かがカスタムディレクティブを定義してたら、読みやすさがなくなるよね。
設定可能なオプションがあるのに、どうしてデフォルト設定がエラーの確率を上げるものなんだろう?特定のニッチでは「便利さがそれだけの価値があるから」って答えになるけど(例えばゲームジャムとかね)。でも、個人的にはエラーが出やすいオプションは、そういう場合にはオプトインにすべきだと思う。率直に言うと、正確さはオプトインじゃなくてオプトアウトであるべきだよね。将来作る予定の言語には、#explode-randomly-at-runtimeって名前のフラグを考えてるよ ;)
そうだね、不変性には`let`キーワードを使うべきだし、コンパイラの解析がその宣言に値のセマンティクスを強制するべきだと思う。
他の言語にもコンパイラの挙動に影響を与える非ローカルな方法があるよね。例えば、Rustの属性(標準)やCのコンパイラプラグマ(非標準)とか。動いているコードを読むとき、言語モードが変数の再代入を許可しているかどうかは関係ない。変更したいときだけ重要だし、間違ったことをするとコンパイラが怒るしね。指示を探すより、試してみる方がずっと早いと思う。私には大したことじゃないように思える。
理想的ではないけど、LSPがホバーイベントで教えてくれることかもしれないね。LSPは見なかったけど(あんまり探してもいないけど)、現代的な言語の使いやすさを提供するのが彼らの使命だと思う。(でも、これがキーワードであるべきだという他のコメントには同意する。もう一つの良い代替案は、グローバルスコープだけにすることかな。)
そう、悲しいかな、10億ドルのミスだよ…著者がこの道を選んだ理由がわからない。すごく混乱するし、すぐに問題が起きた: https://github.com/z-libs/Zen-C/issues/19 もちろん、混乱もね: https://github.com/z-libs/Zen-C/pull/13#issuecomment-3739722... もう人生は大変なのに、開発者にはそうでもないみたいだね。
基本的にはC2/C3だけど、Rustの影響を受けてる感じ。C4って名前にするチャンスを逃したな。
async/awaitの構文が内部でスレッドを「専ら」使ってるのは変だね。実装がシンプルになるのは分かるけど、見たことある言語ではasync/awaitの目的はイベントループや協調マルチタスクを使うことだと思ってた。
初心者の質問だけど、スレッドにコンパイルされるだけなら、特別な構文って必要なの?スレッドでブロックするのに言語のサポートは要らないって理解してたんだけど。
async/awaitのポイントは、自分自身を一時停止できる関数(または監視システムによって一時停止される関数)と、完全に処理を終えて一時停止できない関数との間に構文の区切りを作ることだと思う。計算の一時停止を可能にして、他の計算をその後に進める手段は実装の詳細だしね。だから、非同期関数が同期関数とは別のスレッドで実行されるのは、リソースが利用可能になるのを待つ計算がある中で連続処理を実現するための有効な方法だと思う。C#の発祥とJavaScriptによる構文の普及に触発されているから、async/awaitがイベントループで実装されているのは不思議じゃないね(両方の言語がその実装に使ってるから)。
コード生成を見る限り、deferは「正しく」実装されてないみたいだね:deferされたステートメントはブロックが通常に終了したときだけ実行される。ブロックを「return」や「break」、「continue」(ラベル付きのバリエーションも含む!外側のdeferと微妙に相互作用する)や「goto」で抜けると、完全にスキップされちゃう。これは、正しくないと思うな。例えば、`var f = fopen("file.txt", "r"); defer fclose(f);`で、`fread(&ch, 1, 1, f)`が空のファイルだったらファイルを閉じないことになる。実際、普通の「return 0」でもどうなるか分からない。deferされたステートメントは「return」の後にテキスト的に出力されるから、voidを返す関数や内部ブロックでしか正しく機能しないみたい。
この例をコンパイルできた?
それは昔書こうとしたけど、複雑すぎて失敗したものだ。Cをもう少し扱いやすくするためのメタプリプロセッサ…すごいね!
このプロジェクト、めっちゃ好きだわ。自分のカスタムCライブラリの次のステップって感じ。最初は「チュートリアルC」って書くんだよね。で、セグメンテーションフォルトやダブルフリーがいっぱい起きて、毎回カスタムアロケーターでプロジェクト始めることになる。もうそれを避けたくて仕方ないから…。そんで、学びながらもっと汎用的なライブラリを実装して、アロケーターに依存するプライミティブを追加していく。リファレンスカウンターとか、コンストラクタ、デストラクタとかも入れてね。まあ、これは少なくとも俺の学びの道かな?まだまだ長い道のりだけど、いつも通りね!こんな言語に行くとは思わないけど、経験と気を使えばコードがどこまで進化するかを見るのはインスピレーションになると思う。