go2cs Roadmap

This page is the plan for what comes next in go2cs: the Go and .NET releases the converted standard library moves to, the platforms it runs on, the real-world Go code it converts beyond the standard library, and the runtime and converter work still ahead. It states what is planned. The completed phases, with the git commits and tags that anchor them, are in Roadmap history. Dates are month or quarter targets.

Where things stand. Every implementable package of the converted standard library validates against Go’s own tests on Windows and Linux, and the same validation is under way on macOS, package by package. go2cs -tests -recurse validates real third-party modules against their own tests the same way, and the first converted module is published as a NuGet package. Validated Test Packages, the validation roster, carries the current state of the standard library, and Validated Modules that of third-party modules.

What that rests on. The converter is guarded by hundreds of Go-versus-C# behavioral test projects. Each one is transpiled, compiled and compared against a committed expected output, and each runnable one is executed with its output compared against Go’s. The whole Go standard library compiles cleanly as .NET assemblies. It reproduces Go built with -tags purego: a managed runtime cannot execute Go’s hand-written assembly, so the portable pure-Go variants of the assembly-backed crypto and hash functions are the faithful target (Conversion Strategies). Compiling is not the same as running correctly, which is what Phase 4 below is for.

Phase 4: validation against Go’s own tests

In short. Phase 4 checks the converted standard library against Go’s own tests, package by package, so that “it works” is measured and not claimed. It is done on Windows and Linux, under way on macOS, and has one planned extension.

Phase 4 converts each package’s _test.go suite and compares it, verdict for verdict, with go test. It is not finished, and it stands in three parts:

How the project moves: release hops

In short. go2cs follows Go and .NET one release at a time, and every move proves every package again. For a user that means each go2cs release names exactly one Go release and one .NET release.

The project advances by release migrations, called hops: moving the converted standard library (the corpus) to a new Go release or to a new .NET runtime, one variable at a time and never both in one hop. Each Go hop re-validates every package against that release’s own tests. This page states which releases come next. Three documents carry the work itself, and this roadmap links to them rather than restating them: PLAN-corpus-upgrade.md, which frames how hops are run, and the two standing runbooks that lead on procedure, GoCorpusMigration.md for a Go release migration and DotNetMigration.md for a .NET one. What each document type is for, and which one wins when two disagree, is in Glossary.md.

Go releases

In short. The converted standard library is on Go 1.24.13, a release Go no longer patches, so it lacks the security fixes Go has issued since. Two planned moves bring it to a supported Go release and keep it there: Go 1.26, straight from 1.24, and then Go 1.27. 1.24.13.7 is the last 1.24 release.

Go 1.24 left upstream support in February 2026, and the 1.25 series left it in August 2026, so packages built from either carry none of the Go security fixes issued since. The Go 1.24 line is closed: 1.24.13.7 is its last release. The next hop goes straight to Go 1.26, with no Go 1.25 release, and the hop after it brings Go 1.27:

Hop When What it involves
Go 1.26 (1.26.9), straight from 1.24.13 After 1.24.13.7, the last 1.24 release One hop across two Go releases. Go 1.25’s changes are behavioral (its nil-check fix, updated unicode tables, the AllocsPerRun change) plus testing-API additions in the converted test host; Go 1.26 adds new(expr) and self-referential generic constraints. Every failure is first attributed to the release that caused it, by converting at 1.25.14 as well, without publishing it. Then every package is re-validated at 1.26.
Go 1.27 Q1 2027, by about February Generic methods, which need a conversion design first. The 1.26 series leaves support when Go 1.28 ships, around February 2027, so the corpus reaches 1.27 before then.

How often later hops run is decided ahead of the Go 1.27 hop.

.NET

In short. Converted code targets .NET 10, a long-term-support release, and stays there until .NET 12. You need the .NET 10 SDK, and a newer SDK on your machine does not change what you get.

The published target stays net10.0. .NET 10 is a long-term-support release, supported until November 2028, and converted packages stay on it until net12.0, the next long-term-support release.

The SDK and the C# language version are pinned. A repository global.json pins the .NET SDK, and every project, including the template the converter emits, fixes LangVersion instead of following latest, so a machine whose default SDK moves to a newer .NET does not silently compile converted code under a newer C#. The converter also escapes Go identifiers that newer C# gives new meaning, such as closed, union and safe when they name a type.

A .NET 11 measurement stage follows the Go 1.26 hop, once .NET 11 has shipped (November 2026), and never overlaps a Go hop. It compares JIT and allocation behavior on .NET 11 over identical code, and reviews the deployment shape, including the full-trim publish floor, which depends on the runtime library’s trim safety (Native AOT and trimming; PLAN-bflat-perf-exploration.md). Its findings inform the move to net12.0; net10.0 stays the published target until then.

Declared host limits and their retirement path

The roster’s host-limit disclosure class, defined in ValidatedTestPackages.md, covers tests whose premise rests on a property of the test binary that the converted host’s deployment shape structurally lacks. An entry must name that structural property, never a fixable defect, and it is written to retire itself when the shape gains the property: disclosed tests keep running in every validation run, so an entry that starts passing breaks the roster’s disclosure arithmetic loudly and must be removed. The converted test host publishes as a self-contained, relocatable single-file executable, so Go tests that copy and relaunch their own binary pass.

Entries remain in crypto/tls, os, os/exec and syscall, and not every one retires through the deployment shape; the roster’s class text names what each one needs. In crypto/tls’s TestBogoSuite, BoringSSL’s test runner starts the host once per case inside Go’s own 10-minute test deadline. The interop itself passes, and the run is bound by throughput rather than start-up time, so no start-up work alone retires it. The entry retires when managed steady-state TLS throughput brings the run inside the runner’s deadline, which is what the TLS work under Performance targets.

Native AOT and trimming

In short. You can publish converted Go as a native executable today, and it runs, but the publish can take hours and the file is large. This work makes that publish smaller, quicker and free of warnings, in stages.

A converted program, and a C# program that references the go.* packages, can be published with Native AOT. Making that publish smaller, faster and free of trim warnings is staged work on the runtime library and the source generators:

The cost today is real. With the default trim, Native AOT compiles every go.* assembly a program references in full, so a publish takes hours and the executable is large. Known issues has the measurements. A build, dotnet run and a publish without Native AOT are not affected.

Platforms

In short. go2cs is validated on x64 Windows and x64 Linux. macOS, on Intel and on Apple silicon, has packages and is being validated package by package now. Other platforms are not supported yet.

Windows and Linux. The converter runs on both, and the standard library is multi-target on disk: platform-shared files sit flat, and a package whose Go source varies by platform keeps one folder per target OS (design). Each platform-varying package ships a win-x64 and a linux-x64 flavor, and roster rows record their Linux verdict counts beside the Windows record. Remaining Linux operational items are tracked in PLAN-linux-operation.md and the multi-target design.

macOS (Intel and Apple silicon): packages ship for both chips, the behavioral suite passes on both, and the README walkthrough runs. The standard library is being validated package by package on macOS now. A roster row gains its macOS count only when both chips agree with each other and with Go, and the roster rows show which packages have one. Until a package has its count, don’t expect it to be fully operational there. The work also finds macOS-only gaps, each fixed as it is found. The compile is checked daily in CI on macOS arm64 and x64 runners (CIMatrix.md).

Other architectures receive the Windows flavor until a flavor ships for them: a platform-varying package’s Windows flavor is what compiles and loads, and its platform-entangled behavior (local time zones, sockets, the syscall surface) follows Windows semantics or fails.

Real-world Go modules

In short. Beyond the standard library, go2cs converts third-party Go modules and holds each to the same bar: its own tests must match Go’s. Five modules pass today, and a larger one is next.

Third-party modules are the proving ground after the standard library. Each module is converted, validated against its own Go test suite exactly as a standard-library package is, and given a proof page beside the conversion rather than on the standard-library roster. go2cs -tests -recurse runs a module’s tests across all of its packages, and a validated module packs as one NuGet package.

Five modules validate today, each on the platforms Validated Modules names: golang.org/x/sync, google/uuid, golang-jwt/jwt v5, ritchiecarroll/hashset and spf13/pflag. uuid is one package with no dependencies; it brings Go’s v1, v3, v5 and v6 UUIDs to .NET, where System.Guid generates v4 and v7. jwt is the first module whose tests span several of its own packages, and it exercises the converted crypto stack in real code. The next candidate:

Module Why it comes here When
itchyny/gojq v0.12.19, with its dependency itchyny/timefmt-go v0.1.8 The first conversion with a dependency. It gives .NET an in-process, fully managed jq, with no native binary per platform. Q4 2026

gojq’s full command-line conformance suite is a later second stage, once its command-line package’s own dependencies convert. Further modules follow; the go2cs Target Atlas is one input to their selection.

NuGet packages of converted modules

In short. A Go module that someone has already converted can arrive as a NuGet package, so your conversion restores it and does not convert it again. Converted modules are published this way today, and nugetgo.net, the registry that maps Go modules to their packages, lists them.

With -recurse=nuget, a converted program references the standard library, the runtime and the source generators as published NuGet packages, and the same substitution reaches third-party modules: the nugetgo.net community registry maps Go module paths to published NuGet packages of their conversions, so a dependency someone has already converted becomes a package restore instead of a local transpile. The converter side is built and on by default (see Mapping modules to NuGet packages). The design is PLAN-nugetgo.md.

cgo

In short. Go code that calls C through cgo cannot be converted yet. The converter says so and stops, naming the file, and a bridge to native code is planned from 2027.

A Go file that imports "C" is not converted, and the standard library is converted with CGO_ENABLED=0. The plan is PLAN-cgo-interop.md:

Phase 4D: examples, fuzz seed corpora and benchmarks

In short. Validation counts Go’s Test functions today. Phase 4D adds the other kinds of test a Go package carries: examples, fuzz seeds and benchmarks. It is planned and not yet started.

Validation counts a package’s Test functions. Phase 4D, the next validation slice, widens what “validated” covers:

Active fuzzing, race-detector equivalence and coverage-percentage equivalence stay outside the correctness gate. The design kickoff is planned for Q1 2027, and the work proceeds without delaying Phase 5. Detail: TestingInfrastructureRequirements.md.

Phase 5 — Implement assembly-backed declarations in C#

In short. Phase 5 is about completeness, not speed. A small set of standard-library declarations has no portable Go body, because Go implements them in assembly, in cgo or inside its runtime. Today each of those compiles to a stub that throws a NotImplementedException, naming the declaration, if it is ever called. Phase 5 gives each one a real C# body. Making portable code that already works run faster is a different subject, under Performance.

What it means for you. No validated package’s tests reach a stub, and a call that does reach one fails loudly and by name, never silently. Which exported functions can reach one is measured, in the exported-reach record. On Windows and Linux the only exported calls that do are ones Go itself refuses to carry out in an ordinary program: clearing coverage counters in a program built without coverage, and runtime.Breakpoint. They are being changed to answer exactly as Go does. On macOS, verifying a certificate against the system’s root certificates and CPU profiling also reach a stub. Those are part of the macOS work (Platforms).

The converted library reproduces Go built with -tags purego, so most assembly-backed code takes its portable Go path and needs nothing from Phase 5. The declarations it is about are the ones with no Go body at all: they are backed by assembler, cgo, runtime or compiler intrinsics, or platform services. The PartialStubGenerator writes the throwing stub for each until a C# implementation exists.

It proceeds package by package, as validation demands, not as one campaign. When a package’s own tests need a declaration, it gets a hand-written C# body and the package validates with it. That is how most of the surface that matters has been implemented so far, and it is how the macOS validation is closing the gaps it finds now: os.Executable, the system page size, and an arm64 path for hash/crc32. A declaration no validated test reaches can still be a throwing stub, and a program that calls one stops with a NotImplementedException that names it.

The remaining stubs are measured. The Phase 5 stub inventory is a measured inventory of the declarations that still compile against throwing stubs, per package and per operating system. None of them is reached by a validated package’s tests. The record carries the counts, so this page quotes none.

How a declaration gets its implementation.

What closing Phase 5 means: for every supported target, no unexplained throwing stub remains; each implemented member has a real companion or a documented target exclusion; and every applicable package is validated.

What is closed first. Priority follows the exported-reach record: a stub that an ordinary program can easily reach is closed ahead of everything else. The stubs no exported call reaches stay as they are, by name, until a real use needs one; closing those has no date.

Phase 5 is not the “stage 5” of the trimming work under Native AOT and trimming; the two numberings are unrelated.

Performance

In short. Converted code is usually slower than Go and uses more memory, and the published comparison says by how much. This section lists the work planned to narrow the gap. None of it holds up anything else on this page.

The published performance comparison measures converted C# against Go on the JIT and under Native AOT. The follow-ups run off the critical path:

Expand the sstring stack-string MVP: the remaining increments

A non-escaping string([]byte) conversion emits a zero-copy sstring view instead of a heap copy: for a named local, a comparison or switch operand, a concatenation operand, and a repeated conversion hoisted to one view (strings reference; the completed increments are in Roadmap history). Two increments remain:

Why widening is safe to try. sstring is a ref struct, so the .NET compiler rejects almost every over-reach (storing it in a field, array or map, boxing it, sending it on a channel, capturing it in a closure) at build time. The only silently-wrong vectors are escape through return and mutation of the source buffer during the view’s lifetime, and both stay explicitly guarded. Each increment extends the SStringElision behavioral test, inspects every newly emitted site in a full standard-library reconvert, and re-runs the performance suite.

Not pursued: for range string(x), which has no standard-library sites; and m[string(x)] map keys, where a zero-allocation lookup would need a custom comparer that slows every @string-keyed map.

IDE mode (an experiment)

A gated experiment in writing Go in an editor and running and debugging it as .NET, with the debugger showing Go values the way a Go developer expects. Its feasibility is tested first, and nothing in it is a commitment yet. The plan: IDE mode.

Open converter items

In short. Known, smaller gaps in the converter, none of them scheduled. The validated standard library and modules convert despite them; one matters to you only if your code uses that construct.

Smaller converter items, unscheduled. The first is recorded in the struct-types reference; the rest are tracked in src/go2cs/ToDo.md:

Timeline

In short. The order the work on this page is planned in. Few rows carry a date; most wait on something else finishing first.

When Planned
Now macOS validation of the standard library, package by package. The trimming work for Native AOT. Further converted modules as NuGet packages.
Next The Go 1.26 hop, straight from 1.24.13.7, the last 1.24 release. The performance re-baseline at 1.24.13.7.
After the Go 1.26 hop The .NET 11 measurement stage (.NET 11 ships in November 2026). timefmt-go and gojq, as their prerequisites land. The generic-methods design for Go 1.27. The decision on how often later hops run.
Q1 2027 Go 1.27 hop, by about February. The cgo bridge’s first phases (C library only; Linux, then Windows, then macOS). The Phase 4D design kickoff. The allocation work’s design kickoff.
Unscheduled The Phase 5 stubs that no exported call reaches, each closed when a real use needs it. Interface-conversion performance work. The remaining stack-string increments. Open converter items.

How progress is tracked

In short. Where to check each claim on this page for yourself.

What Where
Validation Validated Test Packages, the authoritative roster: each package’s verdict counts on Windows and Linux, its macOS count once it has one, its disclosures with their stated reasons, and its proof page. Its header carries the current totals.
Third-party modules Validated Modules: each validated module, its version, the platforms it was measured on, and its proof pages.
Compilation The whole converted standard library builds with zero errors (src/go2cs-stdlib.slnx) for Windows and Linux; the macOS compile runs daily in CI (CIMatrix.md).
The converter Hundreds of Go-vs-C# behavioral projects under src/tests/Behavioral, each transpiled, compiled, compared against a committed golden and, where it is a runnable program, output-compared against Go.
Performance The performance comparison.
Releases News.

Every converter change runs the same loop: edit src/go2cs/*.go → go build (with the Go release src/go2cs/go.mod names) → re-transpile → dotnet build, and a whole-corpus reconvert shows no unintended drift before the change lands.