|
| 1 | +--- |
| 2 | +layout: fable-blog-page |
| 3 | +title: Announcing Fable 5 Release Candidate |
| 4 | +author: Mangel Maxime |
| 5 | +date: 2026-02-27 |
| 6 | +author_link: https://twitter.com/MangelMaxime |
| 7 | +author_image: https://github.com/MangelMaxime.png |
| 8 | +# external_link: |
| 9 | +abstract: | |
| 10 | + Fable 5 RC is Here: New Targets, Better Tooling, and Nullness Support |
| 11 | +--- |
| 12 | + |
| 13 | +More than a year after the first alpha release, we are happy to announce that Fable 5 is now in the release candidate stage! |
| 14 | + |
| 15 | +You can install Fable 5 by running the following command: |
| 16 | + |
| 17 | +```bash |
| 18 | +# If you haven't installed a dotnet tool in this directory yet |
| 19 | +dotnet new tool-manifest |
| 20 | + |
| 21 | +dotnet tool install fable --prerelease |
| 22 | +``` |
| 23 | + |
| 24 | +If you already have Fable installed, you can update it by running: |
| 25 | + |
| 26 | +```bash |
| 27 | +dotnet tool update fable --prerelease |
| 28 | +``` |
| 29 | + |
| 30 | +:::info |
| 31 | +When upgrading, make sure to upgrade your dependencies as well. |
| 32 | +::: |
| 33 | + |
| 34 | +## Compatibility with Fable 4 |
| 35 | + |
| 36 | +Fable 5 is compatible with Fable 4 projects, except that it now targets `net10.0`. |
| 37 | + |
| 38 | +## Project cracking |
| 39 | + |
| 40 | +We removed support for the old project cracker using Buildalyzer. This has been replaced by invoking MSBuild directly, which should be more robust for the future. |
| 41 | + |
| 42 | +## `WarnAsError` support |
| 43 | + |
| 44 | +It has been a long-standing request to support `<TreatWarningsAsErrors>true</TreatWarningsAsErrors>` in Fable, and we are happy to announce that it is now supported in Fable 5. |
| 45 | + |
| 46 | +You can enable it in your project file like this: |
| 47 | + |
| 48 | +```xml |
| 49 | +<PropertyGroup> |
| 50 | + <TreatWarningsAsErrors>true</TreatWarningsAsErrors> |
| 51 | +</PropertyGroup> |
| 52 | +``` |
| 53 | + |
| 54 | +Fable will now treat all warnings from **your** code as errors, but it will still allow warnings from dependencies. |
| 55 | + |
| 56 | +This behavior should mimic how the standard F# compiler works. |
| 57 | + |
| 58 | +## .NET 10 and F# 10 support |
| 59 | + |
| 60 | +Fable 5 adds support for the following F# features: |
| 61 | + |
| 62 | +* [Nullable reference types](https://learn.microsoft.com/en-us/dotnet/fsharp/whats-new/fsharp-9#nullable-reference-types) |
| 63 | +* [Discriminated union <code>.Is*</code> properties](https://learn.microsoft.com/en-us/dotnet/fsharp/whats-new/fsharp-9#discriminated-union-is-properties) |
| 64 | +* [Partial active patterns can return <code>bool</code> instead of <code>unit option</code>](https://learn.microsoft.com/en-us/dotnet/fsharp/whats-new/fsharp-9#partial-active-patterns-can-return-bool-instead-of-unit-option) |
| 65 | +* [Empty-bodied computation expressions](https://learn.microsoft.com/en-us/dotnet/fsharp/whats-new/fsharp-9#empty-bodied-computation-expressions) |
| 66 | +* [Updates to the standard library (FSharp.Core)](https://learn.microsoft.com/en-us/dotnet/fsharp/whats-new/fsharp-9#updates-to-the-standard-library-fsharpcore) |
| 67 | + |
| 68 | +## JavaScript / TypeScript |
| 69 | + |
| 70 | +The JavaScript target is still the most used and stable target, and we have continued to improve it over the past year. |
| 71 | + |
| 72 | +### Support for direct nested types when using `jsOptions` |
| 73 | + |
| 74 | +```fs |
| 75 | + let opts = |
| 76 | + jsOptions<Level1> (fun o -> |
| 77 | + o.level2.level3.valueA <- 10 |
| 78 | + o.level2.level3.valueB <- 20 |
| 79 | + o.topValueA <- 20 |
| 80 | + ) |
| 81 | +``` |
| 82 | + |
| 83 | +generates |
| 84 | + |
| 85 | +```js |
| 86 | +export const opts = { |
| 87 | + level2: { |
| 88 | + level3: { |
| 89 | + valueA: 10, |
| 90 | + valueB: 20, |
| 91 | + }, |
| 92 | + }, |
| 93 | + topValueA: 20, |
| 94 | +}; |
| 95 | +``` |
| 96 | + |
| 97 | +### Simplify `Pojo` bindings |
| 98 | + |
| 99 | +Fable has a number of ways to create [Plain Old JavaScript Objects](https://fable.io/docs/javascript/features.html#plain-old-javascript-objects) (POJOs). |
| 100 | + |
| 101 | +In Fable 4, people discovered a way of abusing a combination of attributes to write POJO bindings in a natural F# way. |
| 102 | + |
| 103 | +```fs |
| 104 | +open Fable.Core |
| 105 | +
|
| 106 | +[<AllowNullLiteral>] |
| 107 | +[<Global>] |
| 108 | +type Options |
| 109 | + [<ParamObject; Emit("$0")>] |
| 110 | + ( |
| 111 | + searchTerm: string, |
| 112 | + ?isCaseSensitive: bool |
| 113 | + ) = |
| 114 | + member val searchTerm: string = jsNative with get, set |
| 115 | + member val isCaseSensitive: bool option = jsNative with get, set |
| 116 | +
|
| 117 | +let options1 = new Options("foo") |
| 118 | +
|
| 119 | +let options2 = new Options("foo", isCaseSensitive = true) |
| 120 | +``` |
| 121 | + |
| 122 | +Improving on this, Fable 5 introduces a new attribute, `Pojo`, that can be used to write the same code with only one attribute: |
| 123 | + |
| 124 | +```fs |
| 125 | +open Fable.Core |
| 126 | +
|
| 127 | +[<AllowNullLiteral>] |
| 128 | +[<JS.Pojo>] |
| 129 | +type Options |
| 130 | + ( |
| 131 | + searchTerm: string, |
| 132 | + ?isCaseSensitive: bool |
| 133 | + ) = |
| 134 | + member val searchTerm: string = jsNative with get, set |
| 135 | + member val isCaseSensitive: bool option = jsNative with get, set |
| 136 | +
|
| 137 | +let options1 = new Options("foo") |
| 138 | +
|
| 139 | +let options2 = new Options("foo", isCaseSensitive = true) |
| 140 | +``` |
| 141 | + |
| 142 | +Both examples generate the same JavaScript code: |
| 143 | + |
| 144 | +```js |
| 145 | +export const options1 = { |
| 146 | + searchTerm: "foo", |
| 147 | +}; |
| 148 | + |
| 149 | +export const options2 = { |
| 150 | + searchTerm: "foo", |
| 151 | + isCaseSensitive: true, |
| 152 | +}; |
| 153 | +``` |
| 154 | + |
| 155 | +## Python |
| 156 | + |
| 157 | +If you have been following the Fable 5 alpha releases, you know the Python target has received a staggering amount of love. |
| 158 | + |
| 159 | +This is all thanks to Dag and the early adopters who have been testing it. |
| 160 | + |
| 161 | +* **Python 3.12-3.14 support** (3.10/3.11 are deprecated) |
| 162 | +* **fable-library via PyPI** - No more bundled runtime files |
| 163 | +* **Modern type parameter syntax** - Better type hinting in generated code |
| 164 | +* **`Py.Decorate` attribute** - Add Python decorators from F# |
| 165 | +* **`Py.ClassAttributes` attribute** - Fine-grained class generation control |
| 166 | +* **Improved Pydantic interop** - First-class support for data validation |
| 167 | + |
| 168 | +### Rust Core with PyO3 |
| 169 | + |
| 170 | +One of the biggest changes is that the core of fable-library is now written in Rust using PyO3. |
| 171 | +The motivation here is **correctness**, not performance: |
| 172 | + |
| 173 | +**Why Rust?** |
| 174 | + |
| 175 | +* **Correct .NET semantics** - Sized/signed integers (int8, int16, int32, int64, uint8, etc.) |
| 176 | +* **Proper overflow behavior** - Matches .NET exactly |
| 177 | +* **Fixed-size arrays** - No more Python list quirks for byte streams |
| 178 | +* **Reliable numerics** - Fable 4's pure Python numerics were a constant source of bugs |
| 179 | + |
| 180 | +### fable-library via PyPI |
| 181 | + |
| 182 | +Before Fable v5, the runtime was bundled in the NuGet package and copied to your output directory. |
| 183 | + |
| 184 | +Now it's a simple pip/uv dependency: |
| 185 | + |
| 186 | +```bash |
| 187 | +# Install with pip |
| 188 | +pip install fable-library |
| 189 | + |
| 190 | +# Or with uv (recommended) |
| 191 | +uv add fable-library |
| 192 | +``` |
| 193 | + |
| 194 | +:::info |
| 195 | +You can learn more about Fable.Python in general in our [documentation](https://fable.io/docs/python/build-and-run.html) or in this [blog post](https://cardamomcode.dev/fable-python#heading-introduction-to-fablepython) |
| 196 | +::: |
| 197 | + |
| 198 | +## Rust |
| 199 | + |
| 200 | +The Rust target kept improving as ell, and is now using Rust 2024 language edition. |
| 201 | + |
| 202 | +## Hello Erlang/BEAM ! |
| 203 | + |
| 204 | +Dag Brattli added a new target for Erlang/BEAM, which is still in its early stages. |
| 205 | + |
| 206 | +He has been testing it using [Fable.Giraffe](https://github.com/dbrattli/Fable.Giraffe) and the first results are promising: |
| 207 | + |
| 208 | +> ## Benchmarks |
| 209 | +> |
| 210 | +> Simple `/ping` endpoint returning "pong", 10,000 requests with 100 concurrent |
| 211 | +> connections (oha): |
| 212 | +> |
| 213 | +> | Metric | BEAM | .NET | Python | |
| 214 | +> |---|---|---|---| |
| 215 | +> | Requests/sec | 124,256 | 70,375 | 4,006 | |
| 216 | +> | Avg latency | 0.79 ms | 1.40 ms | 24.9 ms | |
| 217 | +> | P99 latency | 2.49 ms | 3.50 ms | 34.2 ms | |
| 218 | +
|
| 219 | +Indeed, it seems like Erlang/BEAM is a great target for Fable, and we are excited to see how it evolves in the future. |
| 220 | + |
| 221 | +## Conclusion |
| 222 | + |
| 223 | +Fable 5 would not have been possible without the help of the many people who contributed to it in different ways, from testing, to reporting issues, to contributing code. |
| 224 | + |
| 225 | +Thank you all for your help and support over the last year, and we hope you will continue to help us in the future to make Fable even better! |
0 commit comments