rigortype 0.2.1 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +41 -14
  3. data/docs/handbook/01-getting-started.md +311 -0
  4. data/docs/handbook/02-everyday-types.md +337 -0
  5. data/docs/handbook/03-narrowing.md +359 -0
  6. data/docs/handbook/04-tuples-and-shapes.md +321 -0
  7. data/docs/handbook/05-methods-and-blocks.md +339 -0
  8. data/docs/handbook/06-classes.md +305 -0
  9. data/docs/handbook/07-rbs-and-extended.md +427 -0
  10. data/docs/handbook/08-understanding-errors.md +373 -0
  11. data/docs/handbook/09-plugins.md +241 -0
  12. data/docs/handbook/10-sorbet.md +347 -0
  13. data/docs/handbook/11-sig-gen.md +312 -0
  14. data/docs/handbook/12-lightweight-hkt.md +333 -0
  15. data/docs/handbook/README.md +275 -0
  16. data/docs/handbook/appendix-elixir.md +370 -0
  17. data/docs/handbook/appendix-go.md +399 -0
  18. data/docs/handbook/appendix-java-csharp.md +470 -0
  19. data/docs/handbook/appendix-liskov.md +580 -0
  20. data/docs/handbook/appendix-mypy.md +370 -0
  21. data/docs/handbook/appendix-phpstan.md +338 -0
  22. data/docs/handbook/appendix-protocols-and-structural-typing.md +292 -0
  23. data/docs/handbook/appendix-rust.md +446 -0
  24. data/docs/handbook/appendix-steep.md +336 -0
  25. data/docs/handbook/appendix-type-theory.md +1662 -0
  26. data/docs/handbook/appendix-typeprof.md +416 -0
  27. data/docs/handbook/appendix-typescript.md +332 -0
  28. data/docs/install.md +189 -0
  29. data/docs/llms.txt +72 -0
  30. data/docs/manual/01-installation.md +342 -0
  31. data/docs/manual/02-cli-reference.md +557 -0
  32. data/docs/manual/03-configuration.md +152 -0
  33. data/docs/manual/04-diagnostics.md +206 -0
  34. data/docs/manual/05-inspecting-types.md +109 -0
  35. data/docs/manual/06-baseline.md +104 -0
  36. data/docs/manual/07-plugins.md +92 -0
  37. data/docs/manual/08-skills.md +143 -0
  38. data/docs/manual/09-editor-integration.md +245 -0
  39. data/docs/manual/10-mcp-server.md +532 -0
  40. data/docs/manual/11-ci.md +274 -0
  41. data/docs/manual/12-caching.md +116 -0
  42. data/docs/manual/13-troubleshooting.md +120 -0
  43. data/docs/manual/14-rails-quickstart.md +332 -0
  44. data/docs/manual/15-type-protection-coverage.md +204 -0
  45. data/docs/manual/16-rbs-extended-annotations.md +190 -0
  46. data/docs/manual/17-driving-improvement.md +160 -0
  47. data/docs/manual/README.md +87 -0
  48. data/docs/manual/ci-templates/README.md +58 -0
  49. data/docs/manual/plugins/README.md +86 -0
  50. data/docs/manual/plugins/rigor-actioncable.md +78 -0
  51. data/docs/manual/plugins/rigor-actionmailer.md +74 -0
  52. data/docs/manual/plugins/rigor-actionpack.md +80 -0
  53. data/docs/manual/plugins/rigor-activejob.md +58 -0
  54. data/docs/manual/plugins/rigor-activerecord.md +102 -0
  55. data/docs/manual/plugins/rigor-activestorage.md +74 -0
  56. data/docs/manual/plugins/rigor-activesupport-core-ext.md +86 -0
  57. data/docs/manual/plugins/rigor-devise.md +70 -0
  58. data/docs/manual/plugins/rigor-dry-schema.md +56 -0
  59. data/docs/manual/plugins/rigor-dry-struct.md +60 -0
  60. data/docs/manual/plugins/rigor-dry-types.md +59 -0
  61. data/docs/manual/plugins/rigor-dry-validation.md +62 -0
  62. data/docs/manual/plugins/rigor-factorybot.md +76 -0
  63. data/docs/manual/plugins/rigor-graphql.md +89 -0
  64. data/docs/manual/plugins/rigor-hanami.md +83 -0
  65. data/docs/manual/plugins/rigor-mangrove.md +73 -0
  66. data/docs/manual/plugins/rigor-minitest.md +86 -0
  67. data/docs/manual/plugins/rigor-pundit.md +72 -0
  68. data/docs/manual/plugins/rigor-rails-i18n.md +92 -0
  69. data/docs/manual/plugins/rigor-rails-routes.md +94 -0
  70. data/docs/manual/plugins/rigor-rails.md +44 -0
  71. data/docs/manual/plugins/rigor-rbs-inline.md +83 -0
  72. data/docs/manual/plugins/rigor-rspec-rails.md +72 -0
  73. data/docs/manual/plugins/rigor-rspec.md +86 -0
  74. data/docs/manual/plugins/rigor-shoulda-matchers.md +78 -0
  75. data/docs/manual/plugins/rigor-sidekiq.md +78 -0
  76. data/docs/manual/plugins/rigor-sinatra.md +61 -0
  77. data/docs/manual/plugins/rigor-sorbet.md +63 -0
  78. data/docs/manual/plugins/rigor-statesman.md +75 -0
  79. data/docs/manual/plugins/rigor-typescript-utility-types.md +71 -0
  80. data/exe/rigor +1 -1
  81. data/lib/rigor/analysis/incremental_session.rb +4 -2
  82. data/lib/rigor/analysis/run_stats.rb +13 -1
  83. data/lib/rigor/analysis/runner.rb +54 -12
  84. data/lib/rigor/cli/check_command.rb +1 -1
  85. data/lib/rigor/cli/docs_command.rb +248 -0
  86. data/lib/rigor/cli/skill_command.rb +103 -41
  87. data/lib/rigor/cli/skill_describe.rb +346 -0
  88. data/lib/rigor/cli.rb +25 -3
  89. data/lib/rigor/inference/method_dispatcher/constant_folding.rb +124 -32
  90. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +37 -6
  91. data/lib/rigor/inference/scope_indexer.rb +87 -89
  92. data/lib/rigor/plugin/isolation.rb +5 -5
  93. data/lib/rigor/plugin/loader.rb +4 -2
  94. data/lib/rigor/version.rb +1 -1
  95. data/skills/rigor-ask/SKILL.md +172 -0
  96. data/skills/rigor-doctor/SKILL.md +87 -0
  97. data/skills/rigor-editor-setup/SKILL.md +114 -0
  98. data/skills/rigor-mcp-setup/SKILL.md +117 -0
  99. data/skills/rigor-monkeypatch-resolve/SKILL.md +79 -0
  100. data/skills/rigor-next-steps/SKILL.md +113 -0
  101. data/skills/rigor-plugin-tune/SKILL.md +79 -0
  102. data/skills/rigor-protection-uplift/SKILL.md +133 -0
  103. data/skills/rigor-rbs-setup/SKILL.md +128 -0
  104. data/skills/rigor-upgrade/SKILL.md +79 -0
  105. metadata +90 -1
@@ -0,0 +1,399 @@
1
+ # Appendix — Coming from Go
2
+
3
+ If your mental model of "types" was set by Go, this appendix
4
+ maps Rigor's vocabulary onto the concepts you already know. The
5
+ two have a real point of contact that surprises people: Go's
6
+ *implicitly satisfied* interface is exactly how Rigor's
7
+ structural typing works. The single most-misunderstood feature
8
+ in Rigor — "where do I declare that this class implements the
9
+ interface?" — is the one a Go programmer already understands by
10
+ reflex, the way you always did in Go.
11
+
12
+ This is a translation table plus a discussion of where the two
13
+ diverge. Go is small, compiled, and deliberately spare —
14
+ no sum types, no inheritance, errors as values, zero values
15
+ everywhere. Rigor adds the things Go leaves out (unions,
16
+ refinements, literal types, nil-narrowing) and drops the things
17
+ Go needs as a compiled language (the build gate itself).
18
+
19
+ ## The five-second pitch
20
+
21
+ | Question | Go | Rigor |
22
+ | --- | --- | --- |
23
+ | Interface membership | Implicit — have the methods, satisfy the interface | Implicit — same structural rule |
24
+ | When does the checker run? | At compile time; nothing runs until it passes | After the fact, on code that already runs |
25
+ | The "I don't know" type | `interface{}` / `any` | `Dynamic[top]` — direct analogue |
26
+ | Where do annotations live? | In source; `:=` infers locals | In `.rbs` files; whole bodies inferred |
27
+ | Sum types | None — Go has no union types | First-class `T \| U` unions |
28
+ | Errors | Returned as values (`(T, error)`) | Raised; not tracked in the signature |
29
+
30
+ Go and Rigor share the structural-typing instinct and the
31
+ `any`-as-escape-hatch instinct. They part ways on the build
32
+ model: Go's checker is a gate the program passes before it
33
+ exists, while Rigor's is an advisor over a program that already
34
+ runs. They also part ways on richness, where Go is intentionally
35
+ minimal and Rigor adds unions, literal types, and refinements.
36
+
37
+ ## Type vocabulary mapping
38
+
39
+ | Go | Rigor | Notes |
40
+ | --- | --- | --- |
41
+ | `int` / `int64` / `uint` | `Integer` | Ruby integers are arbitrary-precision; no width or signedness. |
42
+ | `float64` / `float32` | `Float` | `Numeric` is the common supertype. |
43
+ | `bool` | `bool` (`Constant<true> \| Constant<false>`) | Structurally a union of two constants. |
44
+ | `string` | `String` | |
45
+ | `byte` / `rune` | `Integer` | Ruby has no distinct byte/rune type. |
46
+ | `nil` | `nil` (`Constant<nil>`) | Go's typed-nil subtleties have no Ruby analogue — there is one `nil`. |
47
+ | `interface{}` / `any` | `Dynamic[top]` | The "be silent here" carrier — a direct match. |
48
+ | `error` | (no type — Ruby raises) | See [Errors as values](#errors-as-values-vs-raising). |
49
+ | `[]T` (slice) | `Array[T]` | |
50
+ | `[N]T` (array) | `Tuple[…]` or `Array[T]` | A fixed-length literal gets a `Tuple`; otherwise `Array[T]`. |
51
+ | `map[K]V` | `Hash[K, V]` | |
52
+ | `struct{ X int; Y int }` | `Point = Data.define(:x, :y)` | See [Structs ↔ Data.define](#structs--datadefine). |
53
+ | `interface{ Draw() string }` | RBS `interface` (structural) | The direct analogue — see below. |
54
+ | `*T` (pointer, nil-able) | `T?` (i.e. `T \| nil`) | A nil-able pointer narrows like `T?`. |
55
+ | `iota` const group | `Constant<…>` union | Go's enum idiom; Rigor uses a union of constants or symbols. |
56
+ | `[T any]` (generics, 1.18+) | RBS `[T]` type parameter | |
57
+ | (no sum types) | `T \| U` | Go has no unions at all; a Rigor addition. |
58
+ | (no literal types) | `Constant<42>` / `Constant<"hi">` | Go has no literal types; a Rigor novelty. |
59
+
60
+ ## Structural interfaces — the part you already know
61
+
62
+ This is the section that makes Go programmers feel at home. In
63
+ Go, a type satisfies an interface by *having its methods* — no
64
+ `implements` keyword, no declaration of intent:
65
+
66
+ ```go
67
+ type Drawable interface { Draw() string }
68
+
69
+ type Button struct{ /* … */ }
70
+ func (b Button) Draw() string { return "[button]" }
71
+ // Button satisfies Drawable. You never said so.
72
+ ```
73
+
74
+ Rigor's RBS `interface` works exactly this way:
75
+
76
+ ```ruby
77
+ # An RBS structural interface
78
+ interface _Drawable
79
+ def draw: () -> String
80
+ end
81
+ ```
82
+
83
+ Any Ruby object that responds to `draw` returning a `String`
84
+ satisfies `_Drawable` — you never write `implements`, never
85
+ declare conformance. This is the **one** word in Rigor that
86
+ trips up readers arriving from Java or C#, where `interface`
87
+ means a *nominal* contract you must declare. For you it needs no
88
+ explanation: it is Go's interface, including the convention of
89
+ keeping interfaces small.
90
+
91
+ Rigor goes one step further than Go: it infers anonymous object
92
+ *shapes* and capability roles even when no interface is
93
+ declared at all — the equivalent of Go inferring `interface{
94
+ Draw() string }` from how you used a value. The
95
+ [structural-typing appendix](appendix-protocols-and-structural-typing.md)
96
+ is the canonical explainer.
97
+
98
+ ## Narrowing — type switch / assertion
99
+
100
+ Go narrows an `interface{}` value through type assertions and
101
+ type switches. Rigor has direct analogues.
102
+
103
+ | Go | Rigor |
104
+ | --- | --- |
105
+ | `if x != nil` | `if x` (strips `nil`), or `unless x.nil?` |
106
+ | `v, ok := x.(string)` (comma-ok assertion) | `x.is_a?(String)` narrowing in an `if` |
107
+ | `switch v := x.(type) { case string: … }` | `case x; in String => v` |
108
+ | `x.(T)` (assertion, panics on fail) | (no panicking assertion) — `is_a?` guard, or `T.cast` via [`rigor-sorbet`](../../plugins/rigor-sorbet/) |
109
+ | user func returning `bool` | `%a{rigor:v1:predicate-if-true x is Foo}` directive |
110
+
111
+ Go's type switch is *not* exhaustive — you can omit cases and
112
+ fall through `default` — and neither is Rigor's `case`. Both
113
+ report what they can prove, not what you forgot. (Where Rigor
114
+ adds value is the dual: if a `case`/`in` clause can *never*
115
+ match because earlier clauses already covered its type, the
116
+ `flow.unreachable-clause` rule from
117
+ [ADR-47](../adr/47-narrowing-driven-clause-reachability.md) says
118
+ so.)
119
+
120
+ ## Errors as values vs raising
121
+
122
+ Go's defining idiom — `(T, error)` returned together, checked
123
+ with `if err != nil` — has no direct Rigor analogue, because
124
+ Ruby signals failure by *raising*, not returning. A Go function
125
+ becomes a Ruby method that returns `T` and raises on the error
126
+ path:
127
+
128
+ ```go
129
+ func parse(s string) (int, error) { return strconv.Atoi(s) }
130
+
131
+ n, err := parse("x")
132
+ if err != nil { return err }
133
+ ```
134
+
135
+ ```ruby
136
+ def parse(s) # returns Integer, raises on bad input
137
+ Integer(s)
138
+ end
139
+
140
+ n = parse("x") # raises ArgumentError on the error path
141
+ ```
142
+
143
+ Rigor does not track the exception as part of the signature —
144
+ there is no typed error return and no `if err != nil` shape to
145
+ narrow. If you want to keep Go's value-returning style, you
146
+ *can* return a `Tuple` (`[value, error]` or `[:ok, value]` /
147
+ `[:error, reason]`) and pattern-match it with `case`/`in`;
148
+ Rigor types the `Tuple` and the union precisely. But that is a
149
+ port of the Go idiom, not idiomatic Ruby.
150
+
151
+ ## nil, zero values, and absence
152
+
153
+ Go has no null-the-type, but it has nil-able pointers, maps,
154
+ slices, and interfaces — and *zero values*: an unset `int` is
155
+ `0`, an unset `string` is `""`, an unset pointer is `nil`.
156
+
157
+ Ruby has no zero-value rule. An unset instance variable reads
158
+ `nil`; an unset *local* raises `NameError` (it does not silently
159
+ become a zero). So:
160
+
161
+ - A Go nil-able `*T` maps onto Rigor's `T?` — `T | nil`,
162
+ narrowed by an `x.nil?` / `if x` guard, the way you write
163
+ `if p != nil` in Go.
164
+ - Go's zero-value-on-declare convenience has no Ruby analogue;
165
+ Ruby forces you to assign before use, and Rigor's flow
166
+ analysis tracks that a never-assigned local is not in scope.
167
+
168
+ The upshot: the `if err != nil` / `if ptr != nil` reflex
169
+ translates straight to `unless x.nil?`, and Rigor narrows the
170
+ non-nil branch exactly as you would expect.
171
+
172
+ ## Structs ↔ `Data.define`
173
+
174
+ A Go `struct` used as an immutable value maps onto Ruby's
175
+ `Data.define` — value-equal, member-shaped, frozen. Rigor models
176
+ it natively ([ADR-48](../adr/48-data-struct-value-folding.md)).
177
+
178
+ ```go
179
+ type Point struct{ X, Y int }
180
+ p := Point{X: 1, Y: 2}
181
+ a := p.X // a : int
182
+ q := p
183
+ q.X = 9 // Go structs are mutable; this copies-then-mutates
184
+ ```
185
+
186
+ ```ruby
187
+ Point = Data.define(:x, :y)
188
+ p = Point.new(1, 2)
189
+ assert_type("1", p.x) # member value is folded, not just Integer
190
+ q = p.with(x: 9) # immutable update — returns a new Point
191
+ ```
192
+
193
+ Two notes for a Go reader:
194
+
195
+ - **Member values fold.** `Point.new(1, 2).x` is `1`,
196
+ not merely `Integer`. Go erases the literal; Rigor keeps it
197
+ (subject to the folding budget).
198
+ - **`Data` is immutable.** Unlike a Go `struct`, `Data.define`
199
+ produces frozen values; you get a new one via `Data#with`. If
200
+ you need Go's mutable struct, Ruby's `Struct` is the closer
201
+ fit — but Rigor does not yet value-fold `Struct`, because its
202
+ mutability breaks the soundness story (see
203
+ [Chapter 6](06-classes.md)).
204
+
205
+ ## Sum types Go doesn't have
206
+
207
+ This is the gap that bites Go programmers most, and the place
208
+ Rigor most clearly adds something. Go has **no union types** —
209
+ no way to say "this is a `Circle` or a `Rectangle`." The
210
+ workarounds are a sealed interface with unexported methods, or
211
+ an `interface{}` plus a type switch with a `default: panic`.
212
+
213
+ Rigor has first-class unions, and a closed set of variants is
214
+ simply a union of `Data.define` types dispatched with `case`/`in`:
215
+
216
+ ```ruby
217
+ Circle = Data.define(:radius)
218
+ Rectangle = Data.define(:w, :h)
219
+
220
+ def area(s) # s : Circle | Rectangle
221
+ case s
222
+ in Circle then Math::PI * s.radius * s.radius
223
+ in Rectangle then s.w * s.h
224
+ end
225
+ end
226
+ ```
227
+
228
+ No marker interface, no `panic` default. The union is a real
229
+ type Rigor tracks, and `in Circle` narrows `s` to `Circle` along
230
+ that clause. This is the single biggest expressiveness gain
231
+ moving from Go's type system to Rigor's.
232
+
233
+ ## Refinements and defined types
234
+
235
+ Go's `type Celsius float64` makes a *nominally distinct* type
236
+ from `float64` — but it cannot say "a positive `Celsius`" or "a
237
+ non-empty string." Rigor cannot reproduce Go's defined-type
238
+ nominal distinctness (`type Celsius float64` collapses to
239
+ `Float` in Rigor), but it adds the thing Go lacks: refinement
240
+ carriers that encode an invariant on the value.
241
+
242
+ | Rigor refinement | Go idiom | Comment |
243
+ | --- | --- | --- |
244
+ | `non-empty-string` | runtime `if len(s) == 0` check | Rigor produces it from `unless s.empty?`. |
245
+ | `positive-int` | runtime `if n <= 0` check | Rigor narrows from `n > 0`. |
246
+ | `int<1, 9>` | runtime range check | Rigor's range carrier handles arbitrary bounds. |
247
+ | `numeric-string` | `strconv.Atoi` + error check | No type-level analogue in Go. |
248
+ | `non-empty-array[T]` | runtime `len(xs) == 0` check | Rigor produces it from `unless arr.empty?`. |
249
+
250
+ So the trade runs both ways: Go gives you nominal newtypes
251
+ Rigor flattens, and Rigor gives you value-level refinements Go
252
+ has to check at runtime.
253
+
254
+ ## Generics
255
+
256
+ Go added generics in 1.18; RBS has had them longer, and Rigor
257
+ reads them. The surfaces are close for the common cases.
258
+
259
+ | Go | Rigor (via RBS) |
260
+ | --- | --- |
261
+ | `func Id[T any](x T) T` | `def id: [T] (T) -> T` |
262
+ | `[]T` | `Array[T]` |
263
+ | `map[K]V` | `Hash[K, V]` |
264
+ | `[T constraints.Ordered]` | `[T < Comparable[T]]` bound |
265
+ | type sets / unions in constraints | RBS bounds (narrower) |
266
+
267
+ Go's constraint type-sets and Rigor's RBS bounds are both
268
+ modest by design; neither tries to be as type-level-expressive
269
+ as a fully dependent system.
270
+
271
+ ## Severity, suppression, and "strict mode"
272
+
273
+ | Go | Rigor |
274
+ | --- | --- |
275
+ | `go vet` / linter severity | `severity_profile: lenient` / `balanced` / `strict` |
276
+ | `//nolint:rule` (golangci-lint) | `# rigor:disable <rule>` |
277
+ | file-level lint disable | `# rigor:disable-file all` |
278
+ | `go build` (the gate) | `rigor check lib` (the advisor) |
279
+
280
+ The mental shift: `go build` is the gate — code does not ship
281
+ until it compiles. `rigor check` is an advisor over code that
282
+ already runs, tuned with severity profiles and adopted
283
+ incrementally via baselines.
284
+
285
+ ## What Go has and Rigor does not
286
+
287
+ Be honest about what you give up:
288
+
289
+ - **The compile gate.** Go does not run until it builds. Rigor
290
+ is advisory; the program runs regardless of what it reports.
291
+ - **Defined-type nominal distinctness.** `type Celsius float64`
292
+ is a distinct type in Go; Rigor flattens it to `Float`.
293
+ - **Goroutines and channel types.** `chan T`, `select`, the
294
+ concurrency type machinery — no Rigor analogue.
295
+ - **Zero values.** Go's declare-and-it-is-zero convenience has
296
+ no place in Ruby's assign-before-use model.
297
+ - **A single binary and compile-time guarantees.** Compile-model
298
+ benefits a runtime advisor does not provide.
299
+
300
+ ## What Rigor has and Go does not
301
+
302
+ The other direction — and for Go the list is long, because Go
303
+ is deliberately minimal:
304
+
305
+ - **Union / sum types.** The big one: `T | U` is first-class in
306
+ Rigor and absent from Go. Closed variant sets need no marker
307
+ interface.
308
+ - **Literal / constant types.** `Constant<42>`, `Constant<:ok>`,
309
+ `Constant<"FOO">`. Go has no literal types; the nearest is an
310
+ `iota` const group.
311
+ - **Constant folding through method calls.** `"foo".upcase` is
312
+ `Constant<"FOO">`, not `string`.
313
+ - **Refinements.** `non-empty-string`, `positive-int`,
314
+ `int<1, 9>` — invariants on the value, no runtime check needed
315
+ to know them statically.
316
+ - **Inferred shapes beyond interfaces.** Rigor infers anonymous
317
+ object shapes and capability roles, not just satisfaction of a
318
+ declared `interface`.
319
+ - **No-false-positives stance.** Silent on `Dynamic[top]`
320
+ receivers rather than complaining; the `interface{}` you
321
+ haven't narrowed yet costs you nothing.
322
+ - **No annotation tax beyond `:=`.** Go infers `:=` locals;
323
+ Rigor infers whole method bodies *and* across `def`
324
+ boundaries. A zero-`.rbs` project still yields useful
325
+ diagnostics.
326
+
327
+ ## A migration vignette
328
+
329
+ You are porting a Go package — an interface, a couple of
330
+ implementers, and a constructor that can fail — to Ruby. The
331
+ original:
332
+
333
+ ```go
334
+ type Shape interface{ Area() float64 }
335
+
336
+ type Circle struct{ Radius float64 }
337
+ func (c Circle) Area() float64 { return math.Pi * c.Radius * c.Radius }
338
+
339
+ type Rectangle struct{ W, H float64 }
340
+ func (r Rectangle) Area() float64 { return r.W * r.H }
341
+
342
+ func parseRadius(s string) (float64, error) { return strconv.ParseFloat(s, 64) }
343
+ ```
344
+
345
+ The Rigor approach — duck-typed implementers, an inferred
346
+ structural interface, and a raising parser:
347
+
348
+ ```ruby
349
+ # lib/shape.rb
350
+ Circle = Data.define(:radius) do
351
+ def area = Math::PI * radius * radius
352
+ end
353
+
354
+ Rectangle = Data.define(:w, :h) do
355
+ def area = w * h
356
+ end
357
+
358
+ def parse_radius(s) # returns Float, raises on bad input
359
+ Float(s)
360
+ end
361
+ ```
362
+
363
+ What carries over and what changes:
364
+
365
+ - The `Shape` interface needs no Ruby declaration. Any object
366
+ with `area` satisfies the structural interface; if you *want*
367
+ to name it, an RBS `interface _Shape` works exactly like Go's,
368
+ satisfied implicitly.
369
+ - `Circle` and `Rectangle` become `Data.define` values with
370
+ methods — immutable where Go's structs were mutable.
371
+ - `(float64, error)` becomes a `Float` return that raises. The
372
+ `if err != nil` check at the call site becomes a `rescue`, or
373
+ you keep the value-style by returning a `Tuple` and matching
374
+ it with `case`/`in`.
375
+ - If you dispatch over the shapes with a `case`/`in`, you get
376
+ the union type Go could not express — and Rigor's
377
+ `flow.unreachable-clause` will flag a clause that can never
378
+ match.
379
+
380
+ ## What's next
381
+
382
+ You probably do not need to read the rest of the handbook
383
+ sequentially. Useful pointers:
384
+
385
+ - [Protocols and structural typing](appendix-protocols-and-structural-typing.md)
386
+ — the canonical page on RBS interfaces, the direct analogue to
387
+ Go's implicit interfaces, and how they differ from ADR-28
388
+ protocol contracts.
389
+ - [Chapter 3 — Narrowing](03-narrowing.md) for the flow rules —
390
+ the analogue to type switches and assertions.
391
+ - [Chapter 6 — Classes](06-classes.md) for `Data.define`, the
392
+ `struct` analogue, and its value-folding.
393
+
394
+ If you want to compare against another tool, the sibling
395
+ appendix pages cover [TypeScript](appendix-typescript.md),
396
+ [PHPStan](appendix-phpstan.md), [mypy](appendix-mypy.md),
397
+ [Steep](appendix-steep.md), [TypeProf](appendix-typeprof.md),
398
+ [Java / C#](appendix-java-csharp.md), [Rust](appendix-rust.md),
399
+ and [Elixir](appendix-elixir.md).