let-go: A Compact, Fast Clojure Implementation Written in Go Challenges JVM Dominance
#Dev

let-go: A Compact, Fast Clojure Implementation Written in Go Challenges JVM Dominance

Startups Reporter
5 min read

let-go introduces a novel approach to running Clojure-like code with remarkable performance characteristics, offering a 10MB binary with 7ms startup time and seamless Go interop.

The programming language ecosystem continues to evolve with innovative implementations that challenge traditional runtime environments. let-go, a project by developer nooga, presents an intriguing alternative for developers who appreciate Clojure's expressive syntax but seek the performance characteristics and deployment simplicity of Go.

Featured image

At its core, let-go is a bytecode compiler and virtual machine that implements a dialect of Clojure entirely in Go. This implementation delivers impressive metrics: a single ~10MB binary with approximately 7ms cold start time, significantly outperforming traditional Clojure on the JVM and even other alternatives like Babashka.

Bridging Two Worlds

The project addresses several pain points developers face when working with Clojure. Traditional JVM-based Clojure applications suffer from large memory footprints and slow startup times, making them less suitable for certain use cases like serverless functions, CLI tools, or web applications requiring rapid initialization.

let-go solves these issues through several key technical decisions:

  • Pre-compiled bytecode format (LGB): Enables near-instant startup even with large standard libraries
  • Standalone executables: Compile programs to self-contained binaries with lg -b myapp main.lg
  • WASM compilation: Generate self-contained HTML web applications with lg -w outdir main.lg
  • Minimal runtime: No external dependencies needed for distribution

These capabilities make let-go particularly interesting for developers who need to distribute Clojure-like applications without the JVM overhead or for scenarios requiring fast startup times.

Performance Benchmarks

The project's GitHub page includes comprehensive benchmarks comparing let-go against several alternatives:

  • Binary size: 10MB vs 68MB (Babashka), 304MB (JDK)
  • Startup time: 7ms vs 20ms (Babashka), 331ms (JVM Clojure)
  • Idle memory: 14MB vs 27MB (Babashka), 92MB (JVM Clojure)
  • Short-lived tasks: 8ms for map/filter pipelines vs 19ms for Babashka
  • Memory efficiency: 14MB for fib(35) vs 77MB for Babashka

These metrics position let-go as an attractive option for cloud-native applications, serverless functions, and resource-constrained environments where traditional Clojure implementations struggle.

Clojure Compatibility

A critical aspect of any language implementation is compatibility with the original. let-go aims to maintain high fidelity with Clojure while leveraging Go's strengths. The project reports 95.4% test suite compliance (4696/4921 assertions) against jank-lang/clojure-test-suite, covering most core Clojure features:

  • Macros, destructuring, protocols, records, multimethods
  • Atoms, regex, metadata, BigInts
  • Core.async channels with real goroutines
  • Comprehensive standard library including HTTP, JSON, Transit

Some limitations exist, mostly around edge cases like numeric overflow detection, BigDecimal support (which is intentionally omitted), and certain advanced features like STMRefs and agents. The project maintains a clear list of known limitations and behavioral differences.

Go Interop Capabilities

Perhaps the most compelling feature of let-go is its seamless integration with Go programs. The implementation provides robust interop mechanisms:

  • Struct mapping: Go structs can be registered as let-go records with cached field converters
  • Zero-cost roundtrip: Unmutated records can convert back to Go structs without performance penalty
  • Function calling: Go functions can be called from let-go and vice versa
  • Channel integration: Go channels work directly with let-go's core.async implementation

This interop makes let-go particularly interesting for Go development teams that want to experiment with functional programming paradigms or leverage Clojure's strengths for specific domains without abandoning their existing codebase.

Practical Applications

The project includes several real-world examples demonstrating its capabilities:

  • xsofy: A roguelike game that runs both in the browser and terminal from the same source
  • lgcr: A container runtime built on let-go's syscall namespace
  • Web applications: Full terminal emulation via xterm.js in browser-based WASM builds

These examples showcase let-go's versatility across different domains and deployment targets.

Development Experience

let-go provides a familiar development experience for Clojure developers:

  • REPL interaction with lg command
  • nREPL server integration for Emacs (CIDER), VS Code (Calva), and Neovim (Conjure)
  • Support for Babashka pods, extending functionality with database, AWS, Docker, and file watching capabilities
  • WASM builds can run directly in browsers with GitHub Pages deployment

The project also includes comprehensive documentation and testing infrastructure, ensuring developers can productively use let-go for real applications.

Market Positioning

let-go enters a space populated by several Clojure alternatives:

  • Clojure on JVM: The original implementation with mature ecosystem but significant startup and memory overhead
  • Babashka: GraalVM-based native compiler with good performance but larger binary size
  • Joker: A Go-based tree-walk interpreter with smaller footprint but slower performance
  • jank: A new Clojure implementation in C with different design goals

let-go differentiates itself through its unique combination of:

  • Extreme performance and small footprint
  • Seamless Go integration
  • Versatile deployment options (native, WASM, embedded)
  • Familiar Clojure syntax and semantics

Potential Impact

The project represents an interesting convergence of programming language design and runtime optimization. By leveraging Go's strengths while maintaining Clojure's expressive syntax, let-go could appeal to several developer segments:

  • Go teams wanting to experiment with functional programming
  • Clojure developers needing faster startup or smaller deployment packages
  • Organizations building cloud-native applications requiring rapid initialization
  • Developers creating cross-platform tools that need to run efficiently on multiple architectures

Conclusion

let-go demonstrates that innovative language implementations can continue to emerge, even in established ecosystems. By combining the expressive power of Clojure with the performance characteristics of Go, this project offers developers a compelling alternative for certain use cases. While it may not replace Clojure on the JVM for all applications, its impressive metrics and versatile deployment options make it a valuable addition to the Clojure family of implementations.

For developers interested in exploring this implementation, the project is available on GitHub with comprehensive documentation, examples, and installation instructions for Linux, macOS, and Windows.

Comments

Loading comments...