G# Is the Surprise That Made .NET Feel New Again

G# is exciting not because it replaces C#, but because it puts Go, Kotlin, and Swift-flavored ergonomics on the .NET platform and reminds us that a runtime can support more than one great way to think.

By Jovani Pink July 12, 2026 12 min — Systems & Complexity Notes

Outcome focus: Reader can evaluate G# as an early .NET language experiment, understand why F# is the ecosystem's proof that distinct language models can thrive on one runtime, and choose a practical way to explore both without mistaking novelty for production readiness.

Every so often, a new programming language arrives with an ambition that feels almost too familiar to notice. It has tidy functions. It handles nullability in a more deliberate way. It makes immutable data pleasant. It borrows a concurrency idea from a language you already like. The immediate reaction can be, "Fine, but why does this need to exist?"

G# made me have the opposite reaction: wait, this is a surprisingly good place for that idea.

The project describes itself as a modern .NET language with Go, Kotlin, and Swift ergonomics. It compiles directly to managed assemblies, can import CLR types, and ships with an unusually serious-looking set of surrounding pieces for such a young language: a gsc compiler, MSBuild SDK, language server, VS Code extension, Portable PDB debugging, a specification, tutorials, and documented design decisions. The launch discussion on Hacker News has the familiar mixture of delight, sharp skepticism, and immediate questions about what is actually gained by putting a new syntax over an existing runtime. Those questions are exactly why this project is interesting.

G# is not exciting because .NET needs a C# replacement. It is exciting because it makes a different claim: the runtime, libraries, deployment story, and toolchain are valuable enough that developers should be able to approach them through more than one excellent language experience.

The surprise is the target, not just the syntax#

A new language usually has to carry an exhausting amount of infrastructure by itself. It needs a standard library, package system, debugger, editor support, deployment path, documentation, community conventions, and an answer for every library somebody asks to use. That is why many lovely languages stay lovely experiments. Their surface may be better, but their operating environment asks early adopters to give up too much.

G# begins from the other side of that equation. It aims to emit ordinary managed assemblies and live inside the .NET world developers already know. That means the value proposition is not "we have invented a new universe." It is closer to: "what if the universe you already trust had a new front door?"

The language is very explicit about the influences of that door. Its tour lays out packages and imports, func, data class, nullable flow through if let and guard let, patterned switch, scope for structured concurrency, async and await, and direct CLR interop. The result is not a neutral syntax. It is a deliberately opinionated recombination of familiar ideas.

package Greeting
 
import System
 
data class Person(Name string, Age int32)
 
func greet(name string?) {
    if let value = name {
        Console.WriteLine("Hello, $value")
    } else {
        Console.WriteLine("Hello, stranger")
    }
}
 
let alice = Person("Alice", 30)
let older = alice with { Age = 31 }
greet(alice.Name)

No one construct in that example is unprecedented. That is the point. The language is trying to make a coherent default vocabulary from constructs that many developers already find readable: explicit package boundaries, lightweight functions, data-oriented values, nullable flow that has a name and a scope, and immutable updates that do not require a ceremony of constructors and assignments.

The details matter more than the visual resemblance. G#'s data class and data struct forms synthesize structural equality, with copies, and deconstruction. Its standard spelling for numeric primitives is intentionally width-bearing, such as int32, uint64, and float64. Its documentation does not hide the tradeoffs behind a loose feature list; the design-decision index records choices about nullability, numeric naming, default sealing, async lowering, imports, CLR interop, and source generation.

That last part may be the most encouraging thing in the whole project. A language is not a collection of pleasant tokens. It is a long series of choices about ambiguity, compatibility, error messages, tool behavior, and what happens when two good ideas collide. Public ADRs do not make those choices correct, but they make the project legible. For a language that wants to feel simple, showing the reasoning is a real form of respect for users.

A language can change the feel without changing the platform#

It helps to separate three layers that get collapsed in most language arguments:

  • The language surface: syntax, semantics, defaults, type model, and the idioms a compiler makes natural.
  • The platform: runtime, libraries, package ecosystem, deployment, debugging, performance work, and host integrations.
  • The application: the actual domain model, operations, data, and people the software has to serve.

Changing the first layer can be valuable even when the second stays stable. Kotlin did not need to replace the JVM to make many developers feel differently about Java's platform. Swift's appeal is not only that it can call frameworks; it is that its type system and language affordances change what feels natural to write. Go made tradeoffs about simplicity, composition, and concurrency visible in every program, even though it still had to win people over with an ecosystem.

G# is making that kind of source-level bet for .NET. The project even has a straightforward answer to the inevitable question, "Can I still use the things I need?" Its repository positions the .NET base-class library, NuGet packages, and dotnet tooling as native assets rather than foreign interoperability problems. In the good version of this future, a developer gets a smaller, more intentional source language while still calling the libraries their organization already depends on.

That does not mean the runtime disappears. It means the runtime is doing its best work: carrying a stable contract underneath multiple ways of expressing programs. The CLR and the .NET ecosystem become a substrate for language design rather than an argument that one syntax must be the syntax forever.

The Hacker News thread is helpful here because it does not merely celebrate. Readers question unfamiliar terminology, whether opt-in language extensions are really extensions, how import boundaries read, the cost of managed binaries and memory, and whether a new language can preserve the useful clarity of the languages it borrows from. Those are not cheap shots. They are the work a young language must invite. Borrowed ergonomics only become a coherent language when they remain understandable under pressure.

F# is the proof that this is not a fantasy#

The practical reason to take G# seriously is not that it has already earned F#'s maturity. It has not. G#'s current documentation is for version 0.3, which is precisely why exploration should be paired with caution. The practical reason is that .NET already has a durable example of a language with a genuinely different center of gravity: F#.

F# is not C# with some functional decoration. It is a functional-first, strongly typed language that is immutable by default, uses type inference, supports first-class functions and pattern matching, and models domains with records and discriminated unions. Microsoft describes it as succinct, robust, performant, cross-platform, and interoperable in its language overview; its language guide makes the more important claim that those features let the programmer focus on the problem domain rather than incidental machinery.

That is not marketing fluff when the type model is used well. Consider a domain that can result in one of several outcomes. In many object-oriented codebases, an API returns null, a Boolean, a status string, or an exception. F# can make the shapes of the result explicit and force handling to be visible:

type WithdrawalResult =
    | Success of amount: decimal * balance: decimal
    | InsufficientFunds of balance: decimal
    | CardExpired of System.DateTime
 
let describe result =
    match result with
    | Success (amount, balance) -> $"Withdrew {amount}; balance {balance}"
    | InsufficientFunds balance -> $"Balance is {balance}"
    | CardExpired date -> $"Card expired on {date:d}"

This is a different way of asking the programmer to be honest. The type says what can happen. The match says what the program does about each possibility. The benefits are not mystical functional-programming points; they are a shorter path from a business rule to a compiler-checkable program.

The broader F# reference history is useful context, but the more important evidence is current stewardship. F# 10 shipped with .NET 10 and Visual Studio 2026 with a refreshingly practical theme: clarity, consistency, performance, compiler and tooling responsiveness. It includes better scoped warning controls, less noisy syntax for computation expressions, and! support in task expressions for concurrent awaits, trimming improvements, and progress toward parallel compilation. The Hacker News discussion of F# 10 captures a useful reality check: people debate tooling and institutional support, yet they also describe using F# successfully for professional systems, network utilities, and mixed C#/F# solutions.

F# matters to the G# story because it shows that .NET can sustain a language whose idioms are not a compromise. It can have one first-class language optimized for broad, mainstream, multi-paradigm application development and another optimized around functional composition, rich algebraic data types, and domain modeling. The platform does not get weaker when those choices coexist. It gets more expressive.

The best F# examples are concrete, not ceremonial#

Functional languages are often presented through toy financial examples or tiny recursive functions, which does them no favors. The most convincing F# material is usually the stuff that looks like ordinary engineering made clearer.

Nick Kossolapov's write-up of a Game Boy emulator in F# is a particularly good example. The project has a CPU, memory map, IO controller, PPU, audio, desktop and web frontends: there is nothing abstract about it. The F# work shows up in the domain model. Distinct instruction source and destination types prevent nonsensical operations from being expressed, while a central stepper keeps emulated components synchronized. The implementation uses mutable state where emulation performance requires it. That is not a betrayal of the language; it is the language being used to make the important parts precise and the hot parts practical.

The accompanying Hacker News thread is worth reading for another reason. It is full of implementation-level discussion: allocation behavior, Fable's JavaScript numeric behavior, instruction representations, and the tradeoffs of performance-minded modeling. That is the level on which a language earns its reputation. Not whether it can write a pretty map, but whether it helps a person build a complex, real thing and have intelligent disagreements about its details.

This is also the standard G# should be held to. The project does not need to prove that func looks clean. It needs small, opinionated, runnable programs that demonstrate why its choices hold together with a database client, a service boundary, a queue consumer, a CLI, a UI, a package, a profiler, and a debugger. The early signs are promising because the language is not pretending interop and tooling are an afterthought. The proof will be in the unglamorous round trips.

What G# should borrow from F# besides a suffix#

G# is not trying to become F#, and it should not. F# has an ML lineage, a deep type vocabulary, and decades of accumulated idioms. G# is seeking a more immediately familiar surface for developers shaped by Go, Kotlin, Swift, TypeScript, and C#. Its data class, nullable flow, lightweight functions, and structured concurrency make a different invitation.

But F# offers a few lessons that would serve any new .NET language well.

First, make the language's center of gravity obvious. F# has escaped the trap of being "C# but shorter" because its records, unions, type inference, and pattern matching point toward a coherent way of modeling systems. G# should be equally clear about what it optimizes for: a small, predictable, data-oriented language with first-class .NET access and deliberately explicit concurrency boundaries.

Second, protect the escape hatches without letting them become the default architecture. F# can interoperate with objects, interfaces, tasks, and the CLR. That is a strength because the functional core remains visible. G# similarly makes CLR interop part of its promise, but its own idioms need to feel complete enough that every program does not immediately collapse into imported C# APIs and an unstructured sea of Task values.

Third, let tooling be part of the language, not a lagging annex. F# 10's type-subsumption cache and parallel-compilation work are not glamorous features, but editor latency and build feedback are part of what a language feels like at 3 PM on a Tuesday. G# already names a compiler, MSBuild integration, an LSP, and debugger support. Keeping those features reliable as the semantics evolve will matter more than adding a clever expression form.

Fourth, be disciplined about stability. A pre-1.0 language is allowed to change. In fact, it should change when the early design is wrong. But a team evaluating G# needs a crisp view of what is experimental, what is specified, how upgrades behave, and where the compiler rejects old forms with useful migrations. The project's documented ADRs and diagnostics are good foundations for that social contract.

How I would explore G# without fooling myself#

The right response to a project like this is neither "production next week" nor "irrelevant until it has ten years of history." It is a bounded experiment with a real question.

Start with a small .NET-shaped task where the existing ecosystem matters: a command-line tool over a NuGet library, a compact service adapter, a file processor, or a data transformation with a few asynchronous boundaries. Avoid critical-path authorization, payment movement, or a library that must promise long-term public API stability. The exercise should be large enough to test imports, build output, error messages, tests, debugging, packaging, and Task-based interop.

Then evaluate five things:

  1. Does the G# source make the domain easier to read than the C# or F# version you would otherwise write?
  2. Do imported CLR APIs feel direct, including generic types, overload resolution, events, exceptions, cancellation, and nullable reference types?
  3. Does structured concurrency make ownership and cancellation clearer, or does it become a second mental model beside .NET tasks?
  4. Can the toolchain survive ordinary work: editor navigation, diagnostics, tests, MSBuild, CI, debugging, packaging, and upgrades?
  5. Does the language's smallness remain an advantage after the first excitement wears off?

The last question is the whole game. A small language is not one with few features. It is one whose features compose so well that the next thing you need does not force a new rule, an exception, or a piece of folklore. G# is making a bid for that kind of coherence. Its early syntax is appealing because it is recognizable; its long-term success would depend on becoming predictable.

.NET is more interesting when it is a platform, not a monoculture#

There is a lazy version of the .NET story in which C# is the platform, F# is a niche curiosity, and every new language is a distraction. That story misses the part worth appreciating: .NET has become a capable, open, cross-platform runtime and tooling ecosystem with enough weight to support different language philosophies.

C# remains the obvious default for a vast amount of software, and that is not an insult. It has mature libraries, a huge pool of developers, excellent tooling, and a continuously evolving language. F# remains one of the strongest reasons to look at .NET with fresh eyes if you care about type-driven domain modeling and functional-first design. G# brings a new, still unproven, but genuinely invigorating possibility: a language whose familiar modern feel may lower the emotional barrier to all that platform capability.

That is why G# feels like a surprise. It does not ask .NET to apologize for being .NET. It asks whether a great platform can be even more welcoming when it gives developers a choice of excellent language experiences.

For now, that is enough to be excited about. Open the docs. Read the ADRs. Build a bounded experiment. Keep F# in the same frame, because it proves the target can support a real alternative rather than a novelty syntax. And watch whether G# turns its attractive first impression into the harder thing: a language people trust when the code stops being a demo.

Back to all writing
On this page
  1. The surprise is the target, not just the syntax
  2. A language can change the feel without changing the platform
  3. F# is the proof that this is not a fantasy
  4. The best F# examples are concrete, not ceremonial
  5. What G# should borrow from F# besides a suffix
  6. How I would explore G# without fooling myself
  7. .NET is more interesting when it is a platform, not a monoculture