So: Clean C from Modern Go
Detailed Description of Solod: A Better C with Go Syntax
Introduction to Solod (Solod is a better C)
At first glance, integrating high-level languages like Go into low-level environments such as C can pose significant challenges in terms of memory management, performance overhead, and interoperability. However, Solod emerges as an innovative solution that aims to bridge this gap by offering a modern, safer, and more intuitive way to program systems with Go’s syntax while still leveraging the power and efficiency of C11. Solod (or "So" in shorthand) is a strict subset of Go designed specifically for systems programming. It translates from pure Go code directly into readable, functional, yet minimalistic C11 code—eliminating runtime overheads like garbage collection or reference counting.
The core philosophy behind Solod revolves around simplicity and readability. By discarding unnecessary features that complicate the language (such as channels, goroutines, closures, and generics), Solod focuses on essential constructs while ensuring compatibility with the standard Go toolchain—including syntax highlighting, LSP integration, linting, and testing via go test.
Core Design Philosophy
1. "Go in, C Out" – Seamless Transpilation
Solod allows developers to write their code in familiar Go syntax while producing clean and maintainable C11 implementations. This approach ensures that the generated C code remains syntactically and logically coherent with human expectations of structured programming. Unlike other transpilers that often generate cryptic or bloated C, Solod maintains readability by leveraging GCC/Clang extensions and explicit type declarations.
2. No Runtime Overhead
A fundamental principle of Solod is the absence of runtime abstractions such as garbage collectors (GC), reference counting, or memory pools. Everything—including variables and functions—is stack-allocated by default. Heap allocations are opt-in through the standard library (so/mem), ensuring no hidden memory management complexities. This design choice eliminates the overhead of dynamic memory management while still allowing controlled heap usage where necessary.
3. Manual Memory Management with Clarity
The language does not hide manual memory management behind abstractions like smart pointers or automatic allocation schemes. Instead, developers manually handle memory via alloca, heap allocations, and explicit deallocation. This approach ensures predictable behavior but requires vigilance to avoid memory leaks or dangling references. AddressSanitizer (-fsanitize=address) can be used during development to detect common memory-related bugs.
4. Native C Interoperability
Solod aims to create a seamless bridge between Go and C, supporting direct interaction without the need for runtime wrappers like CGO (C Go). Functions written in Solod can call standard C libraries directly, while So code can be called from raw C with minimal overhead. This feature enables developers to extend their systems programming projects by integrating existing C libraries or vice versa.
Language Features and Limitations
Supported Go Syntax
Solod retains compatibility with a subset of Go’s syntax while stripping out features that could complicate the generated C code:
- Structs: Defined in C headers (
struct Person { ... }) and implemented in.cfiles. - Methods: Implemented as function pointers with
selfparameter, mirroring traditional C-style callbacks. - Interfaces: Translated into concrete implementations or verified at compile time via structural typing.
- Slices: Supported through manual array handling, as Solod does not include slice growth logic (similar to Go’s slice creation mechanism).
- Multiple Return Values: Allowed in function signatures (
so_int func Foo() (a, b int)). defer: Implemented similarly to C’s equivalent (calling a function when it exits).
Excluded Features
To maintain simplicity, Solod intentionally omits the following:
- Channels: The Go concurrency model is not supported.
- Goroutines: Parallelism via threads is handled externally, requiring explicit synchronization.
- Closures: Lambda functions and closures are forbidden to prevent complexity.
- Generics: Type parameters are not yet included in So (as of its current state).
- Maps: Static or dynamic hash maps are absent; alternatives like arrays with indexing must be used.
This minimalist approach ensures that the generated C code remains concise and predictable while avoiding pitfalls like memory leaks from GC-induced operations.
Example: A Simple Person Struct
To illustrate Solod’s capabilities, let’s break down how a simple Go program is transpiled into C:
1. Original Go Code
package main
type Person struct {
Name string
Age int
Nums [3]int // Fixed-size array
}
func (p *Person) Sleep() int {
p.Age += 1
return p.Age
}
func main() {
p := Person{Name: "Alice", Age: 30}
p.Sleep()
println(p.Name, "is now", p.Age, "years old.")
p.Nums[0] = 42 // Assign value to array index
println("1st lucky number is", p.Nums[0])
}
The Go code defines a struct Person, implements a method (Sleep), and uses basic type declarations. Note that slices are not used here; arrays with fixed sizes are preferred for direct C compatibility.
2. Generated Header File (.h)
#pragma once
#include "so/builtin/builtin.h" // Core So types
typedef struct main_Person {
so_String Name;
so_int Age;
so_int Nums[3];
} main_Person;
so_int main_Person_Sleep(void* self);
Here, so/builtin/builtin.h provides essential low-level utilities like string management (so_String). The generated struct reflects the Go definition, with explicit type declarations for C compatibility.
3. Implementation File (.c)
#include "main.h"
so_int main_Person_Sleep(void* self) {
main_Person* p = (main_Person*)self;
p->Age += 1;
return p->Age; // Return updated age via method invocation
}
int main(void) {
main_Person p = {
.Name = so_str("Alice"),
.Age = 30,
};
main_Person_Sleep(&p);
so_println("%.\*s %s %d %s", p.Name.len, p.Name.ptr, "is now",
p.Age, "years old.");
p.Nums[0] = 42;
so_println("1st lucky number is %d", p.Nums[0]);
}
This C implementation shows:
- Method Binding: The
Sleepfunction is bound to a struct viavoid* self. - String Management: Go’s
stringtype maps toso_String, initialized withso_str(). - Dynamic Allocation: The stack-allocated
Personobject is passed by reference (&p).
4. Key Observations
- No GC or Dynamic Typing: Memory is managed manually; no hidden allocations occur during method calls.
- Direct Array Access: Arrays like
Nums[3]are treated as C-style arrays, allowing direct indexing. - Output Formatting: The
so_printlnmacro mimics Go’sprintln, but with explicit format specifiers.
Installation and Usage Workflow
1. Setting Up Solod
To begin working with Solod, follow these installation steps:
# Install the So command-line tool globally
go install solod.dev/cmd/so@latest
# Initialize a Go module (optional but recommended)
go mod init example
go get solod.dev@latest
The solod (or so) binary is then used to compile, transpile, and run So code.
2. Writing Solod Code
Unlike standard Go modules, which rely on the standard library (net/http, os), Solod encourages importing its own standard library packages:
package main
import "solod.dev/so/c/math" // Example: C-compatible math utilities
func main() {
ans := math.Sqrt(1764) // Compute square root using So's math package
println("Hello, world! The answer is", int(ans))
}
3. Transpiling to C
The process of converting Solod code into readable C involves three primary commands:
- Transpile Only
Generates a C header and implementation file in
generated/:
so translate -o generated/
- Compile and Link Combines transpilation with compilation into a binary executable:
# Uses system C compiler (GCC/Clang) via CC environment variable
so build -o main.exe .
- Direct Execution Compiles, links, and runs the program in one step:
so run . > output.txt
4. Running So Code from C
Since Solod’s generated code is pure C, it can be invoked from existing C libraries or projects:
// Example: Calling a So function from raw C
extern int so_example_function();
int main() {
printf("Output from So function: %d\n", so_example_function());
}
So’s Standard Library
The standard library (so/stdlib) is intentionally minimal but growing, providing both high-level abstractions and low-level wrappers for the C standard library:
High-Level Packages
fmt: Prints formatted text (similar to Go’sfmt.Printf).math: Provides mathematical functions (Sqrt,Sin, etc.).strings: Utilities for string manipulation.time: Time-related utilities, though less polished than Go’s.
Low-Level Wrappers
Solod includes packages like:
so/c/stdlib: Basic C library abstractions (e.g.,malloc,free).so/cstdio: Standard input/output functions (printf,scanf).
These packages are designed to be self-contained, avoiding excessive dependencies on Go’s standard library while still offering useful tools for systems programming.
Testing So Code
Since Solod’s code is valid Go, existing test frameworks (e.g., go test) work seamlessly. Here’s how you might structure tests:
package main
func TestSleep(t *testing.T) {
p := Person{Name: "Alice", Age: 25}
updatedAge := Sleep(&p)
assert.Equal(t, 26, updatedAge)
}
func TestLuckNumbers(t *testing.T) {
p := Person{"Bob", 10, [3]int{}}
p.Nums[0] = 99
assert.Equal(t, 99, p.Nums[0])
}
To run these tests:
go test -v
The so compiler itself also includes its own tests to validate the transpilation process.
Compatibility with C and Constraints
Supported Environments
- Linux, macOS, and Windows (Core language support).
- GCC, Clang, or Zig’s compiler (
zig cc) for compilation. - Not supported: MSVC due to lack of required compiler extensions.
The generated code relies on these GCC/Clang features:
- Binary literals (
0b1010). - Statement expressions in macros (e.g.,
({...})). - Constructor attributes for package initialization.
- Type inference helpers like
__auto_type.
Limitations
- No Generics: Solod lacks type parameters, restricting reusable code templates beyond basic slices and arrays.
- Manual Memory Handling: Users must manage memory explicitly, increasing the risk of leaks or dangling pointers if not careful.
- Limited Features: The standard library is growing but still lacks advanced constructs like JSON parsing or crypto operations.
Design Decisions
Solod’s design prioritizes simplicity and readability above all else:
"Fewer Features Are Always Better" Each feature addition requires justification, ensuring that Solod remains lightweight. This philosophy excludes non-essential Go features (e.g., iterators) to avoid complexity.
Manual Memory Management as a Choice While stack allocation is default, heap allocations (
so/mem) are available for dynamic data structures like strings or slices. Ownership semantics must be clearly documented to prevent bugs.C Interop Without Runtime Overhead Solod avoids CGO’s indirection by directly embedding C types and functions. This simplifies integration with existing C libraries but requires developers to understand how C handles memory.
Readable but Not Perfect C Output The goal is to produce C code that is easy to debug and maintain, even if it sacrifices some modern C idioms (e.g., variadic macros are limited).
Roadmap for Future Development
| Status | Task Description |
|--------------|---------------------------------------|
| ✅ Done | Basic Go-to-C transpiler |
| ✅ Done | Low-level libc wrappers |
| ⏳ In Progress | Core stdlib packages (e.g., fmt, strings) |
| ⏳ In Progress | Maps (hash tables) |
| ⚠️ Upcoming | Error handling improvements |
Upcoming Features:
- Additional stdlib packages (JSON, HTTP).
- Full Windows support.
- More real-world examples to demonstrate use cases.
Contributing to Solod
To contribute to Solod’s development:
- Bug Fixes: Report issues via GitHub and provide minimal reproductions.
- Feature Discussions: Propose new features through issue discussions before coding.
- Code Review: Submit PRs only after reviewing existing codebase thoroughly (AI reviews are insufficient).
- Testing: Add tests for any new functionality.
The project’s philosophy emphasizes collaboration over feature bloat, ensuring incremental and meaningful changes.
License Information
- Go standard library components: BSD 3-Clause License.
- Solod transpiler and So stdlib code: BSD 3-Clause License by Anton Zhiyanov (https://antonz.org/).
Conclusion
Solod represents a bold step toward merging the best of Go’s syntax, type safety, and tooling with C’s efficiency and low-level control. By eliminating runtime overheads, prioritizing manual memory management, and providing seamless interoperability with C, Solod enables systems developers to write cleaner, safer code without sacrificing performance.
However, users must recognize its limitations: Solod is not a replacement for C or Go but rather a bridge between them. Those familiar with either language will find it intuitive, while those new to both may benefit from exploring examples and documentation thoroughly before adopting it in production environments.
For developers who need to write systems code without the complexity of garbage collection or hidden allocations, Solod offers a compelling solution—one that strikes a balance between readability and efficiency. Its growth hinges on the adoption of its growing standard library and real-world use cases, pushing forward its roadmap toward wider compatibility and utility.
Enjoying this project?
Discover more amazing open-source projects on TechLogHub. We curate the best developer tools and projects.
Repository:https://github.com/solod-dev/solod
GitHub - solod-dev/solod: So: Clean C from Modern Go
Detailed Description of Solod: A Better C with Go Syntax...
github - solod-dev/solod