Go, with the features it's missing.
Type safety and ergonomics, with full interop.
Take the tourSoppo uses Go syntax wherever possible. If you know Go, you already know most of Soppo. Use any Go library directly, keep your existing tooling, and introduce Soppo gradually into existing projects.
package main import ( "fmt" "net/http" ) func main() { http.HandleFunc("/", handleHome) http.HandleFunc("/hello", handleHello) fmt.Println("Server starting on :8080") http.ListenAndServe(":8080", nil) }
Tagged unions with struct variants, and pattern matching that ensures you handle every case. The compiler catches missing branches before your code ever runs, so you can refactor with confidence.
type Shape enum { Circle struct { radius float64 } Rectangle struct { width, height float64 } } func area(s Shape) float64 { match s { case Shape.Circle{radius: r}: return 3.14159 * r * r case Shape.Rectangle{width: w, height: h}: return w * h } }
?
No more repetitive if err != nil blocks. The
? operator propagates errors concisely while
keeping error handling explicit and visible. Add custom
handling when you need it.
func processFile(path string) error { // Propagate error directly data := os.ReadFile(path) ? // Custom handling with named error result := parse(data) ? err { return fmt.Errorf("parse failed: {err}") } fmt.Println("Processed {len(result)} items") return nil }
Nil pointer dereferences are a common source of runtime panics in Go. Soppo tracks nilability at compile time, so you catch these bugs before they reach production. After a nil check, the type narrows automatically.
func findUser(id int) ?*User { // ? marks nilable if id == 0 { return nil } return &User{name: "Alice"} } func greet(id int) { user := findUser(id) if user != nil { // user is *User here, not ?*User fmt.Println("Hello {user.name}") } }
When something goes wrong, Soppo tells you exactly what happened and where. Error messages show the relevant code, highlight the problem, and suggest how to fix it. Debugging should be straightforward, not a guessing game.
× Non-exhaustive match ╭─[main.sop:11:5] 10 │ var message string 11 │ ╭─▶ match colour { 12 │ │ case Colour.Red: 13 │ │ message = "Stop" 14 │ │ case Colour.Yellow: 15 │ │ message = "Wait" 16 │ ├─▶ } · ╰──── missing variants: Green 17 │ } ╰──── help: Ensure all enum variants are handled, or add a `default` case
The recommended way to install Soppo is through SOPMOD, a version manager that handles installing, updating, and switching between versions. It also manages Go, which Soppo uses internally for compilation.
Then add to your PATH and install:
Or install just the sop binary using Cargo,
without SOPMOD (requires
Rust
and Go):