Hello, fellow Gophers!
I’m excited to share a venture I’ve been engaged on: a fully Cgo-free Go binding for the Slint UI engine, at the moment engaged on each Home windows and Linux (amd64).
For years, constructing native desktop GUIs in Go has compelled builders into tough trade-offs: both pull in a heavy net stack (Chromium/Webview by way of Wails), cope with immediate-mode UI quirks, or wrestle with Cgo dependencies that break quick construct occasions and painless cross-compilation.
Slint is a contemporary, declarative GUI toolkit written in Rust with tiny useful resource footprints, lovely reactive DSL, and a royalty-free desktop license for proprietary apps. Nevertheless, bringing a Rust engine into Go with out Cgo appeared almost not possible at first. Right here is how it’s constructed below the hood.
The Technical Problem: Why Zero-Cgo is Exhausting
In normal Go growth, linking international native libraries requires Cgo. To remove Cgo solely, two main hurdles needed to be solved:
- Self-contained Native Static Library
Cgo can’t be eliminated if the construct course of nonetheless invokes C compiler headers. We bundled the whole Slint engine and its C-API wrapper (SlintCFlat) right into a single, precompiled static archive. - **Direct Linking by way of
.syso**
The Go compiler (cmd/compileandcmd/hyperlink) natively acknowledges.syso(System Object) information positioned inside a package deal listing and hyperlinks them straight with out invoking GCC or Clang. However merely dropping a.sysointo the package deal doesn’t work out of the field.
The Secret Sauce: Bridging the ABI Hole with Trampolines
The core purpose .syso linking fails for international features is the ABI mismatch:
- Go makes use of its personal inner calling conference (traditionally Plan 9 ABI on x86, transitioning to register-based ABI0/ABIInternal).
- This conference is basically incompatible with normal OS calling conventions (Microsoft x64 ABI on Home windows, System V AMD64 ABI on Linux).
- Register allocations, stack alignment, and shadow area differ fully.
To make the Go compiler name into Slint’s C-API straight:
- Each single API perform requires an meeting trampoline that saves the Go runtime state, aligns the stack in accordance with the host platform’s ABI, strikes parameters from Go’s registers/stack into host registers (e.g.,
RCX/RDX/R8/R9on Home windows orRDI/RSI/RDX/RCXon Linux), calls the international image, and marshals return values again to Go. - Writing lots of of meeting trampolines by hand throughout a number of OS targets is nearly not possible to take care of.
To unravel this, the trampolines and Go perform stubs are routinely generated by the Hike language compiler (a toolchain I developed). You don’t have to jot down a single line of meeting or C.
The End result
By combining pre-packaged .syso archives with compiler-generated meeting trampolines, we obtain:
- Zero Cgo:
CGO_ENABLED=0builds simply work.(Nevertheless, you will need to manually specify the choices that the Slit package deal passes to the linker.) - Lightning-fast compilation: Immediate builds utilizing pure Go toolchains.
- Single static binaries: Slint markup information are merged by way of our bundling instrument and embedded straight into the binary utilizing
go:embed.
Presently, the binding makes use of the Slint dynamic interpreter to judge UI markup at runtime. We hope that sooner or later the Slint workforce will present first-class native Go code era, however for now, this offers a quick, light-weight, and trendy GUI expertise for Go builders.
Take a look at the repository and examples right here:
Suggestions, questions concerning the linking internals, and recommendations are very welcome!
“Go perform stubs are routinely generated by the Hike language compiler (a toolchain I developed).”
Very cool stuff!
It’s additionally attention-grabbing to see within the venture that UI occasions are dealt with in type of queues, if I’m not incorrect (Jogs my memory of io_uring( io_uring – Wikipedia ) however used at the next stage).

