1 ポイント 投稿者 GN⁺ 2025-08-16 | 1件のコメント | WhatsAppで共有
  • GhosttyチームはGTKアプリケーションを全面的に書き直し、GObject型システムを積極的に活用した
  • この過程では、Zig言語との統合とValgrindによるメモリ問題の確認が重要な役割を果たした
  • GObjectシステムの採用により、従来よりもメモリ管理カスタムウィジェット実装が簡素化された
  • Valgrindを活用した結果、Ghosttyのメモリ安全性が大きく向上したことを実感した
  • 新しいGhostty GTKはソースビルドのデフォルトとなり、1.2リリースに含まれる予定

序論

  • GhosttyはmacOS、Linux、FreeBSDをサポートするクロスプラットフォームのターミナルエミュレータ
  • 各プラットフォームごとにネイティブGUIフレームワークを使用して差別化している
    • macOS: SwiftおよびXcodeベースの大規模アプリケーション
    • LinuxおよびBSD: GTKベースのアプリケーションで、X11/Waylandなどと直接統合
    • 共通コアはZigで書かれており、C ABI互換APIを提供する
  • 既存構造でGTKアプリケーションを書き直した理由は、原文のPRを参照可能
  • 本文では、GObject型システムとの連携Valgrindで検証したメモリ問題に重点的に触れる

GObject型システムとZig

  • GTKを使う場合、基本的にGObject型システムとインターフェースする構造になる
  • 過去にはGObjectシステムを避けつつ、参照カウントのないZigオブジェクトとGObjectオブジェクトのライフサイクルを直接合わせようとしたが、メモリ解放が正しく行われない問題が繰り返し発生した
    • 例: Zig側のメモリは解放されたのにGTK側のメモリはまだ生きている、あるいはその逆の状況が繰り返された
  • このアプローチは正しさの問題だけでなく、**GTK固有機能(イベントシグナル、プロパティバインディング、アクション)**の利用も難しくした
  • 具体例として、設定(config)構造体をリロードする際に接続されたすべてのGUI要素が一貫して更新される必要があったが、この過程は複雑でエラーも多かった
    • 現在はZigのConfig構造体をラップする参照カウント付きGhosttyConfig GObjectとして管理しており、プロパティ変更通知によってアプリケーション全体に自然に変更が伝播する
  • カスタムGObjectウィジェットの作成も容易になり、BlueprintなどのモダンなGTK UI技術が利用可能になった
    • 最近ではBlueprintの導入により、GTKタイトルバータブ、アニメーションするベル枠線などの新機能を導入しやすくなった

ValgrindとGTK、Zig

  • 開発過程全体でValgrindを使い、メモリリークや未定義メモリアクセスなどの問題を体系的に検証した
  • GTKアプリケーションのValgrind検査は難しく、大量のsuppressionファイルが必要だった(80%はGTK自体、残りはサードパーティライブラリおよびGPUドライバ)
  • 繰り返しの検査により、特定のケースでのみ発生する複雑なメモリバグを事前に発見できた
    • 例: GObject WeakRefを正しく初期化しないと、対象オブジェクトが後で解放される際に未定義メモリアクセスが発生するが、Valgrindで事前に検出できた
  • 実際の経験では、**Zigコードベース内部の問題は合計2件(リーク1件、未定義アクセス1件)**にすぎず、それもサードパーティC APIとの連携過程で発生したものだった
    • Zigのデバッグ用アロケータおよびValgrind統合機能の有効性も証明された
  • その他に見つかったメモリ問題の大半は、C API境界とGObjectシステムの複雑なライフサイクル管理に起因していた
    • 結論として、複雑なライブラリのC APIを安全に使うにはValgrindのようなツールが必要
  • Zigのメモリ安全性支援機能は理論的な議論にとどまらず、実証的なプロジェクト経験でも効果が確認された

結論

  • 今回はGhosttyのGUI部分を5回目としてゼロから作り直した経験だった
    • GLFW、macOS SwiftUI、macOS AppKit+SwiftUI、Linux GTK(手続き型)、Linux GTK+GObject型システムの順
  • 繰り返しの再実装の過程で、毎回新しい教訓と技術的成長を得てきた
    • 今回の経験をmacOSプロジェクトにも一部適用する計画
  • Ghostty GTKシステム保守チームの積極的な協力も強調した
  • 新たに書き直されたGhostty GTKアプリケーションはすでにソースビルドのデフォルトとなっており、1.2正式リリースに適用される予定

1件のコメント

 
GN⁺ 2025-08-16
Hacker Newsのコメント
  • GTK を直接触った経験はありませんが、今の説明を聞く限りでは、Zig で Godot バインディングを作るときに直面する問題ととても似ていると感じます。Godot にはクラス、仮想メソッド、プロパティ、シグナルなど、OOP の概念が非常に多くあります。そして、それらすべての概念を扱え、ユーザー定義のオブジェクトや属性を作れる C API を提供しています。エンジンオブジェクトのライフタイム管理を自分で行う必要があり、参照カウントされるオブジェクトのツリー構造もあります。特にライフタイムの問題を Zig の慣用句に合わせて最適な API にまとめようとすると、非常に複雑になります。こうしたことを考える中で、oopz ライブラリ も作りました。API の現状はまだこの程度ですが、実際の例はこちらで見られます。Ghostty フロントエンドを Godot 拡張としても作ってみたいです

    • 以前は言語向けの GTK バインディングを直接使っていて、不便だった記憶があります。98% は問題なかったのですが、残りの 2% で「この関数がオブジェクトの参照を受け取るかどうかが別の引数によって変わる」といった部分があり、そのせいでオブジェクトのライフサイクル解析がかなり厄介でした
    • 感謝を伝えると同時に、C# の Godot コードを高性能に書こうとしたとき、エンジン型との相互変換が多すぎて、割り当てが何度も発生する部分で苦労したことがあります。バインディングを作る中で、こうした問題にも直面したのか気になります
    • Zig で Godot バインディングを作るプロジェクトが進んでいるとは知りませんでした。Godot も Zig も好きなので、とても期待しています。継続的に注目していくつもりです
  • 良いプログラミングとは、結局はシステムが提供するやり方に合わせることだとよく分かる例です。OOP やメモリ管理をどう考えるにせよ、GTK を使うなら GObject 型システムと何らかの形でインターフェースを組むしかありません。避けたくても結局は避けられません。ですが私たちはそれを避けようとし、その結果、参照カウントされるオブジェクトと非参照オブジェクトのライフタイムを結び付けるところでとんでもない混乱が起きました。Ghostty GTK アプリでは、Zig のメモリを解放すると GTK のメモリが解放されない、あるいはその逆といったバグが繰り返し発生しました

    • GTK がこうした構造になった背景には Vala の誕生があります。Vala は C# に着想を得て GObject を活用し、コードを C にトランスパイルします。そのため、かなり多くの GTK アプリが実際には Vala で書かれています。むしろ D 言語を使っていたらもっと良かったのではないかという惜しさもあります。D はいろいろな意味でコンパイルされる C# のように感じられます
    • 悪いシステムに従うのは良いことではなく、実用的な選択です
  • OOP とメモリ管理に関する私の立場はさておき、GTK を使えば GObject 型システムに絡まざるを得ないという点には同意します。だからこそ、私は最初から GTK を直接使わないことにしました。統一された UI テーマの価値は理解していますが、私には GTK の利点が、その対価を払ってまで使うほど大きいとは感じられませんでした。オープンソースアプリで GTK 周辺を触った経験から、GTK と GObject の考え方は自分の性格にあまり合わないという確信を持っています。GTK が存在すること自体は嫌いではありません。自分は使わない側を選べば済むので問題ありませんが、その選択を私の権利だと見なさない人が一部いるのは奇妙です。数ある GUI ツールキットのひとつにすぎず、技術的には非常によく磨かれたツールキットであるにもかかわらず、もし GTK のシェアがもう少し低ければ、その polish が構造的にもっと優れた別のツールキットに注がれていたのではないかとも思います。もちろん、自分に良いものが他の全員にとって良いとは限りません。GTK を使っている人のうち、どれくらいが仕方なく使っていて、どれくらいが最高のツールだと感じているのか気になります

    • GTK と GObject の強く意見を持ったスタイルが自分の考えにもあまり合わない、という点に私も同意します。私は Gnome エコシステムの方向性ともあまり合いません。Ghostty で Linux 向けに GTK を使うのはかなり実用的な選択です。Ghostty の Linux におけるプラットフォームネイティブの目標が何かはここで定義されています。GTK は Linux で最も広く使われており、大半のアプリのエコシステムに最も自然になじむため、この決定は避けられません。今後は libghostty を使って、第三者がさまざまなフロントエンドを出してくれることを期待しています。例として、Wayland ネイティブの Ghostty フロントエンドである Wraith もあります。すばらしいです
    • GTK が Linux で広く使われている最大の理由は、まさに「C バインディング」があるからだと思います。そのため、ほぼすべての言語向けのバインディングが標準提供されるか、自動生成しやすくなっています。一方で Qt は C++ と Python に強く結びついていて、そこが大きな障壁になります。開発者がどの言語を使っていても、その場に合わせてくれることが重要です。しかも複雑なデスクトップアプリを書く場合、古典的な命令型 UI ツールキットのほうがむしろ実用的で、実績あるウィジェットも多く、パターンも馴染みがあります。最近の方式は逆に、小さいものから何でも自分で手を入れる必要があり、少し複雑になるだけでかなり大変になります
    • 「GTK を使わなくてもいいはずなのに、まるで他人の選択権ではないかのように考える人がいる」という部分について、主にどのような反対意見に出会ったのか気になります。私から見ると、アクセシビリティや非ローマ字入力など、GTK は開発者が自前で作ると見落としがちな領域をかなりよく扱っていて、そこが主な競争力に見えます
  • 面白いことに、Ghostty や一部の他の GTK アプリでは、マウスがウィンドウの外に出て戻ってきたあと、最初のスクロールクリックが無視される現象があります。2015 年に最初に報告された非常に古いバグが原因です。バグリンク。現在に至るまで修正予定はなく、メンテナーは Wayland を待てという立場です

    • 実際にはこの問題は GTK 自体ではなく、XInput2 で発生しているようです。もちろん GTK 側で Chromium が使っているヒューリスティックのような回避策は取れますが、根本的には上位層の XInput2 の問題です
    • そのバグレポートとリンク先の issue を読めば、何度も修正の試みはあったものの、どうしてもいくつかのヒューリスティックに頼らざるを得ず、抱えていた問題よりも深刻な副作用が次々に発生していたことが分かります。結局、根本的には X11 の基盤に由来する問題なので、本質的な修正が入らない限り、他の改善も意味のある形では進めにくいように思えます。しかし X11 は現在ほぼメンテナンスモードなので、熱心な支持者が「完全に動いているのだから追加作業は不要だ」と主張する限り、期待はしづらいでしょう。結局残された道は Wayland への移行を待つことだけです
  • 「Valgrind ですべての段階を検証した」という部分で、実はあまりに当たり前に聞こえるものの、自分では一度もやったことがなく、他の開発者がそうしているのもあまり見たことがありません。普通、Valgrind は特定のバグや性能低下が現れたときにだけ使われていました。開発の全過程を通して Valgrind、特に Memcheck や Helgrind のようなツールを能動的に使えば、ツールの安定性は大きく向上し、バグも導入された時点ですぐに潰せるので、あとから何百ものコミットを掘り返す苦労も減らせるのではないかと思います

    • 私自身は C と C++ を使うとき、常に valgrind を定期的に使ってきました。valgrind や asan が見つけるエラーは、たいてい即座にクラッシュするわけではなく、目立たないまま断続的に起きる厄介なバグにつながるため、原因を突き止めるのが非常に難しいです。その中にはセキュリティ脆弱性も含まれます。そして小さなメモリリークが少しずつ積み重なって、後になって本当に大きな問題が起きたとき、すでに無数に蓄積された小さなリークのせいで原因究明がさらに難しくなります。だから能動的に使うのが良いのです
    • Valgrind、特に memcheck は、バグレポートを詳細にデバッグする前に簡単に直せる問題から先に潰すため、私は積極的に使ってきました。ただ最大の問題は性能オーバーヘッドが大きく、インタラクティブに実行する体験があまり良くないことです。それでもテストを Valgrind で一通り回しておくのは非常に価値があると思います
    • ただし Valgrind は非常に遅くコストも高いので、コード修正→コンパイル→テストの反復サイクルにそのまま組み込むのは難しいです。テストサイクル、たとえばナイトリーや自動化では使えますが、うまく統合するには追加作業が必要です
  • Ghostty を使っていて、Mac で nano に複数行を貼り付けられないのがとても不便です。端末が “bracketed pasting” をどう処理するかによるようですが、不思議なことに iTerm2 や Term ではこの問題がありません

    • Ghostty は端末エミュレータとして 99% は満足していますが、コピペの問題だけは本当にストレスで、毎日ぶつかります
    • 新しいコンピュータで Ghostty をデフォルト端末として使い始めてから、最も残念なのは検索機能がないことです。普通は出力中の特定の内容を探すためにショートカットをよく使いますが、それができません。実際、issue でも最も頻繁に挙げられている問題です
    • Ubuntu にリモート接続したとき、Ghostty 内では nano 自体が起動しません
      $ nano
      Error opening terminal: xterm-ghostty.
      
      同じ環境でも macOS ターミナルや VSCode 内蔵ターミナルでは問題なく動きます
    • これは本当にバグの可能性があるので、バグ報告を勧めます
    • Cmd+F のようなコマンド検索がないのが一番致命的です
  • Zig の代わりに Rust を使っていたら、メモリエラーは防げていたのか気になります。大半は Zig/C の相互運用から出た問題なので、Rust でも同じようなものだった気がします。Go 開発者の立場からの推測ですが、実際に C と大規模に連携するとき、もっと多くの安全性ツールを提供する言語があるのかも気になります

    • Rust なら 1 件は防げたでしょうが、残りは同じだったはずです。ご指摘のとおり、問題はすべて C API 境界とそのセマンティクスにあったので、実際の安全性はラッパーの品質次第です。Rust にはすでによく検証されたラッパーのエコシステムが多くあるので、その点では自作した Zig のラッパーより危険性は低かったでしょうが、結局のところ大差はありません。たとえば Rust が捕まえられたと思われる undefined memory access は、実際にはこの PRで解決された部分です。実際には不正なメモリが最初のフレームにコピーされていましたが、どこかで使われたり送られたりはしなかったので、深刻ではありませんでした。それでも正しくなかったことは確かです
    • Rust でも C/GObject との FFI 境界では、手動のメモリ管理とライフタイム管理が必要です。Rust の borrow checker は外部コードのメモリ使用を検証できません
    • 記事の要点のひとつは、zig + valgrind の組み合わせによって、予想よりはるかに少ないメモリ問題しか遭遇しなかったという点です
    • Rust で C バインディングを書くのはずっと難しいです。なので Rust で gtk バインディングを作ること自体、成立しないかもしれません
  • Ghostty などの GPU ベースのアプリ(Alacritty、WezTerm、Zed など)を使って、より速くて良いと感じました。しかし皮肉なことに、こうしたアプリは Nvidia ドライバの限界をより鮮明に見せてくれます。以前は GPU をほとんど使っていなかったので気づきませんでしたが、Regolith i3wm のようなコンポジタなし環境でも、sway/wayland 環境でも、画面共有、スリープ復帰、クラッシュなどで Nvidia ドライバがあまりにひどかったです。複数のバージョン(550/560/575/580)を試しても全部同じでした。昔からこんなに悪かったのだと最近になって気づきました

    • 私も Wayland で似た経験をしました。X11 ではコンポジット効果を切れば、1050Ti でも古い AMD カード(radeon ドライバが必要)でも問題なく動作します。一方 Wayland ではカクつきやクラッシュ、表示崩れなどの問題がありました
  • GTK の型システムがコードに影響しないまま、大きなアプリをひとつ作ることはできました。ただしその代わり、クラス継承や拡張ではなく、ラムダをバインドする形ですべてのコンポーネント同士を接続しました。結果としてそこまで汚くはなりませんでしたが、伝統的な GTK スタイルに慣れた開発者には混乱したかもしれません

  • Ghostty に対する大げさな盛り上がり自体が理解できません。タブとコンテキストメニューしかない UI に、ここまでの統合作業やリライトをする価値があるのか疑問です。iTerm2 のような強力な GUI 環境まで追加しようとしているのではないかと推測しています。Kitty は OpenGL で直接タブを描画するので完全なカスタマイズも可能ですし、複雑なフレームワークへの統合に時間を使わず、とても実用的な機能(直前のコマンド結果をページャでラップして出力するなど)も素早く実装しています。Kitty はリモートもよくサポートしています

    • Ghostty の UI はタブだけではなく、分割、"プロセスが終了しました" バナー、終了確認ダイアログ、タイトル変更ダイアログ、安全でない貼り付けの検出、アニメーションする通知ベル、ドロップダウンターミナル、進捗バーなど、思ったより多彩です。Mac では Apple Shortcuts や Spotlight との統合もあります。もちろん、こうしたものがなくても GUI ツールキットなしで純粋実装することは可能でしたが、Ghostty の使命は各プラットフォームのネイティブツールキットを使って、アプリが「本当にネイティブ」だと感じられるようにすることです。このアプローチが好みでなければ、Kitty のようにテキストタブを使うのも良い選択です。追求する価値や優先順位の違いに応じて選べます。今後は GUI 拡張もいろいろ準備されており、プラットフォーム固有機能との連携(iCloud 同期など)もさらに深めていく予定です
    • 「Kovid のほうが機能実装が速かった」という主張については、このアカウントが Kovid 本人ではないかと疑われる経緯があるので注意が必要です。実際、HN や Reddit で Kitty を中立的に紹介するふりをしながら、開発者を批判しているのを見たことがあります。過去のコメント履歴も参照してほしいとしてリンクが挙げられています
    • 上の前向きな説明に加えて、libghostty の登場がゲームチェンジャーだと思います。WebKit のように、誰でも drop-in するだけですぐに動かせる強力な端末実装です
    • 私も端末探しであちこち渡り歩きましたが、Ghostty は完璧に理想的というわけではないものの、他にちょうど満足できるものが見つからなかったときに移行する先になりました。これも私にとっては十分意味のある選択です。たいていは「決定的な理由」があるからではなく、大きな問題がないので使い続ける、ということ自体がむしろ長所です
    • フォントは Kitty より Ghostty のほうがずっときれいにレンダリングされます。Neovide のほうがさらにきれいですが、まだタブ対応がなく、バッテリー消費も多いです