<!-- mobian-agent-page publisher="dailydev" canonical="https://daily.dev/posts/soppo-karjn0zhl" -->

---
title: Soppo | daily.dev
description: Soppo is a new programming language that extends Go syntax with features Go lacks: tagged unions with exhaustive pattern matching, a `?` operator for ergonomic...
canonical: https://daily.dev/posts/soppo-karjn0zhl
twitter:card: summary_large_image
twitter:site: @dailydotdev
og:type: website
og:site_name: daily.dev
og:title: Soppo | daily.dev
og:description: Soppo is a new programming language that extends Go syntax with features Go lacks: tagged unions with exhaustive pattern matching, a `?` operator for ergonomic...
og:url: https://daily.dev/posts/soppo-karjn0zhl
og:image: https://api.daily.dev/og/posts/kARjn0zhl.png
og:image:alt: Soppo
og:image:width: 1200
og:image:height: 630
og:locale: en
---

> ## Documentation Index
> Fetch the complete documentation index at: https://daily.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Soppo

**[Lobsters](https://daily.dev/sources/lobsters)** · 3 min read · 3 upvotes · 0 comments

## Summary

Soppo is a new programming language that extends Go syntax with features Go lacks: tagged unions with exhaustive pattern matching, a `?` operator for ergonomic error propagation, compile-time nil safety with type narrowing, and helpful compiler error messages. It maintains full interoperability with existing Go libraries and tooling, allowing gradual adoption. Installation is available via SOPMOD (a version manager) or the `sop` binary through Cargo.

## Full article

daily.dev links to this article rather than hosting it. Read it at the original source: <https://soppolang.dev/>

## Questions this post answers

### How does Soppo handle error propagation compared to Go's if err != nil pattern?

Soppo uses a question-mark operator to propagate errors concisely instead of repetitive if err != nil blocks. Writing data := os.ReadFile(path) ? automatically propagates the error, while a variant like result := parse(data) ? err { ... } lets you add custom handling with a named error when needed, keeping error handling explicit but less verbose.

_daily.dev surfaces language design experiments like this for developers tired of boilerplate error handling in go._

### How does Soppo prevent nil pointer dereference bugs at compile time?

Soppo tracks nilability at compile time using a question-mark marker on types, such as func findUser(id int) ?*User. After a nil check like if user != nil, the compiler automatically narrows the type from ?*User to *User, catching potential nil dereference bugs before the code runs rather than at runtime like standard go.

_developers weighing safer alternatives to go's nil handling can track projects like this on daily.dev._

### How do I install the Soppo programming language?

The recommended installation method is through SOPMOD, a version manager that handles installing, updating, and switching between Soppo versions, and also manages the Go toolchain it depends on internally. Alternatively, you can install just the sop binary directly via Cargo, which requires both Rust and Go to already be installed.

_daily.dev helps developers evaluating new toolchains like soppo find setup details quickly._

## Community take

How the wider developer community reacted, aggregated from 2 discussions and 24 comments across lobsters, hackernews (as of 2026-09-13).

**TL;DR:** Commenters are genuinely impressed by Soppo's feature set — tagged unions, nil safety, and the `?` operator — but raise real concerns about the ergonomics of error handling, the tension between nil safety and Go's universal zero-value initialization, and the long-term viability of a Go superset language.

**Sentiment:** 45% positive · 40% mixed · 15% skeptical

**The case for**

- Tagged unions with exhaustive pattern matching and compile-time nil safety are seen as exactly the missing pieces that would make Go feel complete.
- Full interoperability with existing Go libraries lowers the barrier to trying it, enabling gradual adoption.
- The `?` operator does improve scoping of new variables compared to Go's `if port, err := ...; err != nil` pattern.

**The pushback**

- The `?` error-handling syntax is considered barely shorter than the standard `if err != nil` dance, and a more concise `??` form wrapping errors with context is suggested as more useful.
- Universal string interpolation with `{}` is divisive — some prefer opt-in interpolation or a backslash-based syntax to avoid escaping issues.
- Go's universal zero-value initialization creates a fundamental tension with nil safety and exhaustive enum matching that Soppo doesn't fully resolve.
- Widespread adoption is seen as unlikely for a Go superset, which historically kills such projects.

**By community**

- lobsters (mixed): Broadly impressed by the feature set but engaged in substantive debate over error-handling ergonomics, string interpolation syntax, and the unsolved zero-value problem for tagged unions.
- hackernews (mixed): No comments were present, so no signal is available.

**Hottest debate:** Whether Soppo's `?` operator meaningfully improves Go error handling, or whether a richer `??`-style syntax that wraps errors with context would be necessary to make it worthwhile.

**Open questions**

- How does Soppo reconcile nil safety and exhaustive pattern matching with Go's universal zero-value initialization for enum/union types?
- Has the design considered and weighed prior Go error-handling proposals (try/check, etc.) and why were they ruled out?
- Will the project achieve enough adoption to survive long-term, given the historical difficulty of Go superset languages gaining traction?

**Highlights**

> I was skeptical when clicking on this, but color me impressed, this all seems remarkably reasonable.  My only nitpicks being string interpolation being universal instead of opt-in, which I think could be a source of annoyance, and I'm not sure how you can have both seamless interop and nil safety. Also I feel like error handling is still not ergonomic enough. [The docs](https://github.com/halcyonnouveau/soppo/blob/main/docs/guide.md) give this example: ``` // Custom handling port := parsePort(config) ? { 	return fmt.Errorf("parse failed") } ``` This feels barely shorter than the usual `if err != nil` dance, so to me it feels completely superfluous. An actually useful solution imo would be to have a shorthand for `return fmt.Errorf("{message}: %w", err)`. Say `port := parsePort(config) ?? "port parsing failed"` would extend to: ``` port, err := parsePort(config) if err != nil {         return fmt.Errorf("port parsing failed: %w", err) } ``` And `port := parsePort(config) ?? ("port parsing on %q failed", config.IPAddress)` would turn to: ``` port, err := parsePort(config) if err != nil {         return fmt.Errorf("port parsing on %q failed: %w", config.IPAddress, err) } ``` (Obviously in cases where `(T, error)` is the return value, it'd unfold into `return ([default value of T], fmt.Errorf(...))`) --- Other than these, yes, yes, and yes. Go is so close to being a perfect "a hammer is a hammer" language, but it still has some sharp corners, that it feels like it really shouldn't have anymore. It annoys me so much that `const` is such a *const*rained modifier in Go. I'd love if I could mark almost everything const and only do var (or `:=`) when I know I'll actually modify things. It may be a Rustism, but one I genuinely believe to be good for everyone involved (you're more confident about correctness, the compiler can optimize more freely, the next reader will be certain no monkey business happens dozens of lines down, etc.) Better matching functionality would clear away the weird "check on variable X, but then variable Y becomes actually that type" syntax. And with nil checks, the compiler can take off some needless mental bookkeeping from the programmer.
> — [nemin on lobsters · 11 points, 3 comments](https://lobste.rs/s/0ykmyt/soppo_go_with_features_it_s_missing#c_fvqjlp)

> The first question I ask when people propose adding tagged unions to Go is how do you handle the fact that every value is default-initialised? It seems that Soppo does not really handle this:         package main 	         import "fmt" 	         type Empty enum {} 	         // Ideally we should not be able to construct this type:         type Foo struct {           empty Empty         } 	         func main() {         	fmt.Println(Foo {}) // Prints "{<nil>}"         } This is a more esoteric example (using the empty type), but it seems to behave similarly for enums like the `Shape` type on the homepage. I guess this you could argue Haskell is in a similar boat where all lazy types have the possibly of being undefined… but this is somewhat disappointing for a strict language. I'd also imagine that the prevalence of default values in Go APIs could make this a somewhat significant footgun, that I'd hope was documented at the very least. If I were “fixing” Go, I'd look towards removing the necessity of a default value for all types, and use compile-time analysis to ensure types are fully initialised before they are used.
> — [brendan on lobsters · 1 points, 2 comments](https://lobste.rs/s/0ykmyt/soppo_go_with_features_it_s_missing#c_gz5vsa)

> I wish the Go team would just adopt these changes. Go could be the perfect language. Soppo is great, but unfortunately the chance of widespread adoption is quite low, which eventually kills such projects.
> — [zknd on lobsters · 1 points, 1 comments](https://lobste.rs/s/0ykmyt/soppo_go_with_features_it_s_missing#c_ktoafw)

> I do still think the `?` case here is better than the alternative when you want to create a new var in the outer scope. ``` port := parsePort(config) ? { 	return fmt.Errorf("parse failed") } ``` one might want to do something like this in the pure go case, but then `port` is in the inner scope ``` if port, err := parsePort(config); err != nil { 	return fmt.Errorf("parse failed") } ``` so yes, as long as the `?` scopes the new var as expected I do think it's a nice addition compared to ``` port, err := parsePort(config) if err != nil { 	return fmt.Errorf("parse failed") } ``` That said, I think your `??` suggestion would be even better since most of the time you really just want to wrap the existing error with some local context.
> — [gerow on lobsters · 1 points](https://lobste.rs/s/0ykmyt/soppo_go_with_features_it_s_missing#c_5euhmi)

> Yeah, it would depend on the goals of the language - i.e. if it's meant to be purely an additive change to the language with minimal need for updating existing code, or whether it's meant to be fixing issues with Go in the process. If it's the former then the addition of enums *will* need some unfortunate compromises - e.g. perhaps one of the following approaches could be chosen: - drop the claim of nil safety and exhaustiveness - require matches on enums to handle the default (nil) case - require one-or-more variants per enum and choose the first variant as the default - require one-or-more variants per enum and require a default variant to be explicitly marked The nil safety stuff seems to suggest they are aiming for it to be a breaking change, however.
> — [brendan on lobsters · 1 points](https://lobste.rs/s/0ykmyt/soppo_go_with_features_it_s_missing#c_b3vpw2)

**Source threads**

- [lobsters](https://lobste.rs/s/0ykmyt/soppo_go_with_features_it_s_missing) · 55 points · 24 comments
- [hackernews](https://news.ycombinator.com/item?id=47715923) · 1 points · 0 comments

## Similar posts on daily.dev

- [Solod: Go can be a better C](https://daily.dev/posts/solod-go-can-be-a-better-c-3smaqoszm) · Anton Zhiyanov · 31 upvotes · 3 comments
- [MadAppGang/dingo: A meta-language for Go that adds Result types, error propagation \(?\), and pattern matching while maintaining 100% Go ecosystem compatibility](https://daily.dev/posts/madappgang-dingo-a-meta-language-for-go-that-adds-result-types-error-propagation-and-pattern--mi5dgcwbh) · Lobsters · 8 upvotes · 0 comments
- [Solod](https://daily.dev/posts/solod-rutjzllbz) · Hacker News · 1 upvotes · 0 comments
- [Relying on Go](https://daily.dev/posts/relying-on-go-izfbk5fxy) · Anton Zhiyanov · 43 upvotes · 5 comments

---

Tags: [#golang](https://daily.dev/tags/golang)

[View this post on daily.dev](https://daily.dev/posts/soppo-karjn0zhl)

```json
{"@context":"https://schema.org","@graph":[{"@type":"Organization","@id":"https://daily.dev/#organization","name":"daily.dev","url":"https://daily.dev","logo":{"@type":"ImageObject","url":"https://daily.dev/apple-touch-icon.png","width":180,"height":180},"sameAs":["https://twitter.com/dailydotdev","https://github.com/dailydotdev","https://www.linkedin.com/company/daily-dev-ltd"]},{"@type":"WebSite","@id":"https://daily.dev/#website","url":"https://daily.dev","name":"daily.dev","publisher":{"@id":"https://daily.dev/#organization"},"potentialAction":{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https://daily.dev/search?q={search_term_string}"},"query-input":"required name=search_term_string"}}]}
{"@context":"https://schema.org","@type":"TechArticle","headline":"Soppo","url":"https://daily.dev/posts/soppo-karjn0zhl","mainEntityOfPage":{"@type":"WebPage","@id":"https://daily.dev/posts/soppo-karjn0zhl"},"datePublished":"2026-08-04T11:07:06.502Z","dateModified":"2026-09-13T21:57:09.912Z","description":"Soppo is a new programming language that extends Go syntax with features Go lacks: tagged unions with exhaustive pattern matching, a `?` operator for ergonomic...","image":"https://media.daily.dev/image/upload/s--1KxV4ohY--/f_auto/v1722860400/public/Placeholder%2007","thumbnailUrl":"https://media.daily.dev/image/upload/s--1KxV4ohY--/f_auto/v1722860400/public/Placeholder%2007","isAccessibleForFree":true,"articleSection":"Lobsters","inLanguage":"en","publisher":{"@type":"Organization","name":"daily.dev","url":"https://daily.dev","logo":{"@type":"ImageObject","url":"https://daily.dev/apple-touch-icon.png","width":180,"height":180}},"author":{"@type":"Organization","name":"Lobsters","logo":"https://media.daily.dev/image/upload/s--tl8v_Fku--/f_auto,t_logo/v1698841318/logos/lobste.jpg","url":"https://daily.dev/sources/lobsters"},"commentCount":0,"discussionUrl":"https://daily.dev/posts/soppo-karjn0zhl","interactionStatistic":[{"@type":"InteractionCounter","interactionType":{"@type":"LikeAction"},"userInteractionCount":3},{"@type":"InteractionCounter","interactionType":{"@type":"CommentAction"},"userInteractionCount":0}],"keywords":"golang","timeRequired":"PT3M"}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://daily.dev"},{"@type":"ListItem","position":2,"name":"Lobsters","item":"https://daily.dev/sources/lobsters"},{"@type":"ListItem","position":3,"name":"Soppo"}]}
{"@context":"https://schema.org","@type":"FAQPage","@id":"https://daily.dev/posts/soppo-karjn0zhl#faq","mainEntity":[{"@type":"Question","name":"How does Soppo handle error propagation compared to Go's if err != nil pattern?","acceptedAnswer":{"@type":"Answer","text":"Soppo uses a question-mark operator to propagate errors concisely instead of repetitive if err != nil blocks. Writing data := os.ReadFile(path) ? automatically propagates the error, while a variant like result := parse(data) ? err { ... } lets you add custom handling with a named error when needed, keeping error handling explicit but less verbose. daily.dev surfaces language design experiments like this for developers tired of boilerplate error handling in go."}},{"@type":"Question","name":"How does Soppo prevent nil pointer dereference bugs at compile time?","acceptedAnswer":{"@type":"Answer","text":"Soppo tracks nilability at compile time using a question-mark marker on types, such as func findUser(id int) ?*User. After a nil check like if user != nil, the compiler automatically narrows the type from ?*User to *User, catching potential nil dereference bugs before the code runs rather than at runtime like standard go. developers weighing safer alternatives to go's nil handling can track projects like this on daily.dev."}},{"@type":"Question","name":"How do I install the Soppo programming language?","acceptedAnswer":{"@type":"Answer","text":"The recommended installation method is through SOPMOD, a version manager that handles installing, updating, and switching between Soppo versions, and also manages the Go toolchain it depends on internally. Alternatively, you can install just the sop binary directly via Cargo, which requires both Rust and Go to already be installed. daily.dev helps developers evaluating new toolchains like soppo find setup details quickly."}}]}
```

