-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathghostty-why-not-rust.html
More file actions
931 lines (899 loc) · 110 KB
/
Copy pathghostty-why-not-rust.html
File metadata and controls
931 lines (899 loc) · 110 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
<!doctype html>
<html lang="en" data-theme="dark" data-lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>why-not-rust · ghostty-org/ghostty</title>
<style>
:root[data-theme="dark"] {
color-scheme: dark;
--page:#0a0a0b; --raised:#111113; --line:#232327; --line-soft:#1a1a1d;
--ink:#f4f4f5; --ink-2:#a8a8b0; --ink-3:#6e6e78;
--blue:#5b9cf0; --teal:#3fbfa0; --rust:#f0803c; --amber:#e0a93c;
--wash:rgba(255,255,255,.028);
}
:root[data-theme="light"] {
color-scheme: light;
--page:#fcfcfb; --raised:#ffffff; --line:#e4e2dd; --line-soft:#efedE8;
--ink:#16161a; --ink-2:#55555f; --ink-3:#8b8b95;
--blue:#1f6fd0; --teal:#0f8f74; --rust:#c85a15; --amber:#9a6b00;
--wash:rgba(0,0,0,.022);
}
:root {
--mono:ui-monospace,SFMono-Regular,"SF Mono",Menlo,Consolas,monospace;
--sans:-apple-system,BlinkMacSystemFont,"Segoe UI",Inter,Roboto,Helvetica,Arial,sans-serif;
--accent:var(--blue);
--measure:74ch;
}
body.v-stay { --accent:var(--blue); }
body.v-extract { --accent:var(--teal); }
body.v-partial { --accent:var(--amber); }
body.v-migrate { --accent:var(--rust); }
* { box-sizing:border-box; margin:0; padding:0; }
html { background:var(--page); }
body {
background:var(--page); color:var(--ink); font-family:var(--sans);
font-size:16px; line-height:1.62; -webkit-font-smoothing:antialiased;
padding:0 32px 96px;
/* Evidence is full of long file paths, commit SHAs and URLs. Without this
a single unbreakable token pushes the page wider than a phone screen. */
overflow-wrap:break-word;
}
.wrap { max-width:1080px; margin:0 auto; }
.mono { font-family:var(--mono); font-variant-ligatures:none; }
a { color:inherit; }
/* ---------- masthead ---------- */
.mast { display:flex; align-items:center; gap:14px; padding:26px 0 22px; }
.logo { font-size:.9rem; font-weight:600; letter-spacing:-.02em; }
.logo .q { color:var(--accent); }
.mast .meta { margin-left:auto; display:flex; gap:14px; align-items:center; color:var(--ink-3); font-size:.78rem; }
.btn {
font:inherit; font-size:.72rem; color:var(--ink-2); background:none; cursor:pointer;
border:1px solid var(--line); border-radius:6px; padding:3px 9px; line-height:1.5;
}
.btn:hover { color:var(--ink); border-color:var(--ink-3); }
/* ---------- hero ---------- */
.hero { padding:22px 0 40px; border-top:1px solid var(--line); }
.kicker {
font-size:.7rem; letter-spacing:.13em; text-transform:uppercase;
color:var(--ink-3); font-weight:600;
}
.kicker .project { color:var(--ink-2); text-transform:none; letter-spacing:.02em; }
.hero-grid { display:grid; grid-template-columns:minmax(0,1fr) 330px; gap:56px; margin-top:26px; align-items:start; }
.scope-word {
font-size:clamp(3.2rem,8vw,4.6rem); line-height:.94; font-weight:760;
letter-spacing:-.045em; color:var(--accent);
}
.scope-sub { margin-top:12px; font-size:1.02rem; color:var(--ink); font-weight:500; }
.auth {
display:inline-flex; align-items:center; gap:8px; margin-top:20px;
font-size:.74rem; font-weight:650; letter-spacing:.05em; color:var(--ink-2);
}
.auth i { width:7px; height:7px; border-radius:50%; background:var(--ink-3); flex:none; }
body.a-approve .auth i { background:var(--teal); }
body.a-reject .auth i { background:var(--blue); }
body.a-defer .auth i { background:var(--amber); }
.why { margin-top:18px; font-size:1.02rem; line-height:1.66; color:var(--ink); max-width:var(--measure); }
.trigger {
margin-top:16px; padding-left:15px; border-left:2px solid var(--line);
font-size:.87rem; line-height:1.6; color:var(--ink-2); max-width:var(--measure);
}
.chips { display:flex; flex-wrap:wrap; gap:9px 20px; margin-top:26px; font-size:.75rem; color:var(--ink-3); }
.chips b { color:var(--ink-2); font-weight:600; }
.chips .scope b { color:var(--accent); }
/* ---------- gates ---------- */
.gatebox h2 {
font-size:.68rem; letter-spacing:.13em; text-transform:uppercase;
color:var(--ink-3); font-weight:650; padding-bottom:11px; border-bottom:1px solid var(--line);
}
.gate { display:grid; grid-template-columns:26px 1fr; gap:11px; padding:13px 0; border-bottom:1px solid var(--line-soft); align-items:start; }
.gate:last-child { border-bottom:0; }
.gate .id { color:var(--ink-3); font-size:.7rem; padding-top:3px; }
.gate .head { display:flex; align-items:center; gap:8px; }
.gate .name { font-size:.88rem; font-weight:620; }
.gate .ev { color:var(--ink-3); font-size:.78rem; line-height:1.5; margin-top:3px; }
/* state words: a dot plus a word. No filled pills anywhere. */
.state { display:inline-flex; align-items:center; gap:5px; font-size:.66rem; font-weight:700; letter-spacing:.07em; white-space:nowrap; }
.state::before { content:""; width:6px; height:6px; border-radius:50%; background:currentColor; flex:none; }
.state.pass { color:var(--teal); }
.state.fail { color:var(--blue); }
.state.unknown { color:var(--amber); }
/* ---------- magnitude figures ---------- */
.tiles {
/* Six magnitude tiles is the house style, and 150px + a 24px gutter is what
lets all six sit on one row inside the 1080px measure. At 168/34 the sixth
tile orphaned onto a row of its own on every report. */
display:grid; grid-template-columns:repeat(auto-fit,minmax(150px,1fr));
gap:30px 24px; padding:32px 0; border-top:1px solid var(--line); border-bottom:1px solid var(--line);
}
.tile .label { font-size:.72rem; color:var(--ink-3); line-height:1.4; }
/* Sized so a nine-character value — which is 117 of the 120 tiles in the
gallery — stays on one line in a six-across row. Longer composite values
("16,350–808,625") wrap at a space, which no usable size prevents. */
.tile .value { margin-top:7px; font-size:1.55rem; font-weight:640; letter-spacing:-.035em; line-height:1.12; }
/* The unit is a single token ("lines", "2016–2021"); never break inside it. */
.tile .unit { font-size:.8rem; font-weight:500; color:var(--ink-3); margin-left:5px; letter-spacing:0; white-space:nowrap; }
.tile .note { margin-top:6px; font-size:.71rem; color:var(--ink-3); line-height:1.45; }
/* ---------- sections ---------- */
section { padding-top:58px; }
.section-title { display:flex; align-items:baseline; gap:12px; font-size:1.18rem; font-weight:640; letter-spacing:-.02em; }
.section-title .no { font-family:var(--mono); font-size:.72rem; color:var(--ink-3); font-weight:500; }
.sub { margin-top:9px; font-size:.85rem; color:var(--ink-3); max-width:var(--measure); line-height:1.6; }
/* ---------- options table ---------- */
.table-wrap { overflow-x:auto; margin-top:22px; }
table { width:100%; border-collapse:collapse; font-size:.79rem; min-width:820px; }
th {
text-align:left; font-size:.66rem; letter-spacing:.1em; text-transform:uppercase;
color:var(--ink-3); font-weight:650; padding:0 14px 10px 0; border-bottom:1px solid var(--line);
}
td { padding:15px 14px 15px 0; border-bottom:1px solid var(--line-soft); vertical-align:top; color:var(--ink-2); line-height:1.5; }
tr:last-child td { border-bottom:0; }
tr.selected td { background:var(--wash); }
tr.selected td:first-child { box-shadow:inset 2px 0 0 var(--accent); padding-left:13px; }
tr.excluded td { opacity:.6; }
.option-name { color:var(--ink); font-weight:620; display:block; }
td .source { color:var(--ink-3); font-size:.72rem; margin-top:3px; display:block; line-height:1.45; }
.scope-tag { font-family:var(--mono); font-size:.66rem; letter-spacing:.05em; color:var(--ink-3); }
.ev-tag { font-size:.66rem; font-weight:700; letter-spacing:.06em; }
.ev-tag.strong { color:var(--teal); } .ev-tag.moderate { color:var(--ink-2); }
.ev-tag.weak { color:var(--amber); } .ev-tag.unknown { color:var(--amber); }
/* ---------- twelve-lens ledger ---------- */
.ledger { margin-top:22px; border-top:1px solid var(--line); }
.lens { display:grid; grid-template-columns:186px minmax(0,1fr); gap:26px; padding:19px 0; border-bottom:1px solid var(--line-soft); align-items:start; }
.lens .name { font-size:.82rem; font-weight:620; color:var(--ink); }
.lens .tags { display:flex; flex-direction:column; gap:5px; margin-top:7px; }
.dir-tag { font-size:.66rem; font-weight:650; letter-spacing:.04em; line-height:1.4; }
.dir-tag.rust { color:var(--rust); } .dir-tag.current { color:var(--blue); }
.dir-tag.neutral { color:var(--ink-3); } .dir-tag.unknown { color:var(--amber); }
.lens .claim { font-size:.87rem; color:var(--ink-2); line-height:1.6; }
.lens .source { margin-top:6px; font-size:.72rem; color:var(--ink-3); word-break:break-word; }
/* ---------- evidence cards ---------- */
.cards { display:grid; grid-template-columns:repeat(auto-fit,minmax(290px,1fr)); gap:26px 34px; margin-top:24px; }
.card { padding-left:15px; border-left:2px solid var(--line); }
.card.rust { border-left-color:var(--rust); }
.card.current { border-left-color:var(--blue); }
.card.unknown { border-left-color:var(--amber); }
.card h3 { font-size:.9rem; font-weight:620; letter-spacing:-.01em; }
.card p { margin-top:7px; font-size:.85rem; color:var(--ink-2); line-height:1.6; }
.card .ref { display:block; margin-top:9px; font-size:.7rem; color:var(--ink-3); word-break:break-word; }
/* ---------- buys / doesn't buy ---------- */
.duo { display:grid; grid-template-columns:1fr 1fr; gap:44px; margin-top:24px; }
.col h3 { display:flex; align-items:center; gap:8px; font-size:.72rem; letter-spacing:.1em; text-transform:uppercase; color:var(--ink-3); font-weight:650; }
.col h3 i { width:7px; height:7px; border-radius:50%; flex:none; }
.col ul { list-style:none; margin-top:14px; }
.col li { padding:11px 0; border-bottom:1px solid var(--line-soft); font-size:.85rem; color:var(--ink-2); line-height:1.58; }
.col li:last-child { border-bottom:0; }
.col li b { color:var(--ink); font-weight:620; }
/* ---------- precedents ---------- */
.precedents { margin-top:22px; border-top:1px solid var(--line); }
.precedent { padding:19px 0; border-bottom:1px solid var(--line-soft); }
.precedent .top { display:flex; align-items:baseline; gap:11px; flex-wrap:wrap; }
.precedent h3 { font-size:.9rem; font-weight:620; }
.precedent .outcome { font-family:var(--mono); font-size:.66rem; letter-spacing:.07em; color:var(--accent); }
.precedent p { margin-top:7px; font-size:.85rem; color:var(--ink-2); line-height:1.6; max-width:var(--measure); }
.precedent .match { margin-top:8px; font-size:.75rem; color:var(--ink-3); line-height:1.55; }
.precedent .ref { display:inline-block; margin-top:8px; font-size:.7rem; color:var(--ink-3); word-break:break-all; }
.precedent .ref:hover { color:var(--accent); }
/* ---------- reversible path ---------- */
.path { margin-top:22px; counter-reset:step; border-top:1px solid var(--line); }
.step { display:grid; grid-template-columns:30px minmax(0,1fr) auto; gap:16px; padding:19px 0; border-bottom:1px solid var(--line-soft); align-items:start; }
.step .number { counter-increment:step; font-family:var(--mono); font-size:.74rem; color:var(--ink-3); padding-top:2px; }
.step .number::before { content:counter(step,decimal-leading-zero); }
.step h3 { font-size:.9rem; font-weight:620; }
.step p { margin-top:6px; font-size:.83rem; color:var(--ink-2); line-height:1.6; }
.step .cost { font-size:.72rem; color:var(--ink-3); white-space:nowrap; padding-top:3px; }
/* ---------- challenge audit ---------- */
.challenge-grid { display:grid; grid-template-columns:1fr 1fr; gap:44px; margin-top:24px; }
.challenge-col h3 { font-size:.72rem; letter-spacing:.1em; text-transform:uppercase; color:var(--ink-3); font-weight:650; padding-bottom:11px; border-bottom:1px solid var(--line); }
.check { padding:13px 0; border-bottom:1px solid var(--line-soft); font-size:.83rem; color:var(--ink-2); line-height:1.58; }
.check:last-child { border-bottom:0; }
.check .state { margin-right:9px; vertical-align:1px; }
.check b { color:var(--ink); font-weight:620; }
/* ---------- method ---------- */
.method { margin-top:22px; padding-top:22px; border-top:1px solid var(--line); }
.method h3 { font-size:.88rem; font-weight:620; }
.method p { margin-top:10px; font-size:.83rem; color:var(--ink-2); line-height:1.68; max-width:var(--measure); }
.method table { margin-top:24px; min-width:0; }
footer {
margin-top:64px; padding-top:20px; border-top:1px solid var(--line);
color:var(--ink-3); font-size:.73rem; display:flex; gap:20px; flex-wrap:wrap;
}
@media (max-width:960px) { .hero-grid { grid-template-columns:1fr; gap:38px; } }
@media (max-width:760px) {
body { padding:0 18px 64px; font-size:15px; }
.mast { flex-wrap:wrap; gap:10px; }
.mast .meta { margin-left:auto; gap:10px; }
.mast .meta span:not(:first-child) { display:none; }
.cards,.duo,.challenge-grid { grid-template-columns:1fr; gap:30px; }
.lens { grid-template-columns:1fr; gap:9px; }
.step { grid-template-columns:24px minmax(0,1fr); }
.step .cost { grid-column:2; }
section { padding-top:44px; }
}
@media print {
body { padding:0 8mm; font-size:11pt; }
.btn { display:none; }
section { padding-top:26px; break-inside:avoid; }
}
/* ---------- bilingual switch ----------
Both languages ship in the document; the root data-lang attribute selects
one. No new script element is involved: the toggle is the same kind of inline handler
as the theme button. */
:root[data-lang="en"] [data-l="zh"] { display:none; }
:root[data-lang="zh"] [data-l="en"] { display:none; }
:root[data-lang="zh"] body {
font-family:"PingFang SC","Hiragino Sans GB","Microsoft YaHei","Noto Sans CJK SC",var(--sans);
line-height:1.75;
}
:root[data-lang="zh"] .mono,
:root[data-lang="zh"] .scope-word,
:root[data-lang="zh"] .scope-tag,
:root[data-lang="zh"] .outcome,
:root[data-lang="zh"] .step .number { font-family:var(--mono); }
:root[data-lang="zh"] .kicker { letter-spacing:.06em; }
:root[data-lang="zh"] .lens { grid-template-columns:172px minmax(0,1fr); }
</style>
</head>
<body class="v-stay a-reject">
<div class="wrap">
<header class="mast">
<div class="logo mono">why<span class="q">-not-</span>rust</div>
<div class="meta">
<span class="mono">ghostty-org/ghostty</span><span>2026-08-02</span><span class="mono">method 2.0</span>
<button class="btn" onclick="document.documentElement.dataset.lang=document.documentElement.dataset.lang==='en'?'zh':'en'">EN / 中文</button>
<button class="btn" onclick="document.documentElement.dataset.theme=document.documentElement.dataset.theme==='dark'?'light':'dark'"><span data-l="en">light / dark</span><span data-l="zh">浅色 / 深色</span></button>
</div>
</header>
<section class="hero">
<div class="kicker"><span data-l="en">Rust adoption decision</span><span data-l="zh">Rust 采用决策</span> · <span class="project"><span data-l="en">Zig · terminal emulator · 311,038 lines of Zig, 32,377 lines of Swift in the macOS app, 995 lines of its own C++</span><span data-l="zh">Zig · 终端模拟器 · Zig 311,038 行,macOS 应用里 Swift 32,377 行,自有 C++ 995 行</span></span></div>
<div class="hero-grid">
<div>
<div class="scope-word">STAY</div>
<div class="scope-sub"><span data-l="en">stay in Zig — the cheap fix has not been priced yet</span><span data-l="zh">留在 Zig——便宜那条路的代价还没人算过</span></div>
<div class="auth"><i></i><span data-l="en">Authorization</span><span data-l="zh">授权</span> · REJECT</div>
<p class="why"><span data-l="en">Ghostty ships its tagged release with Zig's safety checks off, and PACKAGING.md says the maintainer would rather not. So the memory-safety requirement stands. What fails is the price. A safety-checked macOS build is already codesigned, notarized and published on the tip channel every commit to main. Nobody has published what it costs, and src/benchmark/ already holds the harnesses that would say. Meanwhile the quality people praise Ghostty for is 32,377 lines of Swift, and all five of its published advisories are logic bugs Rust would not have caught.</span><span data-l="zh">Ghostty 的正式发布版关掉了 Zig 的安全检查,PACKAGING.md 里作者自己写了他并不情愿。所以内存安全这个需求是立得住的。倒下的是价格这一环。带安全检查的 macOS 构建早就在跑:main 每有一次提交,它都会被签名、公证、发到 tip 通道。可没人公布过它的代价,而 src/benchmark/ 里就摆着能算出这个数的那套 harness。另一边,大家真正称赞 Ghostty 的那部分是 32,377 行 Swift,而它公开的五个安全公告全是逻辑缺陷,Rust 一个也拦不住。</span></p>
<p class="trigger"><span data-l="en">Conditional on two measurements that do not exist. Publish the ReleaseFast-versus-ReleaseSafe cost on src/benchmark's VT harnesses, and classify one release cycle of AFL++ crashes into spatial, temporal and logic buckets. If the safe build turns out unaffordable and the temporal bucket is not empty, the VT extraction behind the existing 185-symbol C ABI becomes the live question.</span><span data-l="zh">结论挂在两份还不存在的测量上。第一份:在 src/benchmark 的 VT harness 上把 ReleaseFast 和 ReleaseSafe 的代价跑出来公布。第二份:把一个发布周期内 AFL++ 找到的崩溃分成空间类、时间类和逻辑类。如果安全构建确实付不起,而时间类那一桶不是空的,那么在现成的 185 个符号 C ABI 后面抽出 VT 引擎,就成了真问题。</span></p>
<div class="chips">
<span class="scope"><span data-l="en">Scope</span><span data-l="zh">范围</span> <b><span data-l="en">keep Zig; price the safety-checked build first</span><span data-l="zh">留在 Zig;先把带安全检查的构建代价算出来</span></b></span>
<span><span data-l="en">Confidence</span><span data-l="zh">置信度</span> <b>MEDIUM</b></span>
<span><span data-l="en">Robustness</span><span data-l="zh">稳健性</span> <b>CONDITIONAL</b></span>
<span><span data-l="en">native-desktop-gui · a Zig core with a Swift frontend, parsing untrusted VT input from every process the user runs</span><span data-l="zh">原生桌面 GUI · Zig 内核配 Swift 前端,解析用户启动的每个进程送来的不可信 VT 输入</span></span>
</div>
</div>
<aside class="gatebox">
<h2><span data-l="en">Four proof gates</span><span data-l="zh">四道证据门</span></h2>
<div class="gate"><span class="id mono">G1</span><div><div class="head"><span class="name"><span data-l="en">Requirement</span><span data-l="zh">需求</span></span><span class="state pass">PASS</span></div><div class="ev"><span data-l="en">The shipped release has bounds and overflow checks disabled.</span><span data-l="zh">发布版本关掉了边界检查和溢出检查。</span></div></div></div>
<div class="gate"><span class="id mono">G2</span><div><div class="head"><span class="name"><span data-l="en">Causality</span><span data-l="zh">因果</span></span><span class="state pass">PASS</span></div><div class="ev"><span data-l="en">Rust's ownership model reaches use-after-free; ReleaseSafe does not.</span><span data-l="zh">Rust 的所有权模型管得到 use-after-free,ReleaseSafe 管不到。</span></div></div></div>
<div class="gate"><span class="id mono">G3</span><div><div class="head"><span class="name"><span data-l="en">Economics</span><span data-l="zh">经济性</span></span><span class="state fail">FAIL</span></div><div class="ev"><span data-l="en">The one-flag alternative already ships. Nobody priced it.</span><span data-l="zh">改一个开关的方案已经在发布了,没人算过它的代价。</span></div></div></div>
<div class="gate"><span class="id mono">G4</span><div><div class="head"><span class="name"><span data-l="en">Delivery</span><span data-l="zh">交付</span></span><span class="state fail">FAIL</span></div><div class="ev"><span data-l="en">275 exported C symbols, two frontends, and no dual-run plan.</span><span data-l="zh">275 个导出的 C 符号、两套前端,没有并跑方案。</span></div></div></div>
</aside>
</div>
</section>
<div class="tiles">
<div class="tile"><div class="label"><span data-l="en">Zig, minus inline tests</span><span data-l="zh">Zig,扣掉内联测试</span></div><div class="value mono">213,626<span class="unit"><span data-l="en">lines</span><span data-l="zh">行</span></span></div><div class="note"><span data-l="en">755 tracked .zig files; 97,412 lines sit inside test blocks</span><span data-l="zh">版本库内 755 个 .zig 文件;其中 97,412 行在 test 块里</span></div></div>
<div class="tile"><div class="label"><span data-l="en">Swift in the macOS app</span><span data-l="zh">macOS 应用里的 Swift</span></div><div class="value mono">32,377<span class="unit"><span data-l="en">lines</span><span data-l="zh">行</span></span></div><div class="note"><span data-l="en">macos/Sources · 160 files · 67 import AppKit, 63 import SwiftUI</span><span data-l="zh">macos/Sources · 160 个文件 · 67 个 import AppKit,63 个 import SwiftUI</span></div></div>
<div class="tile"><div class="label"><span data-l="en">Ghostty's own C++</span><span data-l="zh">Ghostty 自己的 C++</span></div><div class="value mono">995<span class="unit"><span data-l="en">lines</span><span data-l="zh">行</span></span></div><div class="note"><span data-l="en">src/simd/*.cpp · 4 files · portable SIMD via Google Highway</span><span data-l="zh">src/simd/*.cpp · 4 个文件 · 靠 Google Highway 做可移植 SIMD</span></div></div>
<div class="tile"><div class="label"><span data-l="en">Published advisories caused by memory unsafety</span><span data-l="zh">由内存不安全导致的公开公告</span></div><div class="value mono">0<span class="unit"><span data-l="en">of 5</span><span data-l="zh">/5</span></span></div><div class="note"><span data-l="en">CWE-78, CWE-94, CWE-284, an fd leak, an escalation vector</span><span data-l="zh">CWE-78、CWE-94、CWE-284、一个 fd 泄漏、一条提权路径</span></div></div>
<div class="tile"><div class="label"><span data-l="en">Exported C symbols the frontends link</span><span data-l="zh">前端链接的导出 C 符号</span></div><div class="value mono">275<span class="unit"><span data-l="en">symbols</span><span data-l="zh">个</span></span></div><div class="note"><span data-l="en">90 export fn ghostty_* + 185 @export in src/lib_vt.zig</span><span data-l="zh">90 个 export fn ghostty_* 加 src/lib_vt.zig 里 185 个 @export</span></div></div>
<div class="tile"><div class="label"><span data-l="en">Published measurements of the safe build's cost</span><span data-l="zh">关于安全构建代价的公开测量</span></div><div class="value mono">0<span class="unit"><span data-l="en">published</span><span data-l="zh">份</span></span></div><div class="note"><span data-l="en">ReleaseSafe is already codesigned, notarized and shipped on tip</span><span data-l="zh">ReleaseSafe 早已签名、公证,并在 tip 通道发布</span></div></div>
</div>
<section>
<h2 class="section-title"><span class="no">01</span><span data-l="en">The options</span><span data-l="zh">备选方案</span></h2>
<p class="sub"><span data-l="en">Every option answers one requirement: remove or downgrade the memory-unsafety class on Ghostty's VT parsing path, without giving up the native macOS behaviour or the libghostty C ABI.</span><span data-l="zh">所有方案对着同一个需求:把 Ghostty 的 VT 解析路径上的内存不安全缺陷类消除或降级,同时不放弃 macOS 的原生行为,也不动 libghostty 的 C ABI。</span></p>
<div class="table-wrap"><table>
<thead><tr><th><span data-l="en">Option</span><span data-l="zh">方案</span></th><th><span data-l="en">Scope</span><span data-l="zh">范围</span></th><th><span data-l="en">Benefit</span><span data-l="zh">收益</span></th><th><span data-l="en">Cost</span><span data-l="zh">成本</span></th><th><span data-l="en">Time to value</span><span data-l="zh">见效时间</span></th><th><span data-l="en">Compatibility / rollback</span><span data-l="zh">兼容 / 回滚</span></th><th><span data-l="en">Evidence</span><span data-l="zh">证据</span></th></tr></thead>
<tbody>
<tr class="selected"><td><span class="option-name"><span data-l="en">Price ReleaseSafe, then ship it</span><span data-l="zh">先算 ReleaseSafe 的代价,再发布它</span></span><span class="source"><span data-l="en">recommended · already built, signed and shipping on tip</span><span data-l="zh">推荐 · 已经构建、签名,并在 tip 通道发布</span></span></td><td><span class="scope-tag">STAY</span></td><td><span data-l="en">spatial class becomes a deterministic panic across all 213,626 lines</span><span data-l="zh">全部 213,626 行上,空间类缺陷变成确定的 panic</span></td><td><span data-l="en">one benchmark run; one flag</span><span data-l="zh">跑一次基准;改一个开关</span></td><td><span data-l="en">days</span><span data-l="zh">数天</span></td><td><span data-l="en">identical ABI · revert one flag</span><span data-l="zh">ABI 不变 · 回退一个开关</span></td><td><span class="ev-tag moderate">MODERATE</span></td></tr>
<tr class=""><td><span class="option-name"><span data-l="en">Keep the AFL++ programme as it is</span><span data-l="zh">AFL++ 那套照现在继续跑</span></span><span class="source"><span data-l="en">retain · the funded counterfactual any Rust claim must beat</span><span data-l="zh">保留 · 任何 Rust 主张都得先赢过这条已投入的对照</span></span></td><td><span class="scope-tag">STAY</span></td><td><span data-l="en">finds defects in the VT path; eliminates no class</span><span data-l="zh">能在 VT 路径上找出缺陷;消除不了任何缺陷类</span></td><td><span data-l="en">none new; existing triage time</span><span data-l="zh">无新增;用现有的排查时间</span></td><td><span data-l="en">continuous</span><span data-l="zh">持续产出</span></td><td><span data-l="en">native · nothing to roll back</span><span data-l="zh">原生 · 没什么要回滚</span></td><td><span class="ev-tag moderate">MODERATE</span></td></tr>
<tr class=""><td><span class="option-name"><span data-l="en">Rust VT engine behind the existing C ABI</span><span data-l="zh">在现成的 C ABI 后面换一个 Rust 的 VT 引擎</span></span><span class="source"><span data-l="en">retain · the only Rust scope with a seam already built for it</span><span data-l="zh">保留 · 唯一有现成接缝的 Rust 范围</span></span></td><td><span class="scope-tag">EXTRACT</span></td><td><span data-l="en">both classes removed on the VT path; frontends unchanged</span><span data-l="zh">VT 路径上两类缺陷都消失;前端不动</span></td><td><span data-l="en">66,281 lines; a second toolchain</span><span data-l="zh">66,281 行;多一套工具链</span></td><td><span data-l="en">months to years</span><span data-l="zh">数月到数年</span></td><td><span data-l="en">same C ABI · dual-run possible</span><span data-l="zh">C ABI 不变 · 可以并跑</span></td><td><span class="ev-tag weak">WEAK</span></td></tr>
<tr class="excluded"><td><span class="option-name"><span data-l="en">Rewrite the Zig core in Rust, keep Swift</span><span data-l="zh">用 Rust 重写 Zig 内核,Swift 保留</span></span><span class="source"><span data-l="en">exclude · fails G3 against a build flag and G4 against 275 symbols</span><span data-l="zh">排除 · G3 输给一个构建开关,G4 输给 275 个符号</span></span></td><td><span class="scope-tag">MIGRATE</span></td><td><span data-l="en">both classes removed in Zig; Swift, C++ and simdutf untouched</span><span data-l="zh">Zig 里两类缺陷消失;Swift、C++、simdutf 一点不动</span></td><td><span data-l="en">213,626 lines; a new build graph</span><span data-l="zh">213,626 行;一套新的构建图</span></td><td><span data-l="en">years</span><span data-l="zh">数年</span></td><td><span data-l="en">whole C ABI · no rollback</span><span data-l="zh">整套 C ABI · 无回滚</span></td><td><span class="ev-tag weak">WEAK</span></td></tr>
<tr class="excluded"><td><span class="option-name"><span data-l="en">Rewrite the whole app in Rust, UI included</span><span data-l="zh">整个应用用 Rust 重写,UI 也算上</span></span><span class="source"><span data-l="en">exclude · trades the product's most-praised layer for language uniformity</span><span data-l="zh">排除 · 用产品最受称赞的那一层去换语言统一</span></span></td><td><span class="scope-tag">MIGRATE</span></td><td><span data-l="en">one language; loses the AppKit and SwiftUI behaviour</span><span data-l="zh">统一成一门语言;代价是丢掉 AppKit 和 SwiftUI 的行为</span></td><td><span data-l="en">246,003 lines; plus a GUI framework</span><span data-l="zh">246,003 行;再加一套 GUI 框架</span></td><td><span data-l="en">years</span><span data-l="zh">数年</span></td><td><span data-l="en">loses platform integration · no rollback</span><span data-l="zh">丢掉平台集成 · 无回滚</span></td><td><span class="ev-tag weak">WEAK</span></td></tr>
<tr class=""><td><span class="option-name"><span data-l="en">Use a terminal already written in Rust</span><span data-l="zh">改用一个已经用 Rust 写好的终端</span></span><span class="source"><span data-l="en">retain · a user's decision, not the project's</span><span data-l="zh">保留 · 这是用户的决定,不是项目的决定</span></span></td><td><span class="scope-tag">ADOPT</span></td><td><span data-l="en">class removed for that user; a different product</span><span data-l="zh">对那位用户来说缺陷类消失了;但换了个产品</span></td><td><span data-l="en">per user; different feature set</span><span data-l="zh">按用户计;功能集不同</span></td><td><span data-l="en">an afternoon</span><span data-l="zh">一个下午</span></td><td><span data-l="en">nothing shared · switch back freely</span><span data-l="zh">毫无共享 · 想换回来就换</span></td><td><span class="ev-tag moderate">MODERATE</span></td></tr>
</tbody>
</table></div>
</section>
<!-- OPTIONAL: drop this section in quick mode -->
<section>
<h2 class="section-title"><span class="no">02</span><span data-l="en">Evidence ledger</span><span data-l="zh">证据账本</span></h2>
<p class="sub"><span data-l="en">Each state is evidence about named options. They are not a score and they do not add up. D2 stays UNKNOWN because no published profile says where Ghostty's frame time goes, and src/benchmark/ sitting unmeasured in the tree is why.</span><span data-l="zh">每条状态都绑到具体方案,不是分数,也不能相加。D2 停在 UNKNOWN,因为没有公开 profile 说明 Ghostty 的帧时间花在哪;src/benchmark/ 就在仓库里却没人跑,正是这条停住的原因。</span></p>
<div class="ledger">
<div class="lens"><div><div class="name">D1 · <span data-l="en">Requirement & ownership</span><span data-l="zh">需求与归属</span></div><div class="tags"><span class="dir-tag current">SUPPORTS · stay-safe-build, rust options</span><span class="ev-tag strong">STRONG</span></div></div><div><div class="claim"><span data-l="en">The unsafe surface is owned and easy to point at. src/terminal is 140,441 lines of Zig, and it terminates VT sequences coming from whatever the user runs. The tagged release builds all of it with safety checks off. The project wrote that down itself.</span><span data-l="zh">不安全面是自有的,而且一指就到。src/terminal 是 140,441 行 Zig,它直接终结用户所跑程序送来的 VT 序列。正式发布版把这些全部在关掉安全检查的模式下编译。这件事是项目自己写下来的。</span></div><div class="source mono">PACKAGING.md:111 · .github/workflows/release-tag.yml:177 · src/terminal 140,441 lines across 141 files</div></div></div>
<div class="lens"><div><div class="name">D2 · <span data-l="en">End-to-end reach</span><span data-l="zh">端到端影响面</span></div><div class="tags"><span class="dir-tag unknown">UNKNOWN · rust-vt-extract, rust-core</span><span class="ev-tag unknown">UNKNOWN</span></div></div><div><div class="claim"><span data-l="en">No Amdahl figure appears here. Nobody has published where Ghostty's frame time goes, so there is no f worth defending. src/benchmark/ holds 15 harnesses that could produce one. Using a line share as a stand-in for a time share would be a method error.</span><span data-l="zh">这里不给 Amdahl 数。没人公开过 Ghostty 的帧时间花在哪里,所以没有站得住的 f。src/benchmark/ 里有 15 个 harness 能跑出来。拿代码行数占比顶替时间占比,是方法错误。</span></div><div class="source mono">src/benchmark/ · 18 files including TerminalParser, TerminalStream, OscParser, CodepointWidth</div></div></div>
<div class="lens"><div><div class="name">D3 · <span data-l="en">Tail & runtime</span><span data-l="zh">尾延迟与运行时</span></div><div class="tags"><span class="dir-tag neutral">NEUTRAL · all</span><span class="ev-tag moderate">MODERATE</span></div></div><div><div class="claim"><span data-l="en">Neither Zig nor Rust has a collector or a JIT, so no option removes a runtime mechanism from the frame budget. There is none to remove. Rendering goes through Metal on macOS and OpenGL elsewhere, and that driver time is shared by every option on the table.</span><span data-l="zh">Zig 和 Rust 都没有 collector,也没有 JIT,所以没有哪个方案能从帧预算里拿掉一个运行时机制——本来就没有。渲染在 macOS 走 Metal,其他平台走 OpenGL,这部分驱动时间对所有方案都一样。</span></div><div class="source mono">src/renderer/Metal.zig · src/renderer/OpenGL.zig · src/renderer/shaders/shaders.metal</div></div></div>
<div class="lens"><div><div class="name">D4 · <span data-l="en">Fleet footprint</span><span data-l="zh">机队占用</span></div><div class="tags"><span class="dir-tag neutral">N/A</span><span class="ev-tag moderate">MODERATE</span></div></div><div><div class="claim"><span data-l="en">Ghostty is a desktop app. There is no instance count, no utilization figure and no price per hour to multiply. Per-window memory is dominated by scrollback, which the project already compresses.</span><span data-l="zh">Ghostty 是桌面应用。没有实例数、没有利用率、也没有每小时价格可乘。单窗口内存主要由回滚缓冲决定,而项目已经在压缩它了。</span></div><div class="source mono">src/terminal/compress/ · desktop distribution</div></div></div>
<div class="lens"><div><div class="name">D5 · <span data-l="en">Startup shape</span><span data-l="zh">启动形态</span></div><div class="tags"><span class="dir-tag neutral">N/A</span><span class="ev-tag moderate">MODERATE</span></div></div><div><div class="claim"><span data-l="en">The Mac app launches once per session and opens new windows in-process. Launch time is AppKit's, and no core-language change touches it. CLI actions exist under src/cli, but they are not the product.</span><span data-l="zh">Mac 应用一个会话启动一次,新窗口在同一进程里开。启动时间归 AppKit,换内核语言碰不到它。src/cli 下面确实有命令行动作,但那不是产品本身。</span></div><div class="source mono">macos/Sources/App/ · src/cli/ 27 files</div></div></div>
<div class="lens"><div><div class="name">D6 · <span data-l="en">Safety & correctness</span><span data-l="zh">安全与正确性</span></div><div class="tags"><span class="dir-tag rust">SUPPORTS · rust options</span><span class="ev-tag moderate">MODERATE</span></div></div><div><div class="claim"><span data-l="en">Zig's ReleaseSafe restores bounds, overflow and null checks. It has no ownership model, so use-after-free and double-free live through it. Rust's safe subset removes both classes from code it replaces. Bun's team ported roughly 535k lines of Zig and named that exact stream of bugs as the reason.</span><span data-l="zh">Zig 的 ReleaseSafe 会恢复边界、溢出和空值检查。但它没有所有权模型,所以 use-after-free 和 double-free 照样活着。Rust 的 safe 子集在它替换掉的代码里把两类都清掉。Bun 团队移植了约 53.5 万行 Zig,给出的理由正是这一串 bug。</span></div><div class="source mono">PACKAGING.md:111 · https://bun.com/blog/bun-in-rust · GHSA advisory list (5 entries)</div></div></div>
<div class="lens"><div><div class="name">D7 · <span data-l="en">Concurrency & invariants</span><span data-l="zh">并发与不变量</span></div><div class="tags"><span class="dir-tag neutral">NEUTRAL · all</span><span class="ev-tag moderate">MODERATE</span></div></div><div><div class="claim"><span data-l="en">Ghostty already splits render, terminal I/O and app work across threads, and the boundaries are few and coarse. No race incident appears in the advisory history. fish shell called Send and Sync the killer feature of its own port, so this lens could move — but it would need an incident, not an argument.</span><span data-l="zh">Ghostty 已经把渲染、终端 I/O 和应用逻辑分到不同线程,边界数量少而且粗。公告历史里没有竞态事故。fish shell 说过 Send 和 Sync 是它那次移植真正的杀手特性,所以这一条不是不能动;但要动它得靠一次事故,不是靠论证。</span></div><div class="source mono">src/renderer/Thread.zig · src/termio/ 9 files · https://fishshell.com/blog/rustport/</div></div></div>
<div class="lens"><div><div class="name">D8 · <span data-l="en">Distribution & embedding</span><span data-l="zh">分发与嵌入</span></div><div class="tags"><span class="dir-tag neutral">NEUTRAL · all</span><span class="ev-tag moderate">MODERATE</span></div></div><div><div class="claim"><span data-l="en">Nothing about distribution is unmet. Ghostty ships a notarized universal macOS binary with a Sparkle update channel, a GTK app on Linux, plus flatpak and snap. libghostty-vt builds for Windows and for WebAssembly without libc. Rust could reach all of those too.</span><span data-l="zh">分发上没有未满足的约束。Ghostty 发布经过公证的 macOS 通用二进制,带 Sparkle 更新通道;Linux 上是 GTK 应用,另有 flatpak 和 snap。libghostty-vt 能编到 Windows,也能编到 WebAssembly,且不依赖 libc。Rust 同样都能做到。</span></div><div class="source mono">flatpak/ · snap/ · dist/ · include/ghostty/vt/wasm.h · .github/workflows/release-tag.yml</div></div></div>
<div class="lens"><div><div class="name">D9 · <span data-l="en">Ecosystem & alternatives</span><span data-l="zh">生态与替代品</span></div><div class="tags"><span class="dir-tag rust">SUPPORTS · rust options</span><span class="ev-tag moderate">MODERATE</span></div></div><div><div class="claim"><span data-l="en">Zig has not reached 1.0. build.zig.zon pins minimum_zig_version 0.16.0, and breaking compiler changes between minor versions are the normal cost of that. Rust has kept its language stable since 2015. This is the strongest point against staying, and it has nothing to do with speed.</span><span data-l="zh">Zig 还没到 1.0。build.zig.zon 把 minimum_zig_version 钉在 0.16.0,小版本之间的破坏性语言变更就是这么来的常态成本。Rust 从 2015 年起语言层面保持稳定。这是反对「不动」最强的一条,而且跟速度无关。</span></div><div class="source mono">build.zig.zon:6 · pkg/macos 3,273 lines of Zig bindings to Apple frameworks across 56 files</div></div></div>
<div class="lens"><div><div class="name">D10 · <span data-l="en">Boundary & compatibility</span><span data-l="zh">边界与兼容</span></div><div class="tags"><span class="dir-tag rust">DISFAVORS · rust-core, rust-everything</span><span class="ev-tag strong">STRONG</span></div></div><div><div class="claim"><span data-l="en">The compatibility surface is 275 exported C symbols across two APIs, plus 11,353 lines of headers under include/. A 32,377-line Swift app and a 28,408-line GTK frontend compile against them. The extraction option inherits that seam for free, which is the whole reason it survives.</span><span data-l="zh">兼容面是两套 API 上 275 个导出 C 符号,加 include/ 下 11,353 行头文件。一个 32,377 行的 Swift 应用和一个 28,408 行的 GTK 前端就是对着它们编译的。抽取方案能白拿这条接缝,这也正是它没被排除的全部原因。</span></div><div class="source mono">90 export fn ghostty_* in src/ (71 in src/apprt/embedded.zig) · 185 @export names in src/lib_vt.zig · include/ 11,353 lines across 34 files</div></div></div>
<div class="lens"><div><div class="name">D11 · <span data-l="en">Delivery economics</span><span data-l="zh">交付经济性</span></div><div class="tags"><span class="dir-tag rust">DISFAVORS · rust-core, rust-everything</span><span class="ev-tag moderate">MODERATE</span></div></div><div><div class="claim"><span data-l="en">213,626 non-test lines of Zig, and another 97,412 inside test blocks that do not port. One person authored 68.8% of the 16,557 commits on the default branch. fish shell estimated half a year for 57k lines of C++ and took about two. Ghostty's non-test Zig is 3.7 times that.</span><span data-l="zh">非测试的 Zig 有 213,626 行,另有 97,412 行在 test 块里,而这些搬不过去。默认分支上 16,557 次提交里,68.8% 出自一个人。fish shell 当年估计半年搞定 5.7 万行 C++,实际花了约两年。Ghostty 的非测试 Zig 是那个体量的 3.7 倍。</span></div><div class="source mono">gh api repos/ghostty-org/ghostty/contributors --paginate · 425 contributors listed, 16,557 contributions, top author 11,397 · https://fishshell.com/blog/rustport/</div></div></div>
<div class="lens"><div><div class="name">D12 · <span data-l="en">Counterfactual</span><span data-l="zh">对照方案</span></div><div class="tags"><span class="dir-tag current">SUPPORTS · stay-safe-build, stay-fuzz</span><span class="ev-tag strong">STRONG</span></div></div><div><div class="claim"><span data-l="en">Two in-stack options are already running. A ReleaseSafe macOS build is codesigned, notarized and published on the tip channel on every commit to main, with its own Sentry dSYM upload. AFL++ covers three VT targets over 4,001 committed corpus files. A Rust proposal has to beat both on one measurement.</span><span data-l="zh">栈内有两条路已经在跑。ReleaseSafe 的 macOS 构建每次 main 提交都会被签名、公证、发到 tip 通道,还配了自己的 Sentry dSYM 上传。AFL++ 覆盖三个 VT 目标,语料库入库 4,001 个文件。Rust 提案得在同一份测量上赢过这两条。</span></div><div class="source mono">.github/workflows/release-tip.yml:929, 1036, 1044 · test/fuzz-libghostty/ · 3 harnesses, 4,001 corpus files · 3,524 inline test blocks in src/</div></div></div>
</div>
<p class="sub"><span data-l="en">Two lenses are N/A. D4 fleet footprint: a terminal runs on one machine at a time, so there is no instance count to price. D5 startup shape: the Mac app is launched once per session and new windows open in-process.</span><span data-l="zh">两条记为 N/A。D4 机队占用:终端一次只在一台机器上跑,没有实例数可以计价。D5 启动形态:Mac 应用一个会话只启动一次,新窗口在同一进程里开。</span></p>
</section>
<!-- OPTIONAL: drop this section in quick mode -->
<section>
<h2 class="section-title"><span class="no">03</span><span data-l="en">What decided it</span><span data-l="zh">决定性证据</span></h2>
<div class="cards">
<div class="card current"><h3><span data-l="en">The Mac-native part is 32,377 lines of Swift</span><span data-l="zh">Mac 原生那部分是 32,377 行 Swift</span></h3><p><span data-l="en">160 Swift files sit under macos/Sources. 67 import AppKit, 63 import SwiftUI, 36 import Cocoa. Twelve files implement AppleScript support and twelve more implement App Intents. There are four separate titlebar implementations, two of them named for macOS versions. Whichever core language Ghostty had picked, this layer would still be Swift.</span><span data-l="zh">macos/Sources 下有 160 个 Swift 文件。67 个 import AppKit,63 个 import SwiftUI,36 个 import Cocoa。十二个文件做 AppleScript 支持,另外十二个做 App Intents。标题栏有四套独立实现,其中两套按 macOS 版本命名。不管内核选哪门语言,这一层还是 Swift。</span></p><span class="ref mono">macos/Sources · 160 .swift files · 32,377 lines</span></div>
<div class="card unknown"><h3><span data-l="en">A safety-checked build already ships, and nobody priced it</span><span data-l="zh">带安全检查的构建早就在发布,没人算过它的代价</span></h3><p><span data-l="en">.github/workflows/release-tip.yml:929 builds the macOS app with -Doptimize=ReleaseSafe. The job then codesigns it with production certificates, notarizes it through Apple, and publishes it as ghostty-macos-universal-debug-fast.zip on the tip release and to R2. PACKAGING.md:111 says the tagged build uses ReleaseFast because the safe build is currently too slow. No measurement backs that, and src/benchmark/ already contains the harnesses that would produce one.</span><span data-l="zh">.github/workflows/release-tip.yml:929 用 -Doptimize=ReleaseSafe 构建 macOS 应用。同一个 job 接着用生产证书签名、送 Apple 公证,然后以 ghostty-macos-universal-debug-fast.zip 的名字发到 tip release 和 R2。PACKAGING.md:111 写的是:正式版用 ReleaseFast,因为安全构建目前太慢。这句话背后没有测量,而 src/benchmark/ 里已经放着能跑出这个数的 harness。</span></p><span class="ref mono">.github/workflows/release-tip.yml:929 · release-tag.yml:177 · PACKAGING.md:111</span></div>
<div class="card current"><h3><span data-l="en">Zero of five published advisories are memory-safety bugs</span><span data-l="zh">五个公开公告里,内存安全类是零</span></h3><p><span data-l="en">The GHSA list holds five entries: command execution through control characters in paste and drag-and-drop (CWE-78), code injection through window title sequences (CWE-94), world-readable files from write_*_file actions (CWE-284), file descriptors leaked to the shell, and use as a privilege-escalation vector. Rust prevents none of them. This says nothing about whether Ghostty is safe; it says which class its shipped defects belong to.</span><span data-l="zh">GHSA 列表里五条:粘贴与拖放中的控制字符导致命令执行(CWE-78)、窗口标题序列导致代码注入(CWE-94)、write_*_file 生成的文件全局可读(CWE-284)、文件描述符泄漏给 shell,以及被当作提权路径使用。Rust 一条都拦不住。这不能说明 Ghostty 安全不安全,只说明它已发生的缺陷属于哪一类。</span></p><span class="ref mono">gh api repos/ghostty-org/ghostty/security-advisories · 5 entries</span></div>
<div class="card current"><h3><span data-l="en">Ghostty writes C++ on purpose</span><span data-l="zh">Ghostty 是有意去写 C++ 的</span></h3><p><span data-l="en">Four .cpp files in src/simd total 995 lines, and they exist to use Google Highway's portable-SIMD templates plus simdutf. simdutf's amalgamation is vendored in-tree at 52,566 lines; Highway itself is fetched at build time. Zig reaches them with a bare extern "c" fn declaration and keeps scalar Zig fallbacks behind -Dsimd. One language did not cover this problem and the project stopped pretending it did.</span><span data-l="zh">src/simd 下四个 .cpp 一共 995 行,存在的理由是用 Google Highway 的可移植 SIMD 模板加 simdutf。simdutf 的合并源码入库 52,566 行;Highway 本身在构建时才拉。Zig 用一行 extern "c" fn 声明就接上了,同时在 -Dsimd 后面留着纯 Zig 的标量兜底。一门语言没能盖住这个问题,项目也就不再假装它能。</span></p><span class="ref mono">src/simd/vt.cpp:1-9 · src/simd/vt.zig:6 · pkg/simdutf/vendor/ 52,566 lines</span></div>
<div class="card rust"><h3><span data-l="en">The temporal class is where the Rust argument actually lives</span><span data-l="zh">Rust 的论点真正落在时间类缺陷上</span></h3><p><span data-l="en">ReleaseSafe restores bounds, overflow and null checks. Zig has no ownership model, so use-after-free and double-free walk straight through it. Bun's team ported roughly 535k lines of Zig to Rust and named that stream of bugs as the motive; their selected benchmarks moved 2.2 to 4.8%. Ghostty's answer today is AFL++ over three VT targets with 4,001 committed corpus files and 3,524 inline test blocks. Fuzzing finds. It does not eliminate.</span><span data-l="zh">ReleaseSafe 会恢复边界、溢出和空值检查。Zig 没有所有权模型,所以 use-after-free 和 double-free 直接穿过去了。Bun 团队把约 53.5 万行 Zig 移到 Rust,理由就是这一串 bug;他们挑出来的基准变化是 2.2% 到 4.8%。Ghostty 现在的答案是 AFL++ 打三个 VT 目标,语料库入库 4,001 个文件,外加 3,524 个内联 test 块。fuzz 能找到问题。它消不掉缺陷类。</span></p><span class="ref mono">test/fuzz-libghostty/ · 3 harnesses, 4,001 corpus files · https://bun.com/blog/bun-in-rust</span></div>
</div>
</section>
<!-- OPTIONAL: drop this section in quick mode -->
<section>
<h2 class="section-title"><span class="no">04</span><span data-l="en">What Rust buys here, and what it doesn't</span><span data-l="zh">Rust 在这里买得到什么,买不到什么</span></h2>
<div class="duo">
<div class="col"><h3><i style="background:var(--rust)"></i><span data-l="en">Buys</span><span data-l="zh">买得到</span></h3><ul>
<li><b><span data-l="en">The temporal class, which no build flag reaches</span><span data-l="zh">时间类缺陷,任何构建开关都管不到</span></b> — <span data-l="en">use-after-free and double-free leave any Zig that Rust replaces. ReleaseSafe cannot do that, because Zig has no ownership model to check.</span><span data-l="zh">凡是被 Rust 换掉的 Zig,use-after-free 和 double-free 就从那里消失。ReleaseSafe 做不到,因为 Zig 没有所有权模型可查。</span></li><li><b><span data-l="en">A seam that was already built for it</span><span data-l="zh">一条本来就是为它准备好的接缝</span></b> — <span data-l="en">185 symbols exported from src/lib_vt.zig were designed as a C ABI. A Rust VT engine could sit behind them without the Swift or GTK frontends noticing.</span><span data-l="zh">src/lib_vt.zig 导出的 185 个符号本身就是按 C ABI 设计的。Rust 写的 VT 引擎可以藏在后面,Swift 和 GTK 前端都察觉不到。</span></li><li><b><span data-l="en">A compiler that does not move under you</span><span data-l="zh">一个不会在脚下移动的编译器</span></b> — <span data-l="en">build.zig.zon pins Zig 0.16.0, pre-1.0. Rust has held its language stable since 2015, and that is a cost of staying which no measurement here removes.</span><span data-l="zh">build.zig.zon 把 Zig 钉在 0.16.0,还在 1.0 之前。Rust 的语言从 2015 年起保持稳定。这是「不动」要付的一笔钱,本报告里没有任何测量能把它抹掉。</span></li>
</ul></div>
<div class="col"><h3><i style="background:var(--blue)"></i><span data-l="en">Doesn't buy</span><span data-l="zh">买不到</span></h3><ul>
<li><b><span data-l="en">The quality people actually praise</span><span data-l="zh">大家真正称赞的那个品质</span></b> — <span data-l="en">native tabs, AppleScript, App Intents and four titlebar styles are 32,377 lines of AppKit and SwiftUI. They stay Swift under every option here.</span><span data-l="zh">原生标签页、AppleScript、App Intents、四套标题栏样式,是 32,377 行 AppKit 和 SwiftUI。在这里的每个方案下,它们都还是 Swift。</span></li><li><b><span data-l="en">Any of the five published advisories</span><span data-l="zh">五个公开公告里的任何一个</span></b> — <span data-l="en">command execution through pasted control characters, code injection through window titles, file permissions, an fd leak. All language-independent.</span><span data-l="zh">粘贴控制字符导致命令执行、窗口标题导致代码注入、文件权限、fd 泄漏。全都与语言无关。</span></li><li><b><span data-l="en">Freedom from C++</span><span data-l="zh">从 C++ 里解脱</span></b> — <span data-l="en">src/simd's 995 lines and simdutf's 52,566 exist because portable SIMD needed them. A Rust core links them or rewrites them; it does not make them go away.</span><span data-l="zh">src/simd 的 995 行和 simdutf 的 52,566 行之所以存在,是因为可移植 SIMD 需要它们。Rust 内核要么继续链接,要么自己重写;它们不会因此消失。</span></li>
</ul></div>
</div>
</section>
<!-- OPTIONAL: drop this section in quick mode -->
<section>
<h2 class="section-title"><span class="no">05</span><span data-l="en">Who has done this before</span><span data-l="zh">同型先例</span></h2>
<div class="precedents">
<article class="precedent"><div class="top"><h3>Bun · Zig → Rust port</h3><span class="outcome">MIGRATED</span></div><p><span data-l="en">Roughly 535k lines of Zig were ported to Rust, and the motive was a persistent stream of use-after-free, double-free and leak bugs. Selected first-party benchmarks moved 2.2 to 4.8%. Binaries came out about 20% smaller. Nineteen regressions landed and were fixed. Zig's creator publicly disputed the port's quality at the time.</span><span data-l="zh">约 53.5 万行 Zig 被移植到 Rust,动机是一直冒出来的 use-after-free、double-free 和内存泄漏。他们挑出来的第一方基准变化在 2.2% 到 4.8% 之间。二进制小了约 20%。出了 19 处回归,后来修掉了。当时 Zig 作者公开质疑过这次移植的质量。</span></p><div class="match"><span data-l="en">matches</span><span data-l="zh">匹配</span>: <span data-l="en">the same source language, the same defect class, and a codebase of comparable order</span><span data-l="zh">同一门源语言、同一类缺陷,代码体量也是同一个量级</span> · <span data-l="en">differs</span><span data-l="zh">不匹配</span>: <span data-l="en">a JS runtime with no GUI layer and no C ABI that third-party frontends compile against</span><span data-l="zh">那是个 JS 运行时,没有 GUI 层,也没有第三方前端对着编译的 C ABI</span></div><a class="ref mono" href="https://bun.com/blog/bun-in-rust">first-party · engineering blog</a></article>
<article class="precedent"><div class="top"><h3>Alacritty · the "fastest terminal" claim</h3><span class="outcome">CLAIM FAILED AUDIT</span></div><p><span data-l="en">A greenfield Rust and OpenGL terminal called itself the fastest in existence. Dan Luu measured its latency mid-pack and called throughput dumps about as useless a benchmark as he could think of. LWN reported that ancient xterm beat every modern terminal on worst-case latency. Alacritty is a fine terminal. Only the superlative failed.</span><span data-l="zh">一个从零写的 Rust + OpenGL 终端自称是现存最快的。Dan Luu 实测它的延迟处在中游,并说吞吐量 dump 是他能想到的最没用的基准之一。LWN 报道过:在最坏情况延迟上,老旧的 xterm 打败了所有现代终端。Alacritty 是个不错的终端。倒下的只是那个最高级形容词。</span></p><div class="match"><span data-l="en">matches</span><span data-l="zh">匹配</span>: <span data-l="en">the same archetype and the same domain: a native terminal emulator written in Rust</span><span data-l="zh">同一个原型、同一个领域:用 Rust 写的原生终端模拟器</span> · <span data-l="en">differs</span><span data-l="zh">不匹配</span>: <span data-l="en">a claim audit rather than a migration; Ghostty's proposals here are about safety, not speed</span><span data-l="zh">那是对一句主张的审计,不是一次迁移;这里针对 Ghostty 的提案讲的是安全,不是速度</span></div><a class="ref mono" href="https://danluu.com/term-latency/">third-party · measured</a></article>
<article class="precedent"><div class="top"><h3>Zed · GPUI</h3><span class="outcome">MIGRATED</span></div><p><span data-l="en">A greenfield Rust editor had to build its own GPU UI framework, because no existing Rust GUI framework hit the bar. Third-party unaudited numbers put end-to-end open at about 58ms against VS Code's 97ms. The framework cost is real and it belongs in the ledger, not in the footnotes.</span><span data-l="zh">一个从零开始的 Rust 编辑器不得不自己造一套 GPU UI 框架,因为现成的 Rust GUI 框架没有一个达标。第三方未经审计的数字是:端到端打开约 58ms,VS Code 约 97ms。造框架这笔成本是实打实的,该记在账本里,不该塞进脚注。</span></p><div class="match"><span data-l="en">matches</span><span data-l="zh">匹配</span>: <span data-l="en">a native desktop GUI in Rust, and the direct precedent for what rust-everything would owe</span><span data-l="zh">用 Rust 做原生桌面 GUI,正是 rust-everything 那笔账的直接先例</span> · <span data-l="en">differs</span><span data-l="zh">不匹配</span>: <span data-l="en">greenfield rather than a migration, and Zed never had a Swift layer to give up</span><span data-l="zh">那是从零开始,不是迁移;而且 Zed 从来没有一层 Swift 要放弃</span></div><a class="ref mono" href="https://zed.dev/blog/videogame">first-party claim · third-party timings</a></article>
<article class="precedent"><div class="top"><h3>fish shell 4.0 · C++ → Rust</h3><span class="outcome">MIGRATED</span></div><p><span data-l="en">57k lines of C++ became 75k lines of Rust over about two years, against a handwaved estimate of half a year. The team's own performance summary was parity: usually slightly better on time, a slightly higher memory floor with a lower ceiling. They called Send and Sync the killer feature, and the contributor funnel mattered as much as the code.</span><span data-l="zh">5.7 万行 C++ 变成 7.5 万行 Rust,花了约两年,而当初随口估的是半年。团队自己给的性能结论是打平:时间上通常略好,内存下限略高但上限更低。他们说真正的杀手特性是 Send 和 Sync,而贡献者漏斗跟代码本身一样要紧。</span></p><div class="match"><span data-l="en">matches</span><span data-l="zh">匹配</span>: <span data-l="en">a terminal-adjacent tool, a manual-memory source language, and a safety-and-maintainability motive rather than a speed one</span><span data-l="zh">同属终端周边工具、源语言同样手动管内存,动机同样是安全与可维护性而不是速度</span> · <span data-l="en">differs</span><span data-l="zh">不匹配</span>: <span data-l="en">57k lines against Ghostty's 213,626 non-test, and no C ABI or GUI frontend to keep compiling</span><span data-l="zh">5.7 万行对 Ghostty 的 21.3 万非测试行,而且那边没有要继续编译的 C ABI 和 GUI 前端</span></div><a class="ref mono" href="https://fishshell.com/blog/rustport/">first-party · engineering blog</a></article>
<article class="precedent"><div class="top"><h3>Microsoft · VS Code text buffer</h3><span class="outcome">STAYED</span></div><p><span data-l="en">A native text buffer was tried and reverted. Converting strings across the boundary compromised any performance gained, and the fix turned out to be a better data structure in the original language. The same product adopted Rust successfully as a whole subprocess for search. The boundary decided both outcomes.</span><span data-l="zh">他们试过原生文本缓冲区,然后回滚了。字符串在边界两侧来回转换,把拿到的性能又赔掉了;最后的解法是在原语言里换一个更好的数据结构。同一个产品把 Rust 用成整个搜索子进程,那次成了。两次结果都是边界决定的。</span></p><div class="match"><span data-l="en">matches</span><span data-l="zh">匹配</span>: <span data-l="en">the boundary-cost test that separates rust-vt-extract from rust-core here</span><span data-l="zh">正是这道边界成本的测试,把这里的 rust-vt-extract 和 rust-core 分开</span> · <span data-l="en">differs</span><span data-l="zh">不匹配</span>: <span data-l="en">a managed-runtime host crossing into native code; Ghostty's boundary is already a C ABI</span><span data-l="zh">那是托管运行时跨到原生代码;Ghostty 这边的边界本来就是 C ABI</span></div><a class="ref mono" href="https://code.visualstudio.com/blogs/2018/03/23/text-buffer-reimplementation">first-party · engineering blog</a></article>
</div>
</section>
<section>
<h2 class="section-title"><span class="no">06</span><span data-l="en">What to do next</span><span data-l="zh">可逆路径</span></h2>
<div class="path">
<div class="step"><span class="number mono"></span><div><h3><span data-l="en">Publish what the safe build costs</span><span data-l="zh">把安全构建的代价公布出来</span></h3><p><span data-l="en">Build -Demit-bench twice, once at ReleaseFast and once at ReleaseSafe, then run TerminalStream, TerminalParser and OscParser on the same corpus and the same machine. Publish both numbers per harness as a percentage delta. If ReleaseSafe lands inside the frame budget, go to step 2 and the spatial half of this question closes for the price of a flag. Nothing is written to the tree; this is a measurement.</span><span data-l="zh">用 -Demit-bench 构建两次,一次 ReleaseFast,一次 ReleaseSafe,然后在同一份语料、同一台机器上跑 TerminalStream、TerminalParser 和 OscParser。按 harness 分别公布两组数字,给出百分比差值。如果 ReleaseSafe 落在帧预算之内,就走第 2 步,空间类那一半问题用一个开关的价格就结了。这一步不往仓库里写东西,它只是一次测量。</span></p></div><span class="cost"><span data-l="en">1 week</span><span data-l="zh">1 周</span></span></div>
<div class="step"><span class="number mono"></span><div><h3><span data-l="en">Make ReleaseSafe the tagged default if it fits</span><span data-l="zh">代价够低就把 ReleaseSafe 设成正式版默认</span></h3><p><span data-l="en">Change one flag in .github/workflows/release-tag.yml. The safe build is already codesigned, notarized and shipping on tip, so the artifact path is proven. Accept it when the published delta stays inside a stated budget and the tip channel's Sentry crash rate does not get worse over one release cycle. If the cost exceeds budget, keep ReleaseFast and go to step 3. Backing out is one line.</span><span data-l="zh">改 .github/workflows/release-tag.yml 里的一个开关。安全构建早就在签名、公证并从 tip 通道发布,产物链路已经验证过了。通过标准是:公布出来的差值落在事先说定的预算内,并且一个发布周期里 tip 通道的 Sentry 崩溃率没有变差。代价超预算就继续用 ReleaseFast,转第 3 步。要退回去,改一行。</span></p></div><span class="cost"><span data-l="en">days</span><span data-l="zh">数天</span></span></div>
<div class="step"><span class="number mono"></span><div><h3><span data-l="en">Classify what AFL++ actually finds in src/terminal</span><span data-l="zh">把 AFL++ 在 src/terminal 里找到的东西分类</span></h3><p><span data-l="en">Take one release cycle of AFL++ output from the three harnesses and sort every crash into three buckets: spatial defects that ReleaseSafe would have caught, temporal defects it would not, and logic defects. The count in the temporal bucket is the only realized-defect basis a Rust extraction has. If that bucket is empty across a cycle, close the extraction track and say so. No code moves.</span><span data-l="zh">取一个发布周期里三个 harness 产出的 AFL++ 结果,把每个崩溃分进三桶:ReleaseSafe 本来能拦住的空间类、它拦不住的时间类、以及逻辑类。时间类那一桶的数量,是 Rust 抽取方案唯一的已发生缺陷依据。如果一整个周期这桶是空的,就把抽取这条线关掉,并且把话说明白。这一步不动代码。</span></p></div><span class="cost"><span data-l="en">2–4 weeks</span><span data-l="zh">2–4 周</span></span></div>
<div class="step"><span class="number mono"></span><div><h3><span data-l="en">Reopen the extraction only with a plan for 275 symbols</span><span data-l="zh">275 个符号有交代了,才谈重开抽取</span></h3><p><span data-l="en">Whoever proposes the Rust VT engine writes the parity plan first. It names which of the 275 exported C symbols change signature, who owns the resulting Swift and GTK breakage, and how both engines process the same VT stream during dual-run. Without that document no extraction proceeds. Until one exists the current Zig implementation continues unchanged.</span><span data-l="zh">提出 Rust VT 引擎的人先把对等方案写出来。方案要点名 275 个导出 C 符号里哪些会改签名、由谁负责随之而来的 Swift 和 GTK 破坏,以及并跑期间两个引擎怎么处理同一条 VT 流。没有这份文档,抽取一律不往下走。在它出现之前,现在的 Zig 实现原样继续。</span></p></div><span class="cost"><span data-l="en">2 weeks per review</span><span data-l="zh">每轮评审 2 周</span></span></div>
</div>
</section>
<section>
<h2 class="section-title"><span class="no">07</span><span data-l="en">Arguing both sides</span><span data-l="zh">对称反证</span></h2>
<div class="challenge-grid">
<div class="challenge-col"><h3><span data-l="en">Against migrating</span><span data-l="zh">反驳「迁移」</span></h3>
<div class="check"><span class="state unknown">HIT</span><b><span data-l="en">Attribution</span><span data-l="zh">归因</span></b> — <span data-l="en">The quality Ghostty is praised for lives in 32,377 lines of Swift, and all five published advisories are language-independent. Only an unrealized class attaches to the core language.</span><span data-l="zh">Ghostty 被称赞的那个品质住在 32,377 行 Swift 里,而五个公开公告全部与语言无关。真正挂在内核语言上的,是一个还没发生过的缺陷类。</span></div>
<div class="check"><span class="state unknown">HIT</span><b><span data-l="en">Omitted cost</span><span data-l="zh">被漏掉的成本</span></b> — <span data-l="en">The full options leave 995 lines of Ghostty's own C++ and 52,566 vendored lines of simdutf in place, and they re-own a build graph that currently compiles C dependencies through 22 pkg/ wrappers.</span><span data-l="zh">整体方案会留下 Ghostty 自己那 995 行 C++ 和入库的 52,566 行 simdutf,还要接手一整套构建图——现在那套是通过 22 个 pkg/ 包装器来编译 C 依赖的。</span></div>
<div class="check"><span class="state unknown">HIT</span><b><span data-l="en">Baseline and regime</span><span data-l="zh">基线与工况</span></b> — <span data-l="en">Any speed comparison has to run against the shipped ReleaseFast build, not a Debug one. No published profile of Ghostty's frame time exists in either direction, so D2 is UNKNOWN rather than settled.</span><span data-l="zh">任何速度对比都要跟实际发布的 ReleaseFast 构建比,不能拿 Debug 版当基线。Ghostty 的帧时间没有任何公开 profile,两个方向都没有,所以 D2 是 UNKNOWN,不是已经有答案。</span></div>
<div class="check"><span class="state unknown">HIT</span><b><span data-l="en">Delivery ownership</span><span data-l="zh">交付归属</span></b> — <span data-l="en">68.8% of the 16,557 default-branch commits come from one author, and 97,412 lines of inline Zig tests do not port. Review capacity bounds any rewrite before engineering does.</span><span data-l="zh">默认分支 16,557 次提交里,68.8% 出自一位作者;另有 97,412 行内联 Zig 测试搬不过去。约束任何重写的第一个瓶颈是评审带宽,不是工程量。</span></div>
<div class="check"><span class="state pass">PASS</span><b><span data-l="en">Boundary and compatibility</span><span data-l="zh">边界与兼容</span></b> — <span data-l="en">The 275-symbol C ABI is named and priced against the full options, and the extraction option is retained precisely because it inherits that seam instead of breaking it.</span><span data-l="zh">275 个符号的 C ABI 被点名,并计入整体方案的成本;抽取方案之所以保留,正是因为它继承这条接缝而不是打断它。</span></div>
</div>
<div class="challenge-col"><h3><span data-l="en">Against staying</span><span data-l="zh">反驳「不动」</span></h3>
<div class="check"><span class="state pass">PASS</span><b><span data-l="en">Funded counterfactual</span><span data-l="zh">有人投入的对照方案</span></b> — <span data-l="en">Neither in-stack option is hypothetical. The ReleaseSafe build is codesigned and published on every commit to main, and AFL++ covers three VT targets over 4,001 committed corpus files.</span><span data-l="zh">栈内这两条路都不是假想。ReleaseSafe 构建每次 main 提交都会签名并发布;AFL++ 覆盖三个 VT 目标,语料库入库 4,001 个文件。</span></div>
<div class="check"><span class="state unknown">HIT</span><b><span data-l="en">Cost of inaction</span><span data-l="zh">不动的代价</span></b> — <span data-l="en">The tagged release parses hostile VT input with bounds and overflow checks off. PACKAGING.md says the project would rather not. Staying without pricing the safe build leaves that exactly as it is.</span><span data-l="zh">正式发布版在关掉边界和溢出检查的状态下解析敌意 VT 输入。PACKAGING.md 里项目自己说了并不情愿。不算安全构建的代价就继续待着,这个状态一点不变。</span></div>
<div class="check"><span class="state pass">PASS</span><b><span data-l="en">Unsafe-surface omission</span><span data-l="zh">遗漏的不安全面</span></b> — <span data-l="en">The C++ is counted, not hidden behind a memory-safe framing. 995 lines of Ghostty's own plus 52,566 vendored lines of simdutf compile into the binary under every option here.</span><span data-l="zh">C++ 是被计入的,没有藏在「应用是内存安全的」这种说法后面。Ghostty 自有 995 行加入库的 52,566 行 simdutf,在这里每个方案下都会编进二进制。</span></div>
<div class="check"><span class="state pass">PASS</span><b><span data-l="en">Native-advantage denial</span><span data-l="zh">否认原生优势</span></b> — <span data-l="en">The report never claims Rust would be slower or that the extraction is a bad idea. D2 is UNKNOWN, and the path names the benchmark that would settle it.</span><span data-l="zh">报告没有主张 Rust 会更慢,也没说抽取方案是个坏主意。D2 记为 UNKNOWN,路径里点名了能了结它的那次基准测试。</span></div>
<div class="check"><span class="state unknown">HIT</span><b><span data-l="en">Toolchain risk dismissal</span><span data-l="zh">轻视工具链风险</span></b> — <span data-l="en">Ghostty pins a pre-1.0 compiler. Breaking language changes between Zig minor versions are a standing cost of staying, and no measurement in this report removes it.</span><span data-l="zh">Ghostty 钉住的是一个 1.0 之前的编译器。Zig 小版本之间的破坏性语言变更是「不动」要长期付的钱,本报告里没有任何测量能把它抹掉。</span></div>
</div>
</div>
</section>
<section>
<h2 class="section-title"><span class="no">08</span><span data-l="en">How this was produced</span><span data-l="zh">方法与记录</span></h2>
<div class="method"><h3><span data-l="en">ghostty-org/ghostty at 46edeee · static read-only analysis · why-not-rust method 2.0</span><span data-l="zh">ghostty-org/ghostty @ 46edeee · 静态只读分析 · why-not-rust 方法 2.0</span></h3><p><span data-l="en">Repository: github.com/ghostty-org/ghostty at commit 46edeee407ff1cd15fb7db3837025386b2f3a327, shallow clone, 5,815 tracked files. Scope: the whole repository, with src/terminal behind the libghostty C ABI as the candidate seam. Sampling: 755 tracked .zig files hold 311,038 lines, of which 97,412 sit inside test blocks, leaving 213,626. src/terminal is 140,441 lines across 141 files, 74,160 of them inside test blocks. macos/Sources holds 160 .swift files and 32,377 lines; 67 import AppKit, 63 import SwiftUI, 36 import Cocoa. macos/Tests and macos/GhosttyUITests add 2,201 lines. src/apprt is 28,408 lines, 79 of its files under gtk/. Ten C++ translation units are tracked: four are Ghostty's own in src/simd at 995 lines, one is simdutf's vendored amalgamation at 42,510 lines (52,566 with its header), three are Highway shims totalling 240 lines, and the remaining two are an imgui binding and an example. Google Highway's own source is fetched at build time, not vendored, so it is named and not counted. The include/ tree is 11,353 lines across 34 files. Exported C symbols: 90 unique 'export fn ghostty_*' names in src/ (71 in src/apprt/embedded.zig, 13 in src/config/CApi.zig, 5 in src/main_c.zig, 1 in src/benchmark/CApi.zig) plus 185 unique '@export' names in src/lib_vt.zig, with zero overlap between the two sets, for 275 total. Zig source carries 3,524 inline test blocks, 2,579 of them in src/terminal. test/fuzz-libghostty ships three AFL++ harnesses and 4,001 committed corpus files. Build flags were read from the workflows: .github/workflows/release-tag.yml:177 builds the tagged macOS release ReleaseFast, while .github/workflows/release-tip.yml:929 builds a ReleaseSafe macOS app that is then codesigned, notarized and published as ghostty-macos-universal-debug-fast.zip. PACKAGING.md:111 states the reason. build.zig.zon:6 pins minimum_zig_version 0.16.0. GitHub API: 5 published security advisories (CWE-78, CWE-94, CWE-284, plus two without a CWE), none memory-safety; 12 version tags from v1.0.0 on 2024-12-26 to v1.3.1 on 2026-03-13; 425 contributors listed with 16,557 contributions, the top author holding 11,397. No build, test, benchmark or run was performed against the project. Objective: no specific RFC was supplied, so the assessment takes the commonly argued proposal that a terminal parsing untrusted input should be Rust rather than Zig. User-supplied facts: none. No Amdahl calculation appears: D2 is UNKNOWN because no published profile locates Ghostty's frame time, and a line share is not a time share. The decision turns on G3 and G4. G3 fails because the smaller option is a build flag whose cost has never been published, and G4 fails on 275 exported C symbols across two frontends written in other languages. This is a structured decision protocol, not a statistical predictor.</span><span data-l="zh">仓库:github.com/ghostty-org/ghostty,commit 46edeee407ff1cd15fb7db3837025386b2f3a327,shallow clone,5,815 个纳管文件。范围:整个仓库,候选接缝是 libghostty C ABI 后面的 src/terminal。采样:755 个纳管 .zig 文件共 311,038 行,其中 97,412 行在 test 块内,余 213,626 行。src/terminal 是 141 个文件、140,441 行,其中 74,160 行在 test 块内。macos/Sources 有 160 个 .swift 文件、32,377 行;67 个 import AppKit,63 个 import SwiftUI,36 个 import Cocoa。macos/Tests 与 macos/GhosttyUITests 另有 2,201 行。src/apprt 是 28,408 行,其中 79 个文件在 gtk/ 下。纳管的 C++ 翻译单元共十个:四个是 Ghostty 自有的,在 src/simd,995 行;一个是 simdutf 入库的合并源码,42,510 行(含头文件 52,566 行);三个是 Highway 的胶水层,合计 240 行;剩下两个是 imgui 绑定和一个示例。Google Highway 自身的源码在构建时才拉,没有入库,所以只点名不计数。include/ 树是 34 个文件、11,353 行。导出 C 符号:src/ 里 90 个不重复的 'export fn ghostty_*'(src/apprt/embedded.zig 71 个,src/config/CApi.zig 13 个,src/main_c.zig 5 个,src/benchmark/CApi.zig 1 个),加 src/lib_vt.zig 里 185 个不重复的 '@export',两组之间零重叠,合计 275 个。Zig 源码里有 3,524 个内联 test 块,其中 2,579 个在 src/terminal。test/fuzz-libghostty 提供三个 AFL++ harness 和 4,001 个入库语料文件。构建开关取自 workflow:.github/workflows/release-tag.yml:177 用 ReleaseFast 构建正式的 macOS 发布版,而 .github/workflows/release-tip.yml:929 构建的是 ReleaseSafe 的 macOS 应用,随后签名、公证,并以 ghostty-macos-universal-debug-fast.zip 发布。PACKAGING.md:111 写了原因。build.zig.zon:6 把 minimum_zig_version 钉在 0.16.0。GitHub API:5 条公开安全公告(CWE-78、CWE-94、CWE-284,另两条没有 CWE),无一属于内存安全;12 个版本 tag,从 2024-12-26 的 v1.0.0 到 2026-03-13 的 v1.3.1;列出 425 位贡献者、16,557 次贡献,头号作者占 11,397 次。没有对项目做过任何构建、测试、基准或运行。目标:没有人给出具体 RFC,因此按常见的那个说法评估——解析不可信输入的终端应该用 Rust 而不是 Zig。用户提供的事实:无。本报告没有 Amdahl 计算:D2 是 UNKNOWN,因为没有公开 profile 定位 Ghostty 的帧时间,而代码行数占比不等于时间占比。决策落在 G3 和 G4。G3 失败,是因为更小的那个方案只是一个构建开关,而它的代价从来没被公布过;G4 失败,是因为 275 个导出 C 符号背后是两套用别的语言写的前端。这是一套结构化决策流程,不是统计预测器。</span></p>
<div class="table-wrap"><table>
<thead><tr><th><span data-l="en">What we don't know</span><span data-l="zh">证据缺口</span></th><th><span data-l="en">What it would change</span><span data-l="zh">会改变什么</span></th></tr></thead>
<tbody><tr><td><span data-l="en">A published ReleaseFast-versus-ReleaseSafe measurement on src/benchmark's VT harnesses</span><span data-l="zh">在 src/benchmark 的 VT harness 上公布 ReleaseFast 与 ReleaseSafe 的对比</span></td><td><span data-l="en">This is the number the whole decision turns on. PACKAGING.md calls the safe build too slow; nothing published supports or refutes that. The harnesses are already in the tree.</span><span data-l="zh">整个决策就压在这个数字上。PACKAGING.md 说安全构建太慢,但没有任何公开材料支持或否证它。harness 本来就在仓库里。</span></td></tr><tr><td><span data-l="en">A classification of AFL++ findings in src/terminal into spatial, temporal and logic</span><span data-l="zh">把 src/terminal 里 AFL++ 的发现分成空间类、时间类、逻辑类</span></td><td><span data-l="en">Without it the Rust extraction rests on a mechanism rather than a realized defect. It would also tell the safe build how much of the class it actually covers.</span><span data-l="zh">没有它,Rust 抽取方案只站在机制上,站不到已发生的缺陷上。它同时也能告诉安全构建:这个缺陷类里它真正覆盖了多少。</span></td></tr><tr><td><span data-l="en">Any published profile of where Ghostty's frame time goes</span><span data-l="zh">任何一份关于 Ghostty 帧时间去向的公开 profile</span></td><td><span data-l="en">D2 stays UNKNOWN while it is missing, so no performance claim about any option here can be authorized in either direction.</span><span data-l="zh">它缺着的时候 D2 就停在 UNKNOWN,因此这里任何方案的性能主张,往哪个方向都授权不了。</span></td></tr></tbody>
</table></div>
</div>
</section>
<footer><span>why-not-rust · method 2.0 · <span data-l="en">public repository · static analysis at commit 46edeee · GitHub API for release and advisory history · no build, benchmark or run</span><span data-l="zh">公开仓库 · 在 commit 46edeee 上做静态分析 · 发布与公告历史取自 GitHub API · 没有构建、基准或运行</span></span><span class="mono">github.com/xiaotonng/why-not-rust</span></footer>
</div>
<script type="application/json" id="why-not-rust-assessment">
{
"method": "why-not-rust/2.0",
"generated_at": "2026-08-02",
"repository": {
"path": "https://github.com/ghostty-org/ghostty",
"commit": "46edeee407ff1cd15fb7db3837025386b2f3a327",
"scope": "whole repository; src/terminal behind the libghostty C ABI is the candidate seam",
"sampling": "shallow clone, 5,815 tracked files enumerated; src/, macos/, pkg/, include/, test/fuzz-libghostty/ and .github/workflows/ measured; release, tag and advisory history read through the GitHub API because the clone is --depth 1; no build, benchmark or run"
},
"analysis": {
"mode": "public-repository static analysis; no build, benchmark, or network run against the target",
"user_supplied_facts": [],
"evidence_gaps": [
"A published ReleaseFast-versus-ReleaseSafe measurement on src/benchmark's VT harnesses",
"A classification of AFL++ findings in src/terminal into spatial, temporal and logic",
"Any published profile of where Ghostty's frame time goes"
]
},
"objective": {
"driver": "memory safety",
"requirement": "remove or downgrade the memory-unsafety class on Ghostty's VT parsing path, without giving up the native macOS behaviour or the libghostty C ABI",
"baseline": "the tagged release is built -Doptimize=ReleaseFast, which disables Zig's bounds, overflow and null checks; a ReleaseSafe macOS build is already codesigned, notarized and published on the tip channel with no measured cost",
"target": "class elimination or safe-failure on the VT path; no proposal assessed here states a performance target"
},
"decision": {
"authorization": "REJECT",
"scope": "STAY",
"selected_option_id": "stay-safe-build",
"confidence": "MEDIUM",
"robustness": "CONDITIONAL",
"because": "Ghostty ships its tagged release with Zig's safety checks off, and PACKAGING.md says the maintainer would rather not. So the memory-safety requirement stands. What fails is the price. A safety-checked macOS build is already codesigned, notarized and published on the tip channel every commit to main. Nobody has published what it costs, and src/benchmark/ already holds the harnesses that would say. Meanwhile the quality people praise Ghostty for is 32,377 lines of Swift, and all five of its published advisories are logic bugs Rust would not have caught.",
"change_trigger": "Conditional on two measurements that do not exist. Publish the ReleaseFast-versus-ReleaseSafe cost on src/benchmark's VT harnesses, and classify one release cycle of AFL++ crashes into spatial, temporal and logic buckets. If the safe build turns out unaffordable and the temporal bucket is not empty, the VT extraction behind the existing 185-symbol C ABI becomes the live question."
},
"gates": [
{
"id": "G1",
"option_id": "stay-safe-build",
"name": "requirement",
"state": "PASS",
"evidence": "The tagged release is built -Doptimize=ReleaseFast (.github/workflows/release-tag.yml:177), which disables Zig's bounds, overflow and null checks. PACKAGING.md:111 states the preference for a safe build and calls the safe build too slow. src/terminal is 140,441 lines of Zig terminating VT sequences from arbitrary child processes. The requirement is stated by the project itself."
},
{
"id": "G2",
"option_id": "stay-safe-build",
"name": "rust-specific causality",
"state": "PASS",
"evidence": "Zig's ReleaseSafe restores spatial checks but has no ownership model, so use-after-free and double-free survive it. Rust's safe subset removes both classes from code it replaces. Bun ported roughly 535k lines of Zig to Rust and named exactly that stream of bugs as the motive. The mechanism holds. Its reach does not extend to the 32,377-line Swift frontend or to the 995 lines of C++."
},
{
"id": "G3",
"option_id": "stay-safe-build",
"name": "economics and smallest sufficient option",
"state": "FAIL",
"evidence": ".github/workflows/release-tip.yml:929 builds the macOS app with -Doptimize=ReleaseSafe, then codesigns, notarizes and publishes it as ghostty-macos-universal-debug-fast.zip. src/benchmark/ ships 15 harnesses including TerminalParser, TerminalStream and OscParser behind -Demit-bench. No published measurement compares the two modes. A 213,626-line non-test rewrite cannot be the smallest sufficient step while a build flag is unpriced."
},
{
"id": "G4",
"option_id": "stay-safe-build",
"name": "delivery and reversibility",
"state": "FAIL",
"evidence": "90 export fn ghostty_* symbols plus 185 @export names in src/lib_vt.zig, with no overlap, make 275 exported C symbols. A 32,377-line Swift app and a 28,408-line GTK frontend compile against them. 97,412 lines sit inside Zig test blocks and do not port. 68.8% of 16,557 commits on the default branch come from one person. No dual-run or rollback plan exists for any of it."
}
],
"options": [
{
"id": "stay-safe-build",
"name": "Price ReleaseSafe, then ship it",
"implementation": "current",
"scope": "stay",
"target": "remove or downgrade the memory-unsafety class on Ghostty's VT parsing path, without giving up the native macOS behaviour or the libghostty C ABI",
"benefit_interval": "spatial class becomes a deterministic panic across all 213,626 lines",
"one_time_cost": "one benchmark run on existing harnesses; one build-flag change",
"recurring_cost": "whatever throughput the safe build costs, once measured",
"time_to_value": "days",
"compatibility": "identical ABI and behaviour; failure mode changes from corruption to panic",
"reversibility": "revert one build flag",
"evidence_strength": "MODERATE",
"disposition": "selected",
"reason": "Reaches the whole Zig surface for the cost of a measurement, and the artifact it produces already exists in CI. Its cost is the one number nobody has published."
},
{
"id": "stay-fuzz",
"name": "Keep the AFL++ programme as it is",
"implementation": "current",
"scope": "stay",
"target": "remove or downgrade the memory-unsafety class on Ghostty's VT parsing path, without giving up the native macOS behaviour or the libghostty C ABI",
"benefit_interval": "finds defects in the VT path; eliminates no class",
"one_time_cost": "none new; three harnesses already exist",
"recurring_cost": "existing maintainer fuzzing and triage time",
"time_to_value": "continuous",
"compatibility": "native",
"reversibility": "n/a",
"evidence_strength": "MODERATE",
"disposition": "retain",
"reason": "Three AFL++ targets over 4,001 committed corpus files plus 3,524 inline test blocks are shipping work, not a hypothetical. Fuzzing finds bugs; it does not remove the class."
},
{
"id": "rust-vt-extract",
"name": "Rust VT engine behind the existing C ABI",
"implementation": "rust",
"scope": "extract",
"target": "remove or downgrade the memory-unsafety class on Ghostty's VT parsing path, without giving up the native macOS behaviour or the libghostty C ABI",
"benefit_interval": "both classes removed on the VT path; frontends unchanged",
"one_time_cost": "66,281 non-test lines of src/terminal plus 2,579 test blocks to re-express",
"recurring_cost": "a Rust toolchain in a build that currently needs only Zig",
"time_to_value": "months to years",
"compatibility": "185 libghostty-vt symbols must stay byte-compatible",
"reversibility": "keep both engines behind the ABI",
"evidence_strength": "WEAK",
"disposition": "retain",
"reason": "The seam is real and was designed as a C ABI. The benefit is unquantified because nobody has classified what AFL++ actually finds in src/terminal."
},
{
"id": "rust-core",
"name": "Rewrite the Zig core in Rust, keep Swift",
"implementation": "rust",
"scope": "full",
"target": "remove or downgrade the memory-unsafety class on Ghostty's VT parsing path, without giving up the native macOS behaviour or the libghostty C ABI",
"benefit_interval": "both classes removed in Zig; Swift, C++ and simdutf untouched",
"one_time_cost": "213,626 non-test lines, plus 97,412 lines of Zig tests that do not port",
"recurring_cost": "re-owning the build graph for 22 pkg/ C dependency wrappers",
"time_to_value": "years",
"compatibility": "275 exported C symbols, a Swift app and a GTK frontend",
"reversibility": "none",
"evidence_strength": "WEAK",
"disposition": "exclude",
"reason": "Pays a full rewrite for a class that a build flag downgrades and a smaller extraction removes outright at the place it matters most."
},
{
"id": "rust-everything",
"name": "Rewrite the whole app in Rust, UI included",
"implementation": "rust",
"scope": "full",
"target": "remove or downgrade the memory-unsafety class on Ghostty's VT parsing path, without giving up the native macOS behaviour or the libghostty C ABI",
"benefit_interval": "one language; loses the AppKit and SwiftUI behaviour",
"one_time_cost": "213,626 lines of Zig plus 32,377 lines of Swift and a GUI framework",
"recurring_cost": "owning a macOS GUI layer that Apple's frameworks currently own",
"time_to_value": "years",
"compatibility": "AppleScript, App Intents, native tabs and four titlebar styles",
"reversibility": "none",
"evidence_strength": "WEAK",
"disposition": "exclude",
"reason": "Zed had to build GPUI because no Rust GUI framework met its bar. Ghostty avoided that problem by writing the UI in the platform's own language."
},
{
"id": "adopt-rust-terminal",
"name": "Use a terminal already written in Rust",
"implementation": "external",
"scope": "adopt",
"target": "remove or downgrade the memory-unsafety class on Ghostty's VT parsing path, without giving up the native macOS behaviour or the libghostty C ABI",
"benefit_interval": "class removed for that user; a different product",
"one_time_cost": "per-user config migration",
"recurring_cost": "a different feature and platform-integration surface",
"time_to_value": "an afternoon",
"compatibility": "no shared config, keybinds or platform integration",
"reversibility": "reinstall",
"evidence_strength": "MODERATE",
"disposition": "retain",
"reason": "Alacritty and WezTerm exist and are fine terminals. Neither is a plan for Ghostty's own code, and Alacritty's speed superlative failed independent audit."
}
],
"lenses": [
{
"id": "D1",
"name": "Requirement \u0026 ownership",
"option_ids": [
"stay-safe-build",
"rust-vt-extract",
"rust-core"
],
"state": "SUPPORTS",
"strength": "STRONG",
"claim": "The unsafe surface is owned and easy to point at. src/terminal is 140,441 lines of Zig, and it terminates VT sequences coming from whatever the user runs. The tagged release builds all of it with safety checks off. The project wrote that down itself.",
"source": "PACKAGING.md:111 · .github/workflows/release-tag.yml:177 · src/terminal 140,441 lines across 141 files",
"baseline_regime": "static inventory at commit 46edeee; build flags read from the release workflows",
"caveat": "66,281 of those 140,441 lines fall outside test blocks; the rest are inline Zig tests that ship with the source.",
"change_trigger": "A tagged release built ReleaseSafe would close the spatial half of this requirement without any language change."
},
{
"id": "D2",
"name": "End-to-end reach",
"option_ids": [
"rust-vt-extract",
"rust-core"
],
"state": "UNKNOWN",
"strength": "UNKNOWN",
"claim": "No Amdahl figure appears here. Nobody has published where Ghostty's frame time goes, so there is no f worth defending. src/benchmark/ holds 15 harnesses that could produce one. Using a line share as a stand-in for a time share would be a method error.",
"source": "src/benchmark/ · 18 files including TerminalParser, TerminalStream, OscParser, CodepointWidth",
"baseline_regime": "n/a — the measurement exists as code but no result is published",
"caveat": "No proposal assessed here states a performance target either, so there is nothing to test a ceiling against.",
"change_trigger": "A published run of -Demit-bench at ReleaseFast, split by harness, would make both the safe-build and extraction questions quantitative."
},
{
"id": "D3",
"name": "Tail \u0026 runtime",
"option_ids": [],
"state": "NEUTRAL",
"strength": "MODERATE",
"claim": "Neither Zig nor Rust has a collector or a JIT, so no option removes a runtime mechanism from the frame budget. There is none to remove. Rendering goes through Metal on macOS and OpenGL elsewhere, and that driver time is shared by every option on the table.",
"source": "src/renderer/Metal.zig · src/renderer/OpenGL.zig · src/renderer/shaders/shaders.metal",
"baseline_regime": "shipped renderer backends at this commit",
"caveat": "Allocator behaviour differs between a Zig arena style and a Rust equivalent, but no trace here separates that from GPU and compositor time.",
"change_trigger": ""
},
{
"id": "D4",
"name": "Fleet footprint",
"option_ids": [],
"state": "N/A",
"strength": "MODERATE",
"claim": "Ghostty is a desktop app. There is no instance count, no utilization figure and no price per hour to multiply. Per-window memory is dominated by scrollback, which the project already compresses.",
"source": "src/terminal/compress/ · desktop distribution",
"baseline_regime": "n/a",
"caveat": "",
"change_trigger": ""
},
{
"id": "D5",
"name": "Startup shape",
"option_ids": [],
"state": "N/A",
"strength": "MODERATE",
"claim": "The Mac app launches once per session and opens new windows in-process. Launch time is AppKit's, and no core-language change touches it. CLI actions exist under src/cli, but they are not the product.",
"source": "macos/Sources/App/ · src/cli/ 27 files",
"baseline_regime": "n/a",
"caveat": "A libghostty consumer embedding the VT engine in a short-lived process would score this lens differently.",
"change_trigger": ""
},
{
"id": "D6",
"name": "Safety \u0026 correctness",
"option_ids": [
"rust-vt-extract",
"rust-core",
"rust-everything"
],
"state": "SUPPORTS",
"strength": "MODERATE",
"claim": "Zig's ReleaseSafe restores bounds, overflow and null checks. It has no ownership model, so use-after-free and double-free live through it. Rust's safe subset removes both classes from code it replaces. Bun's team ported roughly 535k lines of Zig and named that exact stream of bugs as the reason.",
"source": "PACKAGING.md:111 · https://bun.com/blog/bun-in-rust · GHSA advisory list (5 entries)",
"baseline_regime": "structural, at the VT input boundary; Bun figures are first-party and selected",
"caveat": "Classified against D6's taxonomy, all five of Ghostty's published advisories are language-independent: CWE-78, CWE-94, CWE-284, an fd leak and an escalation vector. None is eliminated-by-construction by any option here.",
"change_trigger": "AFL++ crashes in src/terminal classified as temporal-class would move this from mechanism to realized defect."
},
{
"id": "D7",
"name": "Concurrency \u0026 invariants",
"option_ids": [],
"state": "NEUTRAL",
"strength": "MODERATE",
"claim": "Ghostty already splits render, terminal I/O and app work across threads, and the boundaries are few and coarse. No race incident appears in the advisory history. fish shell called Send and Sync the killer feature of its own port, so this lens could move — but it would need an incident, not an argument.",
"source": "src/renderer/Thread.zig · src/termio/ 9 files · https://fishshell.com/blog/rustport/",
"baseline_regime": "existing thread design at this commit",
"caveat": "Absence of a published race incident is weak evidence in both directions; nobody has audited Ghostty's thread boundaries in public.",
"change_trigger": ""
},
{
"id": "D8",
"name": "Distribution \u0026 embedding",
"option_ids": [],
"state": "NEUTRAL",
"strength": "MODERATE",
"claim": "Nothing about distribution is unmet. Ghostty ships a notarized universal macOS binary with a Sparkle update channel, a GTK app on Linux, plus flatpak and snap. libghostty-vt builds for Windows and for WebAssembly without libc. Rust could reach all of those too.",
"source": "flatpak/ · snap/ · dist/ · include/ghostty/vt/wasm.h · .github/workflows/release-tag.yml",
"baseline_regime": "shipped distribution channels at this commit",
"caveat": "The Zig build compiles the C dependencies itself through 22 pkg/ wrappers; reproducing that build graph is priced at D11, not here.",
"change_trigger": ""
},
{
"id": "D9",
"name": "Ecosystem \u0026 alternatives",
"option_ids": [
"rust-vt-extract",
"rust-core",
"rust-everything"
],
"state": "SUPPORTS",
"strength": "MODERATE",
"claim": "Zig has not reached 1.0. build.zig.zon pins minimum_zig_version 0.16.0, and breaking compiler changes between minor versions are the normal cost of that. Rust has kept its language stable since 2015. This is the strongest point against staying, and it has nothing to do with speed.",
"source": "build.zig.zon:6 · pkg/macos 3,273 lines of Zig bindings to Apple frameworks across 56 files",
"baseline_regime": "toolchain state at this commit",
"caveat": "Cutting the other way: Zed had to build GPUI because no Rust GUI framework met its bar, and Ghostty's UI problem is solved in Swift instead. Ghostty also hand-wrote its Apple framework bindings, which a port would re-own.",
"change_trigger": "A Zig 1.0 release with a stability guarantee would remove this lens's support for the Rust options."
},
{
"id": "D10",
"name": "Boundary \u0026 compatibility",
"option_ids": [
"rust-core",
"rust-everything"
],
"state": "DISFAVORS",
"strength": "STRONG",
"claim": "The compatibility surface is 275 exported C symbols across two APIs, plus 11,353 lines of headers under include/. A 32,377-line Swift app and a 28,408-line GTK frontend compile against them. The extraction option inherits that seam for free, which is the whole reason it survives.",
"source": "90 export fn ghostty_* in src/ (71 in src/apprt/embedded.zig) · 185 @export names in src/lib_vt.zig · include/ 11,353 lines across 34 files",
"baseline_regime": "static contract inventory at this commit; the two symbol sets do not overlap",
"caveat": "libghostty-vt's API signatures are described as still in flux in README.md, so the 185 are less frozen than the 90 the Mac app depends on.",
"change_trigger": ""
},
{
"id": "D11",
"name": "Delivery economics",
"option_ids": [
"rust-core",
"rust-everything"
],
"state": "DISFAVORS",
"strength": "MODERATE",
"claim": "213,626 non-test lines of Zig, and another 97,412 inside test blocks that do not port. One person authored 68.8% of the 16,557 commits on the default branch. fish shell estimated half a year for 57k lines of C++ and took about two. Ghostty's non-test Zig is 3.7 times that.",
"source": "gh api repos/ghostty-org/ghostty/contributors --paginate · 425 contributors listed, 16,557 contributions, top author 11,397 · https://fishshell.com/blog/rustport/",
"baseline_regime": "default-branch commit counts from the GitHub API; the contributors endpoint is capped and counts the default branch only",
"caveat": "Single-author concentration is a risk for staying as well; it is recorded here because it bounds how fast any rewrite could be reviewed.",
"change_trigger": ""
},
{
"id": "D12",
"name": "Counterfactual",
"option_ids": [
"stay-safe-build",
"stay-fuzz"
],
"state": "SUPPORTS",
"strength": "STRONG",
"claim": "Two in-stack options are already running. A ReleaseSafe macOS build is codesigned, notarized and published on the tip channel on every commit to main, with its own Sentry dSYM upload. AFL++ covers three VT targets over 4,001 committed corpus files. A Rust proposal has to beat both on one measurement.",
"source": ".github/workflows/release-tip.yml:929, 1036, 1044 · test/fuzz-libghostty/ · 3 harnesses, 4,001 corpus files · 3,524 inline test blocks in src/",
"baseline_regime": "current upstream CI and test practice at this commit",
"caveat": "Neither removes the temporal class. ReleaseSafe downgrades spatial defects to panics; fuzzing raises detection. Elimination is what the Rust options offer and these do not.",
"change_trigger": ""
}
],
"math": {
"amdahl": null,
"break_even": null
},
"precedents": [
{
"name": "Bun · Zig → Rust port",
"outcome": "MIGRATED",
"match": "the same source language, the same defect class, and a codebase of comparable order",
"mismatch": "a JS runtime with no GUI layer and no C ABI that third-party frontends compile against",
"workload_regime": "selected first-party benchmarks; production readiness contested at publication",
"source_class": "first-party · engineering blog",
"url": "https://bun.com/blog/bun-in-rust"
},
{
"name": "Alacritty · the \"fastest terminal\" claim",
"outcome": "CLAIM FAILED AUDIT",
"match": "the same archetype and the same domain: a native terminal emulator written in Rust",
"mismatch": "a claim audit rather than a migration; Ghostty's proposals here are about safety, not speed",
"workload_regime": "third-party latency measurement; no first-party harness published",
"source_class": "third-party · measured",
"url": "https://danluu.com/term-latency/"
},
{
"name": "Zed · GPUI",
"outcome": "MIGRATED",
"match": "a native desktop GUI in Rust, and the direct precedent for what rust-everything would owe",
"mismatch": "greenfield rather than a migration, and Zed never had a Swift layer to give up",
"workload_regime": "third-party unaudited timings; first-party framerate claims",
"source_class": "first-party claim · third-party timings",
"url": "https://zed.dev/blog/videogame"
},
{
"name": "fish shell 4.0 · C++ → Rust",
"outcome": "MIGRATED",
"match": "a terminal-adjacent tool, a manual-memory source language, and a safety-and-maintainability motive rather than a speed one",
"mismatch": "57k lines against Ghostty's 213,626 non-test, and no C ABI or GUI frontend to keep compiling",
"workload_regime": "first-party migration retrospective with stated method",
"source_class": "first-party · engineering blog",
"url": "https://fishshell.com/blog/rustport/"
},
{
"name": "Microsoft · VS Code text buffer",
"outcome": "STAYED",
"match": "the boundary-cost test that separates rust-vt-extract from rust-core here",
"mismatch": "a managed-runtime host crossing into native code; Ghostty's boundary is already a C ABI",
"workload_regime": "first-party engineering retrospective",
"source_class": "first-party · engineering blog",
"url": "https://code.visualstudio.com/blogs/2018/03/23/text-buffer-reimplementation"
}
],
"path": [
{
"step": 1,
"title": "Publish what the safe build costs",
"owner": "whoever argues Ghostty needs a memory-safe rewrite",
"cost_range": "1 week",
"artifact": "a published ReleaseFast-versus-ReleaseSafe comparison on src/benchmark's TerminalStream, TerminalParser and OscParser harnesses, same corpus and same machine",
"acceptance": "both modes are reported per harness as a percentage delta, and a third party can re-run the harness from the tree",
"deadline_or_stop": "if ReleaseSafe lands inside the frame budget, proceed to step 2 and close the spatial half of the requirement",
"rollback": "measurement only; no code changes"
},
{
"step": 2,
"title": "Make ReleaseSafe the tagged default if it fits",
"owner": "Ghostty maintainers",
"cost_range": "days",
"artifact": "a one-flag change to the tagged release workflow, plus one release cycle of crash-rate comparison against the tip ReleaseSafe channel",
"acceptance": "the published delta stays inside a stated performance budget and the crash rate does not regress over one release cycle",
"deadline_or_stop": "if the measured cost exceeds the stated budget, keep ReleaseFast and escalate to step 3",
"rollback": "revert one build flag"
},
{
"step": 3,
"title": "Classify what AFL++ actually finds in src/terminal",
"owner": "Ghostty maintainers",
"cost_range": "2–4 weeks",
"artifact": "a classification of one release cycle of AFL++ crashes into spatial, temporal and logic buckets, with counts",
"acceptance": "every crash found in the window is classified and counted, and the corpus and harness versions are named",
"deadline_or_stop": "close the extraction track if the temporal bucket is empty across a full release cycle",
"rollback": "analysis only; no code changes"
},
{
"step": 4,
"title": "Reopen the extraction only with a plan for 275 symbols",
"owner": "whoever proposes the Rust VT engine",
"cost_range": "2 weeks per review",
"artifact": "a written parity and dual-run plan covering all 275 exported C symbols and both frontends",
"acceptance": "the plan names the symbols whose signatures change, the owner of Swift-side and GTK-side breakage, and the dual-run comparison method",
"deadline_or_stop": "no extraction proceeds without the document",
"rollback": "the current Zig implementation continues unchanged"
}
],
"assumptions": [
"The shallow clone at commit 46edeee represents the shipped tree; vendored source under pkg/*/vendor is counted as it ships, and dependencies fetched at build time are named but not counted.",
"Test-block line counts come from an awk pass that opens on a line matching /^test[ \"{]/ and closes at the next line equal to '}', which relies on zig fmt keeping top-level closing braces in column 0; the figure is approximate and slightly conservative.",
"The exported C symbol count treats each unique 'export fn ghostty_*' name in src/ and each unique '@export' name in src/lib_vt.zig as one entry point; the two sets were checked for overlap and share none.",
"Commit-share figures come from the GitHub contributors endpoint, which is capped and counts the default branch only.",
"No specific migration RFC was supplied, so the assessment takes the commonly argued proposal: that a terminal emulator parsing untrusted input should be written in Rust rather than Zig."
],
"challenge_audit": {
"migration_case": [
{
"id": "M1",
"name": "Attribution",
"state": "HIT",
"claim": "The quality Ghostty is praised for lives in 32,377 lines of Swift, and all five published advisories are language-independent. Only an unrealized class attaches to the core language.",
"evidence": "macos/Sources 32,377 lines · GHSA list, 5 entries, 0 memory-safety"
},
{
"id": "M2",
"name": "Omitted cost",
"state": "HIT",
"claim": "The full options leave 995 lines of Ghostty's own C++ and 52,566 vendored lines of simdutf in place, and they re-own a build graph that currently compiles C dependencies through 22 pkg/ wrappers.",
"evidence": "src/simd/*.cpp 995 lines · pkg/simdutf/vendor 52,566 lines · 22 pkg/ directories"
},
{
"id": "M3",
"name": "Baseline and regime",
"state": "HIT",
"claim": "Any speed comparison has to run against the shipped ReleaseFast build, not a Debug one. No published profile of Ghostty's frame time exists in either direction, so D2 is UNKNOWN rather than settled.",
"evidence": ".github/workflows/release-tag.yml:177 · src/benchmark/ 15 harnesses, no published result"
},
{
"id": "M4",
"name": "Delivery ownership",
"state": "HIT",
"claim": "68.8% of the 16,557 default-branch commits come from one author, and 97,412 lines of inline Zig tests do not port. Review capacity bounds any rewrite before engineering does.",
"evidence": "gh api contributors · 425 listed, top author 11,397 of 16,557"
},
{
"id": "M5",
"name": "Boundary and compatibility",
"state": "PASS",
"claim": "The 275-symbol C ABI is named and priced against the full options, and the extraction option is retained precisely because it inherits that seam instead of breaking it.",
"evidence": "D10 · 90 export fn + 185 @export, no overlap"
}
],
"staying_case": [
{
"id": "S1",
"name": "Funded counterfactual",
"state": "PASS",
"claim": "Neither in-stack option is hypothetical. The ReleaseSafe build is codesigned and published on every commit to main, and AFL++ covers three VT targets over 4,001 committed corpus files.",
"evidence": ".github/workflows/release-tip.yml:929 · test/fuzz-libghostty/"
},
{
"id": "S2",
"name": "Cost of inaction",
"state": "HIT",
"claim": "The tagged release parses hostile VT input with bounds and overflow checks off. PACKAGING.md says the project would rather not. Staying without pricing the safe build leaves that exactly as it is.",
"evidence": "PACKAGING.md:111 · release-tag.yml:177"
},
{
"id": "S3",
"name": "Unsafe-surface omission",
"state": "PASS",
"claim": "The C++ is counted, not hidden behind a memory-safe framing. 995 lines of Ghostty's own plus 52,566 vendored lines of simdutf compile into the binary under every option here.",
"evidence": "src/simd/*.cpp · pkg/simdutf/vendor/simdutf.cpp 42,510 + .h 10,056"
},
{
"id": "S4",
"name": "Native-advantage denial",
"state": "PASS",
"claim": "The report never claims Rust would be slower or that the extraction is a bad idea. D2 is UNKNOWN, and the path names the benchmark that would settle it.",
"evidence": "D2 UNKNOWN · path step 1"
},
{
"id": "S5",
"name": "Toolchain risk dismissal",
"state": "HIT",
"claim": "Ghostty pins a pre-1.0 compiler. Breaking language changes between Zig minor versions are a standing cost of staying, and no measurement in this report removes it.",
"evidence": "build.zig.zon:6 · minimum_zig_version 0.16.0"
}
]
}
}
</script>
</body>
</html>