ハクソク

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

C3プログラミング言語

概要

C3はC言語の進化形であり、Cプログラマに馴染みやすい設計。
C ABI互換性があり、既存のC/C++プロジェクトにシームレス統合が可能。
モジュールシステム演算子オーバーロードなど、現代的な機能を搭載。
コンパイル時マクロ契約プログラミングによる堅牢なコード実現。
エラー処理デバッグ支援も充実し、安全性と利便性を両立。

C3 Programming Languageの特徴

  • C言語の構文と意味論をベースにした進化系プログラミング言語
  • C ABI完全互換性を持ち、C/C++と混在した開発が可能
    • 特別な「C互換型」や関数指定が不要
    • C3の全機能をCから利用可能
  • シンプルなモジュールシステム
    • わかりやすい構成
    • デフォルト設定が直感的
  • 演算子オーバーロードの導入
    • C++の複雑さを排除
    • ベクトルや行列、固定小数点演算に最適
  • コンパイル時マクロとセマンティックマクロ
    • 関数のような記述で明快なマクロ定義
    • Cのプリプロセッサを大きく超える表現力
  • 段階的契約(Gradual Contracts)
    • 実行時・コンパイル時の制約を簡潔に表現
    • プログラミング・バイ・コントラクトの普及
  • ゼロオーバーヘッドなエラー処理
    • Result型エラーと例外処理の良い所取り
    • Cとのシームレスな統合
  • ジェネリックモジュール
    • シンプルかつ明快なジェネリック型の作成
  • ランタイム・コンパイル時リフレクション
    • 型情報への柔軟なアクセス
    • マクロや関数の拡張性向上
  • インラインアセンブリ
    • 通常のコードと同じ感覚でasm記述
    • 文字列や制約表現の煩雑さを排除
  • デバッグ時の安全性チェック
    • 境界チェックや値チェックを自動挿入
    • 契約機能と連携しバグ早期発見を支援
  • 詳細なスタックトレース
    • デバッグビルド時に標準ライブラリが詳細な情報を提供
    • 「セグメンテーションフォルト」だけで終わらないエラー解析

C3の実用例と互換性

  • vkQuakeプロジェクトでの実証
    • コードの一部をC3に書き換え、c3cコンパイラでビルド
    • フォーク版が公開されており、既存Cプロジェクトへの適用事例を提示

C3が目指すもの

  • Cプログラマ向けの進化的言語
    • 革命的ではなく、Cの親しみやすさを維持した進化
    • 新機能と安全性を両立し、現代的な開発体験を提供

Hackerたちの意見

TsodingがC3を使ってたくさんのライブ配信をやってたよ。興味がある人は30時間以上の内容があるからチェックしてみてね。 https://youtube.com/playlist?list=PLpM-Dvs8t0VYwdrsI_O-7wpo-...
どのタイミングで言語を作る価値があるんだろう?個人的にはCでジェネリクスやスライス、エラープロパゲーションを実装したことがあるけど、結構大変だけどやれることだよね。もちろん、Cの標準ライブラリはゴミ箱行きだけど、あんまり価値もないし。コードも少ないし、かなり古くなってるしね。一方で、コンパイラはめっちゃ複雑な話だよ。自分はコンパイラを書くなんて絶対にやりたくないな。分散システムで遊んでた方が楽しかったから。イディオマティックなCは進むべき道じゃなかったから、Cの方言とGoを選んだんだ。こういうことをどうやって見積もるんだろう?それとも楽しもう、yolo?
> 一方で、コンパイラはものすごく複雑な話だよね。大きなプロジェクトを作るのがどれだけ大変かを軽視するつもりはないけど、LLVMにすごく合った「より良いC」言語のクラスがあるのは明らかだと思う。純粋に楽しむ目的で、LLVMを使えば午後のうちに小さなものを立ち上げることができるよ。本当に楽しいし、敷居も低いんだ。
実は、これが始まったのはクリストファー(C3の作者)がC2に貢献して、開発スピードに満足できず、自分のやりたいことを試してもっと早く進めたかったからなんだって。どうやらLLVMと一緒に、新しいコンパイラを書くのが可能だったみたいで、C2の後継となるものを作ることができたらしいよ。
> いつ言語を作る価値があるのか気になるな。 どんな時でも。新しい言語がもたらす改善以上のものは存在しない。パラダイムシフトを定着させる唯一の方法だし、「規律」を「普通の仕事」に変える唯一の方法でもある。例えば、「みんなが物を変えるのが難しいって知ってる」っていうのがある。 * オプション1: 規律 * オプション2: 「let」と「var」(またはそれに相当するもの)を持って、誰かが「このvarは変わるのか?」って考えなきゃいけない場面を何百万回も取り除く。 「手動でメモリを管理するのは難しい」 * オプション1: 規律 * オプション2: 自動メモリ管理のある全てのコードベースのトリリオンのオブジェクトに対して、ほぼ100%心配しなくて済む。 * オプション3: そして、今はもっと安全にスレッド間でこれを確実に行える。 言語で実際に進展を得るのは難しい。改善が必要な競合するものがフラクタルのように存在していて、大部分のユーザーは進歩に反対で、Cのようなものを数十年も苦しむ方が、Pascalのようなものでの徐々に進展する方が好まれるから。言語は構文(重要)と標準ライブラリ(重要)、フレームワークの終わり方(重要)、コンパイル時と実行時の結果(重要)、ツール(重要)を調整する必要がある。これらのどれかを欠けてしまうと、失敗することになる。でも、OSやファイルシステム、DB以外に、将来的にポジティブな影響を及ぼすプロジェクトは他にない。
自分はC3を試したことはないけど、クリストファー・レルノやジンジャービル、いくつかのZigのメンテナーとたくさんやり取りしたことがあるんだ。C3、オーディン、Zigが互いに競争してるんじゃなくて、お互いから学び合って、言語設計の際にどんなトレードオフをしたかを話し合ってるって知れてすごく良かったよ。彼らから、どうやってなぜ異なるビルド方法を実装したのか、特定の機能を実装するかしないかの理由を学ぶのは本当に楽しい経験だった。
> 互いに競争してるんじゃなくて、お互いから学び合ってる それってどう違うの?丁寧な言葉でコミュニケーションしてるってこと?
C3をしばらく追いかけてるけど、デザイン哲学の discipline にすごく感心してる。新しいメモリモデルを強制することもないし、C++になろうともしない。自分にとってのキラーフィーチャーは完全なABI互換性だね。バインディングを書く必要がなくなって、C3のファイルを既存のCビルドシステムに混ぜられるのは、摩擦をほぼゼロにしてくれる。進化を重視するビジョンを守ってくれてるメンテナーに感謝!脳をリセットする必要がなくて、C99よりもモダンに感じる週末の言語を探してるなら、ぜひ試してみてほしい。チームの素晴らしい仕事だよ。
C3でライブラリを書いて、シンボルをエクスポートしてバインディングに使うことってできるのかな?キャリアの残りをフルCで過ごすのを妨げているのは、C文字列や、プロセスが終わった後にクリーンアップされない生メモリへのダングリングポインタなんだよね。
もっとシンプルなものを期待してたけど、新しくて便利な機能がたくさんあってびっくりしたよ。あんまり深いことじゃないかもしれないけど、C3の例外に相当するものが「Excuses」って呼ばれてるのを見て、思わず笑っちゃった。
GitHubプロジェクトにはもっと詳しい情報があるよ: https://github.com/c3lang/c3c C3がCと違う点: - ヘッダーファイルが必須じゃない - 新しいセマンティックマクロシステム - モジュールベースの名前空間 - スライス - 演算子のオーバーロード - コンパイル時のリフレクション - コンパイル時実行の強化 - ジェネリクス(ジェネリックモジュールを通じて) - "Result"ベースのゼロオーバーヘッドエラーハンドリング - Defer - 値メソッド - 関連付けられた列挙データ - プリプロセッサなし - 未定義動作が少なく、「安全」モードでランタイムチェックが追加 - 限定的な演算子オーバーロード(ユーザーランドの動的配列を有効にするため) - オプションの前提条件と後提条件
ドキュメントをざっと見て、2つの重要な質問の答えを見つけたから、他の人の時間を節約できるかもしれないのでここに書いとくね: - LLVMを使ってる(つまり、LLVMと同じくらいポータブル) - 残念ながら、タグ付き列挙はサポートしてない それ以外にも、イントロスペクションやマクロなど、非常に望ましい機能がいくつか追加されてるよ。
結果(または期待値)を「オプショナル」と名付けたの?いやいや、「オプショナル」は「Tまたは空」を意味するんだよ。「TまたはE」じゃない。
概念を勝手に名前変更するのは嫌だって気持ち、よくわかる。でも、もし言語に二つのうち一つしかないなら、「オプショナル」の方がわかりやすいと思う。結果は、通常の操作や関数呼び出しの結果や戻り値の非公式な名前だから、オプショナルは明らかに普通のものじゃないしね。実用的なシステム設計の観点から見ると、Either/Resultパターンだけをサポートするのも理にかなってるかも。`faultdef KeyNotInMap`を追加するのはそんなに手間じゃないし、消費者にとってもなぜ実際の答えが返ってこなかったのかが明確になるから。
オプションと結果の両方と比べて、ここには重要な違いがあると思う。C3のオプショナルは任意のエラータイプを許可していなくて、Cスタイルの整数エラーコードだけなんだ。それがすごく理にかなっていて、「進化、革命ではなく」という哲学にぴったり合ってると思う。そして、構文が「type?」であって「Optional」ではないっていうのも、混乱を和らげる要因になってる。
契約についてのちょっとバカな質問なんだけど、ドキュメントを読んでたら(https://c3-lang.org/language-common/contracts/)こんなことが書いてあったんだ。「契約は、コンパイラが静的解析、ランタイムチェック、最適化に使用するかもしれないオプショナルな前提条件と後提条件のチェックです。C3に準拠したコンパイラは、前提条件や後提条件を全く使用する義務はありません。ただし、前提条件または後提条件のいずれかを違反することは未定義の動作であり、コンパイラは常にそれらが真であるかのようにコードを最適化することがあります—潜在的なバグがそれらを違反させる可能性があってもです。セーフモードでは、前提条件と後提条件はランタイムアサートを使ってチェックされます。」多分何か見落としてるんだろうけど、コードにチェックを追加してるように読めるんだよね。ただ、それが実行される保証もないし、コンパイル時かランタイムかもわからない。時には間違いを捕まえる代わりに、これらのチェックが静かに未定義の動作をプログラムに持ち込むこともあるんじゃない?それってちょっと悪くない?どうやってこれを信頼して使うの?(それ以外はC3は本当にクールだと思う!)
なんか、いつも起こることを標準化する方法みたいに思える。コンパイラは常に最適化の方法を探してるし、それは一般的に仮定をすることを意味するよね。その仮定をコード内で指定するのは、コンパイラのフラグで指定するよりも良いと思う。
これは、他のツールが基にできるように、標準化された方法で意図を表現する能力を与えてくれるんだ。でも、強制の度合いは適用されるコンテキストによって異なることを認識している。大企業のチームは厳格にそれを守りたいかもしれないけど、Visual Studioのような広く使われているツールは、コードの実行を妨げたくないから、パラダイムに慣れようとしている人たちが警告を通じてどう動くかを見られるようにしつつ、コードを実行できるようにしてるんだ。
私が考えるに、契約はあまり達成されないことを期待する柔らかい条件なんだよね。何かが常に真でなければならないなら、関数やマクロ内で「実際の」コードを使ってその条件をチェックして、望む方法で失敗させるべきだと思う。
コントラクトは不変条件を表現する方法で、「これは常に真であるべき」というものだよね。これらの不変条件に対してできることは主に3つあって、具体的なやり方や、どの条件を指定できるか、またそれがプログラム全体、ファイルごと、関数ごとに選べるかは別の話。1. 不変条件を無視する。書いたけど、人間が読めるだけで、機械は気にしない。コメントやドキュメントに注釈をつけるのと同じだし、実際そうする人もいる。2. 不変条件をチェックする。もし不変条件が真でなければ、何かが間違っているから誰かに知らせるかもしれない。3. これらの不変条件が常に真であると仮定する。だから、最適化ツールはそれを使って、より小さいか速い機械コードを生成できるけど、これらの不変条件が正しい場合に限る。例えば、ある言語がプログラム全体がチェックされると言わせたり、プログラム全体が真だと仮定させたり、関数Aのポインタの有効性については実行時にチェックするけど、関数Bの契約では奇数を選ばなきゃいけないっていうのは仮定を使う、ってこともある。奇数の条件については、N=0のときにはそのコードがうまくいかないってことを教えてくれたから、最適化ツールはその少し速い機械コードを生成するのがOKになる。
それらはコンパイラを助けるためにあると思うから、最適化ツールはそれが真であると仮定するかもしれない(でも必ずしもそうではない)。そうすることで非常に便利なこともあるよ。例えば、2つの数が常に異なるとか、ある値が常にxより小さいってことがわかっているとき。標準Cではそれを実現するのは不可能だけど、主要なコンパイラには拡張として表現する方法がある。GCCの例で言うと、if (x) __builtin_unreachable(); C3ではそれを言語構造にしている。安全のために実行時チェックが必要なら、assertを使える。コンパイラはそれを安全/デバッグモードでassertに変換するから、パフォーマンスが重要でないビルドでバグを見つけるのに役立つよ。
- 「準拠したC3コンパイラは、前提条件や後続条件を全く使用する義務はない。」っていうのは、コンパイラが条件を使ってコードのコンパイル方法を選んだり、コンパイル時エラーがあるかどうかを決めたりする必要がないってこと。 - 「ただし、前提条件または後続条件を違反することは未定義の動作であり、コンパイラはそれらが常に真であるかのようにコードを最適化することができる。」っていうのは、基本的に明らかなことを言ってるだけ。コンパイラは真の条件がコードが対処すべきものであると仮定する。条件が偽のときにコードをどうコンパイルするかを推測することはない。 - 「安全モードでは、前提条件と後続条件は実行時のassertを使ってチェックされる。」っていうのは、実行時分析中に条件を有効にする「モード」があることを意味していて、オフにするモードもあるってこと。これにより、条件がソースコードに残っていても、製品リリース用にコンパイルしたときの実行時パフォーマンスに影響を与えない。
コントラクトによる設計は良いと思う。いくつかのプロジェクトで使ったことがある。 https://en.wikipedia.org/wiki/Design_by_contract 最初に出会ったのは、ベルナール・メイヤーの「オブジェクト指向ソフトウェア構築」という本を読んでいるときだった。 https://en.wikipedia.org/wiki/Object-Oriented_Software_Const... 記事の冒頭から: [オブジェクト指向ソフトウェア構築(OOSC)は、ベルナール・メイヤーによる本で、オブジェクト指向プログラミングの基礎的なテキストと広く見なされている。最初の版は1988年に出版され、第二版は1997年に大幅に改訂され、1300ページ以上に及ぶ。多くの翻訳があり、オランダ語(最初の版のみ)、フランス語(1+2)、ドイツ語(1)、イタリア語(1)、日本語(1+2)、ペルシャ語(1)、ポーランド語(2)、ルーマニア語(1)、ロシア語(2)、セルビア語(2)、スペイン語(2)などがある。この本は数千回引用されている。2011年12月15日現在、ACMのコンピュータ文献ガイドは、コンピュータサイエンスのジャーナルや技術書において、第二版だけで2233回の引用を数えている。Google Scholarでは7305回の引用がある。2006年9月の時点で、この本はコンピュータサイエンス文献において、全時代で最も引用された作品(書籍、記事など)のリストで35位にランクインしており、1260回の引用がある。この本は1994年にジョルト賞を受賞した。第二版はオンラインで無料で入手可能。] https://en.wikipedia.org/wiki/Bertrand_Meyer
C3言語を使って簡単なゲームを作るのが楽しかったし、すごく簡単に習得できたよ。最初に引っかかったのは、一時的なメモリアリーナの存在を知らなかったことだけど、最終的にはそれがすごく気に入った! [0] https://github.com/Syn-Nine/c3-mini-games