-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathLEARN_RUST.html
More file actions
1753 lines (1531 loc) · 94.6 KB
/
Copy pathLEARN_RUST.html
File metadata and controls
1753 lines (1531 loc) · 94.6 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
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Learn Rust — window-selector</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Bitter:wght@400;700;900&family=Source+Sans+3:ital,wght@0,400;0,600;0,700;1,400&family=Fira+Code:wght@400;600&display=swap" rel="stylesheet">
<style>
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
:root{
--bg:#0b0f14;--surface:#111820;--surface2:#161e2a;--surface3:#1c2636;
--border:#1e2a3a;--border-light:#2a3a50;
--text:#c8d0da;--text-muted:#6b7a8d;--text-dim:#3d4d60;
--accent:#e8762d;--accent-glow:#e8762d40;--accent-dim:#e8762d18;
--amber:#f0bf18;--amber-dim:#f0bf1820;
--rust:#b7410e;--rust-dim:#b7410e30;
--green:#3fb950;--blue:#58a6ff;--purple:#bc8cff;--cyan:#56d4dd;--pink:#f778ba;
--red:#ff7b72;--yellow:#e3b341;
--code-bg:#0d1219;--code-border:#1a2333;
--font-display:'Bitter',Georgia,serif;
--font-body:'Source Sans 3','Segoe UI',sans-serif;
--font-code:'Fira Code','Cascadia Code','JetBrains Mono',monospace;
--sidebar-w:280px;
--progress:0;
}
html{scroll-behavior:smooth;font-size:16px;scrollbar-color:var(--border) var(--bg)}
body{background:var(--bg);color:var(--text);font-family:var(--font-body);line-height:1.72;overflow-x:hidden}
::selection{background:var(--accent);color:#fff}
::-webkit-scrollbar{width:8px}
::-webkit-scrollbar-track{background:var(--bg)}
::-webkit-scrollbar-thumb{background:var(--border);border-radius:4px}
::-webkit-scrollbar-thumb:hover{background:var(--border-light)}
/* ═══ PROGRESS BAR ═══ */
#progress-bar{position:fixed;top:0;left:0;height:3px;background:linear-gradient(90deg,var(--rust),var(--accent),var(--amber));width:calc(var(--progress)*1%);z-index:1000;transition:width .3s ease;box-shadow:0 0 12px var(--accent-glow)}
/* ═══ SIDEBAR ═══ */
#sidebar{position:fixed;top:0;left:0;width:var(--sidebar-w);height:100vh;background:var(--surface);border-right:1px solid var(--border);z-index:100;display:flex;flex-direction:column;transition:transform .3s ease}
#sidebar-header{padding:24px 20px 16px;border-bottom:1px solid var(--border)}
#sidebar-header h1{font-family:var(--font-display);font-size:15px;font-weight:900;color:var(--accent);letter-spacing:.5px;text-transform:uppercase}
#sidebar-header .subtitle{font-size:12px;color:var(--text-muted);margin-top:2px;letter-spacing:.3px}
#toc{flex:1;overflow-y:auto;padding:12px 0;scrollbar-width:thin}
#toc a{display:flex;align-items:center;padding:7px 20px;font-size:13.5px;color:var(--text-muted);text-decoration:none;transition:all .2s;border-left:2px solid transparent;gap:10px;line-height:1.3}
#toc a:hover{color:var(--text);background:var(--surface2)}
#toc a.active{color:var(--accent);border-left-color:var(--accent);background:var(--accent-dim)}
#toc .ch-num{font-family:var(--font-code);font-size:11px;color:var(--text-dim);min-width:22px;font-weight:600}
#toc .ch-check{width:14px;height:14px;border-radius:50%;border:1.5px solid var(--border-light);display:flex;align-items:center;justify-content:center;flex-shrink:0;font-size:9px;transition:all .3s}
#toc .ch-check.done{border-color:var(--green);background:var(--green);color:#000}
#sidebar-footer{padding:14px 20px;border-top:1px solid var(--border);font-size:12px;color:var(--text-dim)}
#sidebar-footer .progress-text{font-family:var(--font-code);color:var(--text-muted);font-size:11px}
/* ═══ LANG TOGGLE ═══ */
#lang-toggle{display:flex;align-items:center;gap:8px;margin-top:12px;cursor:pointer;user-select:none}
#lang-toggle .lang-btn{font-family:var(--font-code);font-size:11px;padding:3px 8px;border-radius:4px;border:1px solid var(--border);color:var(--text-muted);background:none;cursor:pointer;transition:all .2s;letter-spacing:.5px}
#lang-toggle .lang-btn.active{background:var(--accent);color:#000;border-color:var(--accent);font-weight:700}
#lang-toggle .lang-btn:hover:not(.active){border-color:var(--text-muted);color:var(--text)}
#lang-divider{color:var(--text-dim);font-size:10px}
/* ═══ MOBILE TOGGLE ═══ */
#menu-toggle{display:none;position:fixed;top:12px;left:12px;z-index:200;width:40px;height:40px;background:var(--surface);border:1px solid var(--border);border-radius:8px;color:var(--accent);font-size:18px;cursor:pointer;align-items:center;justify-content:center}
/* ═══ MAIN CONTENT ═══ */
#main{margin-left:var(--sidebar-w);min-height:100vh}
.chapter{padding:60px 48px 80px;border-bottom:1px solid var(--border);max-width:860px;opacity:0;transform:translateY(20px);transition:opacity .5s ease,transform .5s ease}
.chapter.visible{opacity:1;transform:translateY(0)}
/* ═══ HERO ═══ */
#hero{padding:100px 48px 80px;max-width:860px;border-bottom:1px solid var(--border)}
#hero .overline{font-family:var(--font-code);font-size:12px;color:var(--accent);text-transform:uppercase;letter-spacing:2px;margin-bottom:16px}
#hero h1{font-family:var(--font-display);font-size:clamp(32px,5vw,52px);font-weight:900;line-height:1.15;color:#eaeef3;margin-bottom:16px}
#hero h1 em{font-style:normal;color:var(--accent);position:relative}
#hero h1 em::after{content:'';position:absolute;bottom:-2px;left:0;width:100%;height:3px;background:var(--accent);border-radius:2px;opacity:.4}
#hero .lead{font-size:18px;color:var(--text-muted);max-width:540px;line-height:1.7}
#hero .stats{display:flex;gap:32px;margin-top:36px;flex-wrap:wrap}
#hero .stat{text-align:center}
#hero .stat-num{font-family:var(--font-display);font-size:28px;font-weight:900;color:var(--amber)}
#hero .stat-label{font-size:12px;color:var(--text-dim);text-transform:uppercase;letter-spacing:1px;margin-top:2px}
/* ═══ TYPOGRAPHY ═══ */
.chapter h2{font-family:var(--font-display);font-size:28px;font-weight:900;color:#eaeef3;margin-bottom:8px;display:flex;align-items:center;gap:14px}
.chapter h2 .ch-badge{font-family:var(--font-code);font-size:13px;background:var(--accent);color:#000;padding:3px 10px;border-radius:4px;font-weight:700}
.chapter h3{font-family:var(--font-display);font-size:19px;font-weight:700;color:#d4dae3;margin:36px 0 12px;padding-top:12px}
.chapter p{margin:12px 0;font-size:16.5px}
.chapter strong{color:#dde3ec;font-weight:700}
.chapter em{color:var(--cyan);font-style:italic}
/* ═══ INLINE CODE ═══ */
.chapter code:not(pre code){font-family:var(--font-code);font-size:13.5px;background:var(--surface2);color:var(--pink);padding:2px 7px;border-radius:4px;border:1px solid var(--border)}
/* ═══ CODE BLOCKS ═══ */
pre{background:var(--code-bg);border:1px solid var(--code-border);border-radius:10px;padding:0;margin:16px 0;overflow:hidden;position:relative}
pre .code-header{display:flex;align-items:center;justify-content:space-between;padding:8px 16px;background:var(--surface);border-bottom:1px solid var(--code-border);font-family:var(--font-code);font-size:11px;color:var(--text-dim)}
pre .code-header .lang{color:var(--accent);font-weight:600;text-transform:uppercase;letter-spacing:1px}
pre .code-header .file-ref{color:var(--text-muted)}
pre .copy-btn{background:none;border:1px solid var(--border);border-radius:4px;color:var(--text-muted);font-size:11px;padding:2px 8px;cursor:pointer;font-family:var(--font-code);transition:all .2s}
pre .copy-btn:hover{color:var(--accent);border-color:var(--accent)}
pre .copy-btn.copied{color:var(--green);border-color:var(--green)}
pre code{display:block;padding:16px 20px;overflow-x:auto;font-family:var(--font-code);font-size:13.5px;line-height:1.65;color:var(--text);tab-size:4}
/* ═══ SYNTAX HIGHLIGHTING ═══ */
.hl-kw{color:#ff7b72;font-weight:600}
.hl-ty{color:#79c0ff}
.hl-fn{color:#d2a8ff}
.hl-str{color:#a5d6ff}
.hl-num{color:#79c0ff}
.hl-cm{color:#484f58;font-style:italic}
.hl-mac{color:#56d4dd}
.hl-attr{color:#f0883e}
.hl-op{color:#c9d1d9}
.hl-life{color:#ffa657}
.hl-self{color:#ff7b72;font-style:italic}
.hl-bool{color:#79c0ff}
/* ═══ LISTS ═══ */
.chapter ul,.chapter ol{padding-left:24px;margin:10px 0}
.chapter li{margin:6px 0;font-size:16px}
.chapter li::marker{color:var(--accent)}
/* ═══ TABLES ═══ */
.chapter table{width:100%;border-collapse:collapse;margin:16px 0;font-size:14.5px}
.chapter th{text-align:left;padding:10px 14px;background:var(--surface2);color:var(--text-muted);font-weight:700;font-size:12px;text-transform:uppercase;letter-spacing:.5px;border-bottom:2px solid var(--accent)}
.chapter td{padding:10px 14px;border-bottom:1px solid var(--border);vertical-align:top}
.chapter tr:hover td{background:var(--surface)}
.chapter td code{font-size:12.5px}
/* ═══ COLLAPSIBLE SECTIONS ═══ */
details{margin:12px 0;border:1px solid var(--border);border-radius:8px;overflow:hidden}
details[open]{border-color:var(--border-light)}
summary{padding:12px 16px;background:var(--surface);cursor:pointer;font-weight:700;font-size:15px;color:#d4dae3;display:flex;align-items:center;gap:8px;user-select:none;list-style:none}
summary::-webkit-details-marker{display:none}
summary::before{content:'>';font-family:var(--font-code);font-size:12px;color:var(--accent);transition:transform .2s;display:inline-block;width:16px}
details[open] summary::before{transform:rotate(90deg)}
summary:hover{background:var(--surface2)}
details .detail-body{padding:16px 20px}
/* ═══ CALLOUT BOXES ═══ */
.callout{border-left:3px solid var(--accent);background:var(--accent-dim);padding:14px 18px;border-radius:0 8px 8px 0;margin:16px 0}
.callout.warning{border-color:var(--amber);background:var(--amber-dim)}
.callout.danger{border-color:var(--red);background:var(--rust-dim)}
.callout-title{font-weight:700;font-size:13px;text-transform:uppercase;letter-spacing:.5px;margin-bottom:4px;color:var(--accent)}
.callout.warning .callout-title{color:var(--amber)}
.callout.danger .callout-title{color:var(--red)}
/* ═══ FILE REFERENCE TAG ═══ */
.file-tag{display:inline-flex;align-items:center;gap:4px;font-family:var(--font-code);font-size:12px;color:var(--amber);background:var(--amber-dim);padding:2px 8px;border-radius:4px;margin:4px 0}
.file-tag::before{content:'>';color:var(--text-dim)}
/* ═══ DIAGRAM ═══ */
.diagram{background:var(--surface);border:1px solid var(--border);border-radius:10px;padding:24px;margin:20px 0;overflow-x:auto}
.diagram pre{background:none;border:none;margin:0;padding:0}
.diagram pre code{font-size:13px;color:var(--cyan);padding:0}
/* ═══ EXERCISES ═══ */
.exercise{background:var(--surface);border:1px solid var(--border);border-radius:10px;padding:20px;margin:12px 0;position:relative}
.exercise::before{content:attr(data-num);position:absolute;top:-10px;left:16px;background:var(--accent);color:#000;font-family:var(--font-code);font-size:11px;font-weight:700;padding:2px 10px;border-radius:4px}
.exercise h4{font-family:var(--font-display);font-size:16px;margin-bottom:6px;color:#eaeef3}
.exercise p{font-size:14.5px;color:var(--text-muted)}
/* ═══ RESPONSIVE ═══ */
@media(max-width:900px){
#sidebar{transform:translateX(-100%)}
#sidebar.open{transform:translateX(0);box-shadow:20px 0 60px rgba(0,0,0,.5)}
#main{margin-left:0}
#menu-toggle{display:flex}
.chapter{padding:40px 20px 60px}
#hero{padding:70px 20px 50px}
.chapter h2{font-size:24px}
pre code{font-size:12.5px}
}
@media(max-width:600px){
#hero h1{font-size:28px}
#hero .stats{gap:20px}
.chapter table{font-size:13px}
.chapter td,.chapter th{padding:8px 10px}
}
</style>
</head>
<body>
<!-- Progress Bar -->
<div id="progress-bar"></div>
<!-- Mobile Menu Toggle -->
<button id="menu-toggle" aria-label="Toggle navigation">☰</button>
<!-- Sidebar -->
<nav id="sidebar">
<div id="sidebar-header">
<h1 data-i18n="sidebar-title">Rust Forge</h1>
<div class="subtitle" data-i18n="sidebar-subtitle">window-selector codebase tour</div>
<div id="lang-toggle">
<button class="lang-btn active" data-lang="en" onclick="setLang('en')">EN</button>
<span id="lang-divider">/</span>
<button class="lang-btn" data-lang="zh" onclick="setLang('zh')">繁中</button>
</div>
</div>
<div id="toc"></div>
<div id="sidebar-footer">
<div class="progress-text"><span id="done-count">0</span> / 16 chapters read</div>
</div>
</nav>
<!-- Main Content -->
<main id="main">
<!-- HERO -->
<section id="hero">
<div class="overline">// A hands-on Rust course</div>
<h1>Learn Rust Through<br><em>window-selector</em></h1>
<p class="lead">Every example is real code from your own codebase. No toy projects, no contrived exercises — just the patterns you actually shipped.</p>
<div class="stats">
<div class="stat"><div class="stat-num">16</div><div class="stat-label">Chapters</div></div>
<div class="stat"><div class="stat-num">22</div><div class="stat-label">Source Files</div></div>
<div class="stat"><div class="stat-num">67</div><div class="stat-label">Tests Pass</div></div>
<div class="stat"><div class="stat-num">7</div><div class="stat-label">Design Patterns</div></div>
</div>
</section>
<!-- ══════════════════════════════════════════════ -->
<!-- CHAPTER 1 -->
<!-- ══════════════════════════════════════════════ -->
<section class="chapter" id="ch-1" data-chapter="1">
<h2><span class="ch-badge">01</span> Project Structure & Module System</h2>
<h3>How Rust organizes code</h3>
<p>Rust uses a <strong>module tree</strong> rooted at <code>main.rs</code> (for binaries) or <code>lib.rs</code> (for libraries). Every <code>.rs</code> file in <code>src/</code> is a module, but it only becomes part of your program when you declare it with <code>mod</code>.</p>
<span class="file-tag">main.rs:3-24</span>
<pre><div class="code-header"><span class="lang">rust</span><button class="copy-btn" onclick="copyCode(this)">copy</button></div><code>mod about_dialog;
mod accent_color;
mod animation;
mod config;
mod dwm_thumbnails;
mod grid_layout;
mod hotkey;
// ... 15 more modules</code></pre>
<p>Each <code>mod foo;</code> tells the compiler: <em>"load <code>src/foo.rs</code> and make it a child module of main."</em> Without this declaration, the file is <strong>ignored</strong> — even if it exists on disk.</p>
<h3>Visibility: <code>pub</code> vs private</h3>
<p>By default, everything in Rust is <strong>private</strong>. To use something from another module, mark it <code>pub</code>.</p>
<span class="file-tag">state.rs:8-23</span>
<pre><div class="code-header"><span class="lang">rust</span><span class="file-ref">public enum, public fields</span><button class="copy-btn" onclick="copyCode(this)">copy</button></div><code>pub enum OverlayState { // pub → other modules can use this type
Hidden,
FadingIn,
Active {
selected: Option<usize>, // fields inside enum variants are always public
},
FadingOut {
switch_target: Option<HWND>,
},
}</code></pre>
<span class="file-tag">animation.rs:31-34</span>
<pre><div class="code-header"><span class="lang">rust</span><span class="file-ref">public struct, public fields</span><button class="copy-btn" onclick="copyCode(this)">copy</button></div><code>pub struct FadeAnimator {
pub current_alpha: u8, // pub → overlay.rs can read this
pub direction: Option<FadeDirection>,
}</code></pre>
<h3><code>use</code> imports</h3>
<p><code>use</code> brings items into scope so you don't write the full path every time.</p>
<span class="file-tag">interaction.rs:1-8</span>
<pre><div class="code-header"><span class="lang">rust</span><button class="copy-btn" onclick="copyCode(this)">copy</button></div><code>use crate::state::{OverlayState, SessionTags}; // crate:: = root of this project
use crate::window_info::WindowInfo;
use windows::Win32::Foundation::HWND; // external crate
use windows::Win32::UI::Input::KeyboardAndMouse::{
GetAsyncKeyState, VK_CONTROL, VK_RETURN, // import multiple items from one path
VK_SPACE, VK_ESCAPE, VK_A, VK_Z,
};</code></pre>
<div class="callout">
<div class="callout-title">Key syntax</div>
<ul>
<li><code>crate::</code> — start from your project root</li>
<li><code>super::</code> — go up one module (parent)</li>
<li><code>self::</code> — current module (rarely needed)</li>
<li><code>use X as Y</code> — rename on import</li>
</ul>
</div>
<h3><code>pub(crate)</code> — visible within the crate but not outside</h3>
<p>You'll sometimes see <code>pub(crate)</code> which means "public to other modules in this crate, but not to external users." Since this is a binary (not a library), <code>pub</code> and <code>pub(crate)</code> are functionally identical here.</p>
</section>
<!-- ══════════════════════════════════════════════ -->
<!-- CHAPTER 2 -->
<!-- ══════════════════════════════════════════════ -->
<section class="chapter" id="ch-2" data-chapter="2">
<h2><span class="ch-badge">02</span> Ownership, Borrowing & Lifetimes</h2>
<p>This is Rust's core innovation. Every value has exactly <strong>ONE owner</strong>. When the owner goes out of scope, the value is dropped (freed).</p>
<h3>Move semantics</h3>
<span class="file-tag">main.rs:207-217</span>
<pre><div class="code-header"><span class="lang">rust</span><button class="copy-btn" onclick="copyCode(this)">copy</button></div><code>let mut app_state = Box::new(AppState { ... });
// ^^^^^^^^^^^^^^^^ app_state OWNS this heap allocation
let app_state_ptr = app_state.as_mut() as *mut AppState;
// ^^^^^^^^^ borrow, then cast to raw pointer — ownership stays with app_state
// ... later at line 292:
drop(app_state); // explicitly drop — frees the heap memory</code></pre>
<p>If you tried <code>let x = app_state;</code> somewhere in between, <code>app_state</code> would be <strong>moved</strong> into <code>x</code>, and using <code>app_state</code> afterward would be a compile error.</p>
<h3>Borrowing: <code>&</code> and <code>&mut</code></h3>
<ul>
<li><code>&T</code> — shared/immutable reference. Multiple allowed simultaneously.</li>
<li><code>&mut T</code> — exclusive/mutable reference. Only ONE at a time.</li>
</ul>
<span class="file-tag">interaction.rs:62-68</span>
<pre><div class="code-header"><span class="lang">rust</span><button class="copy-btn" onclick="copyCode(this)">copy</button></div><code>pub fn handle_key_down(
vk_code: u32,
state: &OverlayState, // shared borrow — just reading
windows: &[WindowInfo], // shared borrow of a slice
tags: &mut SessionTags, // exclusive borrow — we might modify
direct_switch: bool, // copy — u32/bool implement Copy
) -> KeyAction {</code></pre>
<p>Why <code>&[WindowInfo]</code> instead of <code>&Vec<WindowInfo></code>? A <strong>slice</strong> (<code>&[T]</code>) is more flexible — it works with any contiguous data, not just <code>Vec</code>. This is a common Rust idiom.</p>
<h3>The borrow checker in action</h3>
<div class="callout danger">
<div class="callout-title">This won't compile</div>
<pre><div class="code-header"><span class="lang">rust</span></div><code>let mut v = vec![1, 2, 3];
let first = &v[0]; // shared borrow
v.push(4); // ERROR: can't mutate while shared borrow exists
println!("{}", first); // first is still alive here</code></pre>
</div>
<p>In your codebase, you work around this naturally:</p>
<span class="file-tag">main.rs:595-596</span>
<pre><div class="code-header"><span class="lang">rust</span><span class="file-ref">cloning to avoid borrow conflicts</span><button class="copy-btn" onclick="copyCode(this)">copy</button></div><code>let snap = app.window_snapshot.clone(); // clone to get owned copy
app.overlay_manager.show(&snap, &mut app.overlay_state);
// Now app.overlay_manager borrows snap (not app.window_snapshot)
// so there's no conflict with &mut app.overlay_state</code></pre>
<h3>Clone vs Copy</h3>
<ul>
<li><strong><code>Copy</code></strong> — bitwise copy, automatic. For simple types: <code>u8</code>, <code>i32</code>, <code>f32</code>, <code>bool</code>, <code>char</code>.</li>
<li><strong><code>Clone</code></strong> — explicit deep copy via <code>.clone()</code>. For heap types: <code>String</code>, <code>Vec<T></code>.</li>
</ul>
<span class="file-tag">window_info.rs:4</span>
<pre><div class="code-header"><span class="lang">rust</span><button class="copy-btn" onclick="copyCode(this)">copy</button></div><code>#[derive(Debug, Clone)]
pub struct WindowInfo {
pub hwnd: HWND,
pub title: String, // String is on the heap → needs Clone, not Copy
pub is_minimized: bool, // bool is Copy
// ...
}</code></pre>
<h3>Lifetimes (brief)</h3>
<p>Lifetimes appear when the compiler can't figure out how long a reference is valid. Your codebase avoids explicit lifetimes by using owned types and raw pointers for the Win32 interop. You'll mostly encounter lifetimes when writing generic functions that accept references.</p>
</section>
<!-- ══════════════════════════════════════════════ -->
<!-- CHAPTER 3 -->
<!-- ══════════════════════════════════════════════ -->
<section class="chapter" id="ch-3" data-chapter="3">
<h2><span class="ch-badge">03</span> Structs & Methods</h2>
<h3>Defining structs</h3>
<span class="file-tag">grid_layout.rs:5-13</span>
<pre><div class="code-header"><span class="lang">rust</span><button class="copy-btn" onclick="copyCode(this)">copy</button></div><code>#[derive(Debug, Clone, Copy, PartialEq)] // derive = auto-implement traits
pub struct CellRect {
pub x: f32,
pub y: f32,
pub width: f32,
pub height: f32,
pub window_index: usize,
}</code></pre>
<div class="callout">
<div class="callout-title">Derive macros explained</div>
<ul>
<li><code>Debug</code> — enables <code>{:?}</code> formatting for printing</li>
<li><code>Clone</code> — enables <code>.clone()</code></li>
<li><code>Copy</code> — enables implicit bitwise copy (only for simple stack types)</li>
<li><code>PartialEq</code> — enables <code>==</code> comparison</li>
</ul>
</div>
<h3><code>impl</code> blocks: methods and associated functions</h3>
<span class="file-tag">grid_layout.rs:15-32</span>
<pre><div class="code-header"><span class="lang">rust</span><span class="file-ref">method (takes &self)</span><button class="copy-btn" onclick="copyCode(this)">copy</button></div><code>impl CellRect {
// This is a METHOD — takes &self as first parameter
pub fn scaled(&self, factor: f32) -> CellRect {
let new_w = self.width * factor;
let new_h = self.height * factor;
let cx = self.x + self.width / 2.0;
let cy = self.y + self.height / 2.0;
CellRect {
x: cx - new_w / 2.0,
y: cy - new_h / 2.0,
width: new_w,
height: new_h,
window_index: self.window_index,
}
}
}</code></pre>
<span class="file-tag">state.rs:51-56</span>
<pre><div class="code-header"><span class="lang">rust</span><span class="file-ref">associated function (constructor)</span><button class="copy-btn" onclick="copyCode(this)">copy</button></div><code>impl SessionTags {
pub fn new() -> Self { // Self = SessionTags. This is a constructor.
Self {
tags: HashMap::new(),
}
}
}</code></pre>
<p>Call it: <code>SessionTags::new()</code> — note the <code>::</code> syntax, not <code>.</code>.</p>
<h3><code>&self</code> vs <code>&mut self</code> vs <code>self</code></h3>
<pre><div class="code-header"><span class="lang">rust</span></div><code>pub fn get(&self, number: u8) -> Option<HWND> // read-only access
pub fn assign(&mut self, number: u8, hwnd: HWND) // modifying access
pub fn into_inner(self) -> HashMap<u8, HWND> // consumes the struct (takes ownership)</code></pre>
</section>
<!-- ══════════════════════════════════════════════ -->
<!-- CHAPTER 4 -->
<!-- ══════════════════════════════════════════════ -->
<section class="chapter" id="ch-4" data-chapter="4">
<h2><span class="ch-badge">04</span> Enums & Pattern Matching</h2>
<p>Rust enums are <strong>algebraic data types</strong> — each variant can hold different data.</p>
<h3>Defining enums</h3>
<span class="file-tag">state.rs:8-23</span>
<pre><div class="code-header"><span class="lang">rust</span><button class="copy-btn" onclick="copyCode(this)">copy</button></div><code>pub enum OverlayState {
Hidden, // no data
FadingIn, // no data
Active { selected: Option<usize> }, // struct-like variant with named field
FadingOut { switch_target: Option<HWND> }, // struct-like variant
}</code></pre>
<span class="file-tag">interaction.rs:44-56</span>
<pre><div class="code-header"><span class="lang">rust</span><button class="copy-btn" onclick="copyCode(this)">copy</button></div><code>pub enum KeyAction {
None,
Select(usize), // tuple-like variant holding a usize
SwitchTo(HWND), // tuple-like variant holding an HWND
Dismiss,
TagAssigned,
}</code></pre>
<h3><code>match</code> — exhaustive pattern matching</h3>
<p><code>match</code> forces you to handle EVERY variant. The compiler errors if you miss one.</p>
<span class="file-tag">main.rs:308-342</span>
<pre><div class="code-header"><span class="lang">rust</span><span class="file-ref">the main message dispatcher</span><button class="copy-btn" onclick="copyCode(this)">copy</button></div><code>match msg {
WM_CREATE => LRESULT(0),
WM_DESTROY => {
PostQuitMessage(0);
LRESULT(0)
}
WM_HOTKEY => {
if wparam.0 as i32 == hotkey::HOTKEY_ID {
handle_hotkey(app);
}
LRESULT(0)
}
// ... more arms ...
_ => DefWindowProcW(hwnd, msg, wparam, lparam), // _ = catch-all
}</code></pre>
<h3>Destructuring in match</h3>
<span class="file-tag">main.rs:676-698</span>
<pre><div class="code-header"><span class="lang">rust</span><span class="file-ref">matching KeyAction with destructuring</span><button class="copy-btn" onclick="copyCode(this)">copy</button></div><code>match action {
KeyAction::None => {}
KeyAction::Select(idx) => { // extract the usize from Select
app.overlay_state = OverlayState::Active { selected: Some(idx) };
// ...
}
KeyAction::SwitchTo(target) => { // extract the HWND from SwitchTo
app.overlay_manager.begin_hide(&mut app.overlay_state, Some(target));
}
KeyAction::Dismiss => {
dismiss_overlay(app);
}
KeyAction::TagAssigned => { /* ... */ }
}</code></pre>
<h3><code>if let</code> & <code>matches!</code></h3>
<pre><div class="code-header"><span class="lang">rust</span><span class="file-ref">state.rs — match a single pattern</span><button class="copy-btn" onclick="copyCode(this)">copy</button></div><code>// if let — when you only care about ONE variant
pub fn selected_index(&self) -> Option<usize> {
if let OverlayState::Active { selected } = self {
*selected
} else {
None
}
}
// matches! macro — boolean pattern check
pub fn is_visible(&self) -> bool {
!matches!(self, OverlayState::Hidden)
}</code></pre>
</section>
<!-- ══════════════════════════════════════════════ -->
<!-- CHAPTER 5 -->
<!-- ══════════════════════════════════════════════ -->
<section class="chapter" id="ch-5" data-chapter="5">
<h2><span class="ch-badge">05</span> Traits & Polymorphism</h2>
<p>Traits are Rust's version of interfaces — they define shared behavior.</p>
<h3><code>impl Trait</code> for your types</h3>
<span class="file-tag">state.rs:95-99</span>
<pre><div class="code-header"><span class="lang">rust</span><span class="file-ref">implementing Default trait</span><button class="copy-btn" onclick="copyCode(this)">copy</button></div><code>impl Default for SessionTags {
fn default() -> Self {
Self::new()
}
}</code></pre>
<p>Now <code>SessionTags::default()</code> works, and any generic code that requires <code>T: Default</code> accepts <code>SessionTags</code>.</p>
<h3><code>Drop</code> — custom destructor (RAII)</h3>
<span class="file-tag">dwm_thumbnails.rs:23-31</span>
<pre><div class="code-header"><span class="lang">rust</span><button class="copy-btn" onclick="copyCode(this)">copy</button></div><code>impl Drop for ThumbnailHandle {
fn drop(&mut self) {
if self.thumbnail_id != 0 {
unsafe {
let _ = DwmUnregisterThumbnail(self.thumbnail_id);
}
}
}
}</code></pre>
<p>When a <code>ThumbnailHandle</code> goes out of scope, Rust automatically calls <code>drop()</code>. This is <strong>RAII</strong> (Resource Acquisition Is Initialization) — resources are cleaned up deterministically.</p>
<span class="file-tag">window_switcher.rs:21-37</span>
<pre><div class="code-header"><span class="lang">rust</span><span class="file-ref">RAII guard for thread input</span><button class="copy-btn" onclick="copyCode(this)">copy</button></div><code>struct ThreadInputGuard {
our_thread: u32,
target_thread: u32,
}
impl Drop for ThreadInputGuard {
fn drop(&mut self) {
// Always detach thread input, even on panic or early return
let ok = unsafe {
AttachThreadInput(self.our_thread, self.target_thread, false).as_bool()
};
}
}</code></pre>
<h3>Derive macros & Serde</h3>
<pre><div class="code-header"><span class="lang">rust</span><span class="file-ref">config.rs — external derive macros</span><button class="copy-btn" onclick="copyCode(this)">copy</button></div><code>// Derive macros = compiler generates trait implementations for you
#[derive(Debug, Clone, Copy, PartialEq)]
// External derive macros (from serde crate) — auto-generate serialization code
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AppConfig { ... }</code></pre>
</section>
<!-- ══════════════════════════════════════════════ -->
<!-- CHAPTER 6 -->
<!-- ══════════════════════════════════════════════ -->
<section class="chapter" id="ch-6" data-chapter="6">
<h2><span class="ch-badge">06</span> Error Handling</h2>
<p>Rust has no exceptions. Errors are values.</p>
<h3><code>Result<T, E></code> and the <code>?</code> operator</h3>
<span class="file-tag">config.rs:64-78</span>
<pre><div class="code-header"><span class="lang">rust</span><span class="file-ref">the ? operator propagates errors</span><button class="copy-btn" onclick="copyCode(this)">copy</button></div><code>fn save_to_path(config: &AppConfig, config_path: &Path) -> std::io::Result<()> {
if let Some(parent) = config_path.parent() {
fs::create_dir_all(parent)?; // ? = if this errors, return the error immediately
}
let toml_str = toml::to_string(config)
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
// ^^^^^^^^ convert toml::ser::Error into std::io::Error, then ?
let tmp_path = config_path.with_extension("toml.tmp");
fs::write(&tmp_path, &toml_str)?; // ? again
fs::rename(&tmp_path, config_path)?;
Ok(()) // success! return the "empty" Ok
}</code></pre>
<h3><code>Option<T></code> — nullable values without null</h3>
<pre><div class="code-header"><span class="lang">rust</span><button class="copy-btn" onclick="copyCode(this)">copy</button></div><code>pub letter: Option<char>, // Some('a') or None
pub number_tag: Option<u8>, // Some(1) or None
// Pattern match
if let Some(letter) = win.letter { /* use letter */ }
// Unwrap with default
let tag = win.number_tag.unwrap_or(0);
// Map/transform
let upper: Option<String> = win.letter.map(|c| c.to_uppercase().to_string());</code></pre>
<h3>Fallback patterns</h3>
<span class="file-tag">main.rs:134-140</span>
<pre><div class="code-header"><span class="lang">rust</span><button class="copy-btn" onclick="copyCode(this)">copy</button></div><code>let config = match AppConfig::load(&config_dir) {
Ok(c) => c,
Err(e) => {
tracing::error!("Config load failed: {}", e);
AppConfig::default() // fall back to defaults on error
}
};
// let _ = ... explicitly ignores errors
let _ = SetForegroundWindow(hwnd);</code></pre>
</section>
<!-- ══════════════════════════════════════════════ -->
<!-- CHAPTER 7 -->
<!-- ══════════════════════════════════════════════ -->
<section class="chapter" id="ch-7" data-chapter="7">
<h2><span class="ch-badge">07</span> Collections & Iterators</h2>
<h3><code>Vec<T></code> and <code>HashMap<K, V></code></h3>
<pre><div class="code-header"><span class="lang">rust</span><button class="copy-btn" onclick="copyCode(this)">copy</button></div><code>// Vec — growable array
let mut monitors: Vec<MonitorInfo> = Vec::new();
let mut buf = vec![0u16; (title_len as usize) + 1]; // vec of zeros
monitors.push(info); // append
monitors.sort_by_key(|m| if m.is_primary { 0 } else { 1 });
ctx.windows.retain(|w| !own_hwnds.contains(&w.hwnd)); // filter in-place
// HashMap — key-value store
pub struct SessionTags {
tags: HashMap<u8, HWND>, // number key → window handle
}
self.tags.insert(number, hwnd);
self.tags.get(&number).copied() // lookup → Option<HWND></code></pre>
<h3>Iterator chains</h3>
<p>Iterators are Rust's killer feature for data transformation. They're <strong>lazy</strong> — nothing happens until you "consume" the chain.</p>
<span class="file-tag">mru_tracker.rs:87-92</span>
<pre><div class="code-header"><span class="lang">rust</span><span class="file-ref">build a HashMap from an iterator</span><button class="copy-btn" onclick="copyCode(this)">copy</button></div><code>let mru_pos: HashMap<isize, usize> = self
.order
.iter() // iterate over Vec<HWND>
.enumerate() // (index, &hwnd)
.map(|(i, &h)| (h.0 as isize, i)) // transform to (isize, usize) pairs
.collect(); // collect into HashMap</code></pre>
<span class="file-tag">grid_layout.rs:79-93</span>
<pre><div class="code-header"><span class="lang">rust</span><span class="file-ref">generate cells with map + collect</span><button class="copy-btn" onclick="copyCode(this)">copy</button></div><code>let cells = (0..window_count) // range 0, 1, 2, ...
.map(|i| {
let row = i / cols;
let col = i % cols;
let x = PADDING + col as f32 * (cell_width + PADDING);
let y = PADDING + row as f32 * (cell_height + PADDING);
CellRect { x, y, width: cell_width, height: cell_height, window_index: i }
})
.collect(); // collect into Vec<CellRect></code></pre>
<table>
<tr><th>Method</th><th>What it does</th></tr>
<tr><td><code>.iter()</code></td><td>iterate by reference (<code>&T</code>)</td></tr>
<tr><td><code>.iter_mut()</code></td><td>iterate by mutable reference (<code>&mut T</code>)</td></tr>
<tr><td><code>.into_iter()</code></td><td>iterate by value (consumes the collection)</td></tr>
<tr><td><code>.map(fn)</code></td><td>transform each element</td></tr>
<tr><td><code>.filter(fn)</code></td><td>keep elements where fn returns true</td></tr>
<tr><td><code>.find(fn)</code></td><td>first element where fn returns true</td></tr>
<tr><td><code>.position(fn)</code></td><td>index of first match</td></tr>
<tr><td><code>.enumerate()</code></td><td>add index: <code>(usize, T)</code></td></tr>
<tr><td><code>.collect()</code></td><td>consume iterator, build a collection</td></tr>
<tr><td><code>.any(fn)</code></td><td>true if any element matches</td></tr>
<tr><td><code>.take(n)</code></td><td>first n elements</td></tr>
</table>
</section>
<!-- ══════════════════════════════════════════════ -->
<!-- CHAPTER 8 -->
<!-- ══════════════════════════════════════════════ -->
<section class="chapter" id="ch-8" data-chapter="8">
<h2><span class="ch-badge">08</span> Closures</h2>
<p>Closures are anonymous functions that can capture variables from their environment.</p>
<p>Syntax: <code>|params| body</code> or <code>|params| { multi-line body }</code></p>
<pre><div class="code-header"><span class="lang">rust</span><button class="copy-btn" onclick="copyCode(this)">copy</button></div><code>// mru_tracker.rs — closure captures mru_pos from outer scope
windows.sort_by_key(|w| {
mru_pos
.get(&(w.hwnd.0 as isize))
.copied()
.unwrap_or(usize::MAX)
});
// window_enumerator.rs — retain takes a closure
ctx.windows.retain(|w| {
!own_hwnds.contains(&w.hwnd) // captures own_hwnds
});
// Closures vs function pointers:
pub type KeyHandler = fn(vk_code: u32) -> bool; // fn pointer — no captured variables
let threshold = 100;
let filter = |x: &i32| *x > threshold; // closure — captures `threshold`</code></pre>
</section>
<!-- ══════════════════════════════════════════════ -->
<!-- CHAPTER 9 -->
<!-- ══════════════════════════════════════════════ -->
<section class="chapter" id="ch-9" data-chapter="9">
<h2><span class="ch-badge">09</span> Generics & Type Parameters</h2>
<h3>In the standard library</h3>
<pre><div class="code-header"><span class="lang">rust</span><button class="copy-btn" onclick="copyCode(this)">copy</button></div><code>Vec<WindowInfo> // Vec is generic over T
HashMap<u8, HWND> // HashMap is generic over K, V
Option<usize> // Option is generic over T
Result<(), std::io::Error> // Result is generic over T, E</code></pre>
<h3><code>as</code> — type casting</h3>
<p>Numeric conversions are explicit in Rust (no implicit widening/narrowing):</p>
<pre><div class="code-header"><span class="lang">rust</span><button class="copy-btn" onclick="copyCode(this)">copy</button></div><code>let n = window_count as f32; // usize → f32
let cmd = (wparam.0 & 0xFFFF) as u32; // usize → u32
let c = (b'a' + (vk - VK_A.0 as u32) as u8) as char; // chain of casts</code></pre>
</section>
<!-- ══════════════════════════════════════════════ -->
<!-- CHAPTER 10 -->
<!-- ══════════════════════════════════════════════ -->
<section class="chapter" id="ch-10" data-chapter="10">
<h2><span class="ch-badge">10</span> <code>unsafe</code> Rust & FFI</h2>
<p>Your codebase is heavily <code>unsafe</code> because it calls raw Win32 APIs.</p>
<div class="callout warning">
<div class="callout-title">What unsafe means</div>
<p><code>unsafe</code> does NOT mean "bad code." It means: <em>"I, the programmer, am manually guaranteeing invariants that the compiler can't verify."</em></p>
<p>It unlocks: (1) Dereference raw pointers, (2) Call unsafe functions, (3) Access mutable statics, (4) Implement unsafe traits, (5) Access union fields.</p>
</div>
<h3>Raw pointers & dereferencing</h3>
<pre><div class="code-header"><span class="lang">rust</span><button class="copy-btn" onclick="copyCode(this)">copy</button></div><code>// main.rs:88 — getting a raw pointer
fn get_app_state() -> *mut AppState {
APP_STATE_PTR.load(std::sync::atomic::Ordering::Relaxed) as *mut AppState
}
// main.rs:306 — dereferencing it
unsafe {
let app = &mut *app_ptr; // raw pointer → mutable reference
}</code></pre>
<h3>Win32 callbacks</h3>
<pre><div class="code-header"><span class="lang">rust</span><span class="file-ref">main.rs:296 — extern "system" calling convention</span><button class="copy-btn" onclick="copyCode(this)">copy</button></div><code>unsafe extern "system" fn main_wndproc(
hwnd: HWND, // unsafe — called by Windows directly
msg: u32, // extern "system" — Windows calling convention
wparam: WPARAM, // must match the exact signature Windows expects
lparam: LPARAM,
) -> LRESULT {</code></pre>
<h3>Callback context via LPARAM</h3>
<span class="file-tag">window_enumerator.rs:39-51</span>
<pre><div class="code-header"><span class="lang">rust</span><button class="copy-btn" onclick="copyCode(this)">copy</button></div><code>let ctx_ptr = &mut ctx as *mut EnumContext; // Rust struct → raw pointer
unsafe {
let _ = EnumWindows(
Some(enum_windows_callback), // function pointer to our callback
LPARAM(ctx_ptr as isize), // context packed as integer
);
}
// In the callback — unpack integer → pointer → reference:
unsafe extern "system" fn enum_windows_callback(hwnd: HWND, lparam: LPARAM) -> BOOL {
let ctx = &mut *(lparam.0 as *mut EnumContext);
}</code></pre>
<h3><code>transmute</code> & <code>forget</code></h3>
<pre><div class="code-header"><span class="lang">rust</span><button class="copy-btn" onclick="copyCode(this)">copy</button></div><code>// keyboard_hook.rs:115 — reinterpret cast (extremely dangerous)
let handler: KeyHandler = std::mem::transmute(handler_ptr);
// logging.rs:58 — prevent Drop from running (intentional leak)
std::mem::forget(_guard);</code></pre>
</section>
<!-- ══════════════════════════════════════════════ -->
<!-- CHAPTER 11 -->
<!-- ══════════════════════════════════════════════ -->
<section class="chapter" id="ch-11" data-chapter="11">
<h2><span class="ch-badge">11</span> Smart Pointers & Memory Layout</h2>
<h3><code>Box<T></code> — heap allocation</h3>
<pre><div class="code-header"><span class="lang">rust</span><button class="copy-btn" onclick="copyCode(this)">copy</button></div><code>let mut app_state = Box::new(AppState { ... });
// Box::new() allocates on the HEAP (not the stack)
// 1. AppState is too large for comfortable stack usage
// 2. We need a stable pointer — stack addresses change; heap addresses don't</code></pre>
<h3>Stack vs Heap</h3>
<div class="diagram">
<pre><code>Stack (fast, automatic) Heap (slower, manual/Box)
┌────────────────────┐ ┌────────────────────┐
│ vk_code: u32 │ │ AppState { │
│ result: KeyAction │ │ config: ... │
│ idx: usize │ │ overlay_state │
└────────────────────┘ │ window_snapshot │
(freed when fn returns) │ ... │
└────────────────────┘
(freed when Box is dropped)</code></pre>
</div>
<h3><code>Vec<T></code> internals</h3>
<div class="diagram">
<pre><code>Stack: Vec<WindowInfo> Heap:
┌──────────────┐ ┌──────────────────┐
│ ptr ─────────────────────── → │ WindowInfo[0] │
│ len: 5 │ │ WindowInfo[1] │
│ capacity: 8 │ │ WindowInfo[2] │
└──────────────┘ │ WindowInfo[3] │
│ WindowInfo[4] │
│ (unused) [5..7] │
└──────────────────┘</code></pre>
</div>
</section>
<!-- ══════════════════════════════════════════════ -->
<!-- CHAPTER 12 -->
<!-- ══════════════════════════════════════════════ -->
<section class="chapter" id="ch-12" data-chapter="12">
<h2><span class="ch-badge">12</span> Concurrency Primitives</h2>
<p>Your codebase uses concurrency primitives in a single-threaded context. This is unusual but intentional — it avoids <code>static mut</code> (which is unsound in Rust 2024).</p>
<h3><code>AtomicUsize</code> & <code>AtomicBool</code></h3>
<pre><div class="code-header"><span class="lang">rust</span><button class="copy-btn" onclick="copyCode(this)">copy</button></div><code>// main.rs:83 — AtomicUsize instead of static mut
static APP_STATE_PTR: std::sync::atomic::AtomicUsize =
std::sync::atomic::AtomicUsize::new(0);
// Why? AtomicUsize is Send + Sync (safe in statics). static mut is unsound.
// keyboard_hook.rs:21 — AtomicBool for on/off flag
static HOOK_ACTIVE: AtomicBool = AtomicBool::new(false);
pub fn set_active(active: bool) {
HOOK_ACTIVE.store(active, Ordering::Relaxed);
}
// Ordering::Relaxed = "no sync guarantees beyond atomicity"
// Fine for single-thread — no other thread to sync with.</code></pre>
<h3><code>OnceLock</code> & <code>unsafe impl Send + Sync</code></h3>
<pre><div class="code-header"><span class="lang">rust</span><button class="copy-btn" onclick="copyCode(this)">copy</button></div><code>// mru_tracker.rs:129 — write-once global
static MRU_TRACKER_CELL: std::sync::OnceLock<SendPtr> = std::sync::OnceLock::new();
// Raw pointers are neither Send nor Sync by default.
// Here, the programmer manually asserts single-thread safety:
struct SendPtr(*mut MruTracker);
unsafe impl Send for SendPtr {}
unsafe impl Sync for SendPtr {}</code></pre>
</section>
<!-- ══════════════════════════════════════════════ -->
<!-- CHAPTER 13 -->
<!-- ══════════════════════════════════════════════ -->
<section class="chapter" id="ch-13" data-chapter="13">
<h2><span class="ch-badge">13</span> Testing</h2>
<h3>Test structure</h3>
<pre><div class="code-header"><span class="lang">rust</span><button class="copy-btn" onclick="copyCode(this)">copy</button></div><code>#[cfg(test)] // only compile this module during `cargo test`
mod tests {
use super::*; // import everything from the parent module
#[test] // marks this function as a test
fn test_fade_in_reaches_max() {
let mut anim = FadeAnimator::new();
anim.start_fade_in();
assert!(anim.is_animating()); // panic if false
let mut ticks = 0;
while anim.tick() {
ticks += 1;
assert!(ticks < 20, "Fade-in should complete within 20 ticks");
}
assert_eq!(anim.current_alpha, ALPHA_MAX); // assert equality
}
}</code></pre>
<h3>Test helpers</h3>
<pre><div class="code-header"><span class="lang">rust</span><button class="copy-btn" onclick="copyCode(this)">copy</button></div><code>// Create fake HWNDs for testing
fn hwnd(n: isize) -> HWND {
HWND(n as *mut _)
}
// Create test WindowInfo with a letter
fn make_window_info(hwnd_n: isize, letter: char) -> WindowInfo {
let mut w = WindowInfo::new(hwnd(hwnd_n), format!("Window {}", hwnd_n), false, 0);
w.letter = Some(letter);
w
}</code></pre>
<h3>Testable vs Win32-coupled</h3>
<table>
<tr><th>File</th><th>Win32 dependency</th><th>Tests</th></tr>
<tr><td><code>grid_layout.rs</code></td><td>None</td><td>8 tests</td></tr>
<tr><td><code>animation.rs</code></td><td>None</td><td>5 tests</td></tr>
<tr><td><code>interaction.rs</code></td><td>Minimal</td><td>15+ tests</td></tr>
<tr><td><code>letter_assignment.rs</code></td><td>None</td><td>6 tests</td></tr>
<tr><td><code>overlay.rs</code></td><td>Heavy</td><td>Manual only</td></tr>
</table>
<pre><div class="code-header"><span class="lang">bash</span></div><code>cargo test # all tests
cargo test config::tests # only config module tests
cargo test -- --test-threads=1 # if tests share Win32 state
cargo test -- --nocapture # show println! output</code></pre>
</section>
<!-- ══════════════════════════════════════════════ -->
<!-- CHAPTER 14 -->
<!-- ══════════════════════════════════════════════ -->
<section class="chapter" id="ch-14" data-chapter="14">
<h2><span class="ch-badge">14</span> Design Patterns</h2>
<details open>
<summary>Pattern 1: State Machine</summary>
<div class="detail-body">
<div class="diagram">
<pre><code> ┌──────────────────────────────────┐
│ │
▼ │
Hidden ──hotkey──→ FadingIn ──done──→ Active
▲ │ │
│ │ (dismiss) │ (escape / select+enter)
│ ▼ ▼
└───────────── FadingOut ◄──────────┘
│
│ done + target?
▼
switch_to_window()</code></pre>
</div>
<p><code>state.rs</code> defines the enum. <code>main.rs</code> checks it before every action.</p>
</div>
</details>
<details>
<summary>Pattern 2: RAII (Resource Acquisition Is Initialization)</summary>
<div class="detail-body">
<p>Resources are tied to object lifetimes. When the object is dropped, the resource is released.</p>
<table>
<tr><th>Resource</th><th>Type</th><th>Cleanup in <code>Drop</code></th></tr>
<tr><td>DWM thumbnail</td><td><code>ThumbnailHandle</code></td><td><code>DwmUnregisterThumbnail</code></td></tr>
<tr><td>WinEvent hook</td><td><code>MruTracker</code></td><td><code>UnhookWinEvent</code></td></tr>
<tr><td>Thread attachment</td><td><code>ThreadInputGuard</code></td><td><code>AttachThreadInput(false)</code></td></tr>
</table>
</div>
</details>
<details>
<summary>Pattern 3: Callback Context via Raw Pointer</summary>
<div class="detail-body">
<p>Win32 callbacks don't support closures. The pattern is:</p>
<ol>
<li>Create a context struct</li>
<li>Cast its pointer to <code>LPARAM</code> (integer)</li>
<li>In the callback, cast <code>LPARAM</code> back to a pointer</li>
</ol>
<p>Used in: <code>window_enumerator.rs</code>, <code>monitor.rs</code>, <code>mru_tracker.rs</code>.</p>
</div>
</details>
<details>
<summary>Pattern 4: Global State via Atomic</summary>
<div class="detail-body">
<p>Instead of <code>static mut</code> (unsound), the codebase uses atomics. The safety invariant: <strong>only the message pump thread accesses these.</strong></p>
<pre><div class="code-header"><span class="lang">rust</span></div><code>static APP_STATE_PTR: AtomicUsize = AtomicUsize::new(0); // main.rs
static OVERLAY_WNDPROC_PTR: AtomicUsize = ...; // overlay.rs
static HOOK_HANDLE: AtomicUsize = ...; // keyboard_hook.rs
static HOOK_ACTIVE: AtomicBool = ...; // keyboard_hook.rs
static MRU_TRACKER_CELL: OnceLock<SendPtr> = ...; // mru_tracker.rs</code></pre>
</div>
</details>
<details>
<summary>Pattern 5: Command Pattern</summary>
<div class="detail-body">
<p><code>handle_key_down()</code> returns a <code>KeyAction</code> enum instead of performing the action directly. This separates <strong>decision</strong> from <strong>execution</strong>:</p>
<div class="diagram">
<pre><code>Input (vk_code) → handle_key_down() → KeyAction::Select(3)
↓
main.rs match block → actually updates state</code></pre>
</div>
<p>Benefits: <code>handle_key_down()</code> is testable (returns a value, doesn't mutate global state). The caller decides HOW to execute.</p>
</div>
</details>
<details>
<summary>Pattern 6: Snapshot Pattern</summary>
<div class="detail-body">
<p>At overlay activation, the entire window list is snapshotted into <code>Vec<WindowInfo></code>. All subsequent operations work on this snapshot — not live Win32 state. This prevents windows appearing/disappearing mid-interaction.</p>
</div>
</details>
<details>
<summary>Pattern 7: Layered Windows (Two-HWND Rendering)</summary>
<div class="detail-body">
<div class="diagram">
<pre><code>Z-order (top to bottom):
┌─────────────────────────────┐
│ Label Overlay (GDI) │ ← letter badges, number tags
│ WS_EX_TRANSPARENT │ click-through, color-keyed
├─────────────────────────────┤
│ DWM Thumbnails │ ← live window previews
│ (composited by Windows) │ rendered above the D2D surface
├─────────────────────────────┤
│ Primary Overlay (Direct2D) │ ← backdrop, cell backgrounds, aura glow
│ WS_EX_LAYERED │ alpha-blended
└─────────────────────────────┘</code></pre>
</div>