Solod v0.2: Networking, new targets, friendlier interop

Lobsters Hottest Tools

Summary

Solod v0.2 releases with networking support (TCP, UDP, Unix sockets), new compilation targets (32-bit, WebAssembly, bare metal), and improved C interoperability.

<p><a href="https://lobste.rs/s/glklp0/solod_v0_2_networking_new_targets">Comments</a></p>
Original Article
View Cached Full Text

Cached at: 06/29/26, 08:31 PM

# Solod v0.2: Networking, new targets, friendlier interop Source: [https://antonz.org/solod-v0-2/](https://antonz.org/solod-v0-2/) Solod \(**So**\) is a system\-level language with Go syntax, zero runtime, and a familiar standard library\. It's designed for two main audiences: - Go developers who want low\-level control and zero\-cost C interop without having to learn Zig or Odin\. - C developers who like Go's style\. The[previous version](https://antonz.org/solod-v0-1)\(v0\.1\) focused on porting core Go stdlib packages and providing convenient C interop\. At the end of that post, I said the next release would focus on networking, concurrency, or both\. Now, networking is here — the v0\.2 release I'm sharing today includes support for TCP, UDP, and Unix domain sockets\. Concurrency is still planned for the future, so for now, servers handle one connection at a time\. This release also lets you compile So to more targets, like 32\-bit platforms, WebAssembly, and bare metal\. And C interop even smoother\! [Networking](https://antonz.org/solod-v0-2/#networking)•[TCP server](https://antonz.org/solod-v0-2/#tcp-server)•[TCP client](https://antonz.org/solod-v0-2/#tcp-client)•[Deadlines](https://antonz.org/solod-v0-2/#deadlines)•[IP addresses](https://antonz.org/solod-v0-2/#ip-addresses)•[Targets](https://antonz.org/solod-v0-2/#new-targets)•[Interop](https://antonz.org/solod-v0-2/#friendlier-interop)•[Stdlib](https://antonz.org/solod-v0-2/#more-stdlib)•[Wrapping up](https://antonz.org/solod-v0-2/#wrapping-up) ## Networking The main feature in v0\.2 is the`net`package\. It's a simplified version of Go's`net`package which supports the three most commonly used transports: - **TCP**\(networks`tcp`,`tcp4`,`tcp6`\) via`ResolveTCPAddr`,`DialTCP`, and`ListenTCP`, with the`TCPConn`and`TCPListener`types\. - **UDP**\(networks`udp`,`udp4`,`udp6`\) via`ResolveUDPAddr`,`DialUDP`\(a connected socket\), and`ListenUDP`\(an unconnected socket with`ReadFrom`/`WriteTo`\)\. - **Unix domain sockets**\(`unix`for streams,`unixgram`for datagrams\) via`ResolveUnixAddr`,`DialUnix`,`ListenUnix`, and`ListenUnixgram`\. The API mirrors Go closely, so most of it will feel familiar\. The big difference is that So has no goroutines, so there's no concurrent server support — you accept and serve connections sequentially\. More on that in a moment\. ## TCP server Let's build a classic: an echo server that accepts a connection, reads a message, and sends it back\. ``` package main import "solod.dev/so/net" func main() { // Resolve the local address to listen on. laddr, err := net.ResolveTCPAddr("tcp", "127.0.0.1:8080") if err != nil { panic(err) } // Start listening on the local address. ln, err := net.ListenTCP("tcp", &laddr) if err != nil { panic(err) } defer ln.Close() println("listening on", "127.0.0.1:8080") // Accept connections and serve them in a loop. for { conn, err := ln.Accept() if err != nil { panic(err) } serve(&conn) } } // serve reads one message from the connection, echoes it back, // and closes the connection. func serve(conn *net.TCPConn) { defer conn.Close() var buf [256]byte n, err := conn.Read(buf[:]) if err != nil { return } conn.Write(buf[:n]) } ``` ``` listening on 127.0.0.1:8080 ``` If you've written a TCP server in Go, this should look familiar —`ListenTCP`, an`Accept`loop, and`Read`/`Write`on the connection\. The only thing missing is a`go serve\(conn\)`: without goroutines, each connection is handled to completion before moving on to the next`Accept`\. ## TCP client The client starts the connection using`DialTCP`, then uses`Write`to send a request and`Read`to get the reply: ``` package main import "solod.dev/so/net" func main() { // Resolve the server address. raddr, err := net.ResolveTCPAddr("tcp", "127.0.0.1:8080") if err != nil { panic(err) } // A nil laddr lets the system choose the local address. conn, err := net.DialTCP("tcp", nil, &raddr) if err != nil { panic(err) } defer conn.Close() // Send a request and read the reply. conn.Write([]byte("hello")) var buf [256]byte n, err := conn.Read(buf[:]) if err != nil { panic(err) } println(string(buf[:n])) } ``` UDP and Unix domain sockets work in a similar way\. For UDP, an unconnected`ListenUDP`socket uses`ReadFrom`to get data and the sender's address, and`WriteTo`to send a reply\. For Unix sockets, there are`ListenUnix`\(stream\) and`ListenUnixgram`\(datagram\)\. ## Deadlines By default,`Accept`,`Read`, and`Write`are blocking\. In Go, you'd typically use goroutines and contexts to prevent getting stuck forever\. Since that's not available in So \(yet\), every connection and listener supports deadlines instead: ``` // Give the client 5 seconds to send something. conn.SetReadDeadline(time.Now().Add(5 * time.Second)) n, err := conn.Read(buf[:]) if err == net.ErrTimeout { // The client went quiet; drop the connection. return } ``` `SetDeadline`,`SetReadDeadline`, and`SetWriteDeadline`are available on`TCPConn`,`UDPConn`,`UnixConn`, and listener types\. When the deadline passes, any pending call fails with`net\.ErrTimeout`\. If you don't set a deadline, a blocked call will wait forever\. This isn't concurrency, but it's enough to keep a single\-threaded server responsive\. ## IP addresses Along with`net`, v0\.2 ports Go's`net/netip`package, which provides small, allocation\-free value types for IP addresses\.`Addr`represents an IP address,`AddrPort`combines an IP address with a port, and`Prefix`is an IP with a prefix length \(a CIDR block\): ``` addr, err := netip.ParseAddr("192.168.1.10") if err != nil { panic(err) } println(addr.Is4()) // true ap := netip.AddrPortFrom(addr, 8080) println(ap.Port()) // 8080 prefix := netip.MustParsePrefix("192.168.1.0/24") println(prefix.Contains(addr)) // true ``` These are simple value types that don't use any heap allocation, which fits well with So's explicit\-memory approach\. The`net`package also provides`SplitHostPort`and`JoinHostPort`functions to help you work with`host:port`strings\. ## New targets Solod compiles to plain C, which \(in theory\) means it can target anything a C compiler can\. Because of this, v0\.2 adds new targets: - **32\-bit platforms**\. The compiler and stdlib now work correctly on 32\-bit platforms, where`int`and pointers are narrower\. - **WebAssembly \(WASI\)**\. You can compile a So program to`wasm32\-wasi`and run it under any WASI runtime\. - **Freestanding mode**\. So programs can run on bare\-metal systems without any C standard library\. No libc means no malloc, but you can use`mem\.Arena`instead\. Here's the complete toolchain you need to build a freestanding`wasm32`binary using`zig cc`: ``` export CC="zig cc" export CFLAGS="-Oz --target=wasm32-freestanding -nostdlib -Wl,--no-entry -Wl,--export=main" so build -o main.wasm . ``` A large part of the standard library \(`bytes`,`strings`,`strconv`,`slices`,`maps`,`math`,`encoding/binary`, and more\) works just fine in freestanding mode\. For more details, check out the[freestanding guide](https://github.com/solod-dev/solod/blob/main/doc/freestanding.md)\. ## Friendlier interop A bunch of smaller changes make Solod nicer to write\. **Three new directives**for low\-level work, all documented in the[interop guide](https://github.com/solod-dev/solod/blob/main/doc/interop.md): ``` //so:volatile var counter int // emits a C volatile //so:thread_local var perThread int // emits C11 _Thread_local //so:attr packed type header struct { // emits __attribute__((packed)) version byte length int } ``` `so:attr`works with variables, constants, types, and functions\. You can use it on multiple lines, and the attributes will stack\. For example,`//so:attr aligned\(16\)`will combine with`//so:attr packed`\. **Type aliases**\. So now supports Go\-style type aliases: **Numeric C types**\. The`so/c`package now includes named types for C's numeric types —`Int`,`UInt`,`Long`,`Short`,`UChar`,`LongLong`, and others\. When you declare an extern function, you can use the actual C types in its signature instead of trying to guess the correct fixed\-width Go type for your platform\. **Third\-party packages**\. You can now add external So packages using`go install`or by vendoring, and you can organize your own code into multiple modules\. So doesn't have a real package ecosystem yet, but it's a good start\. **Better diagnostics**\. By default, panic messages report the C file and line\. Pass`\-\-track\-source`to report the original So source location instead: There's also an optional`\-\-check\-nil`flag that adds nil\-pointer checks when accessing struct fields and calling interface methods\. This way, if there's a bad dereference, the program will panic cleanly instead of causing a segmentation fault\. Both options are off by default to keep the generated code more readable\. ## More stdlib Beyond`net`and`net/netip`, v0\.2 adds a few more packages: - `encoding/hex`— hex encoding and decoding, including`Dump`for hexdump\-style output\. - `uuid`— generating and parsing UUIDs \(v4 and v7\), with random components from a cryptographically secure source\. And a small but handy update to memory management:`mem\.Arena\.Free`now reclaims the last allocation if you give it the matching pointer\. It's a minor optimization, but it means a quick alloc/free pair on an arena no longer wastes space\. [Stdlib documentation](https://github.com/solod-dev/solod/blob/main/doc/stdlib.md) ## Wrapping up With v0\.2, Solod has evolved from just "command\-line tools and C glue" into something you can actually use on a network — like a TCP or UDP server, a small protocol client, or a Unix\-socket daemon\. The new targets \(32\-bit, WASM, freestanding\) mean the same code can now run in more places, even down to bare metal\. The big thing that's still missing is concurrency\. A server that handles requests one at a time works for some tasks, but a real network service needs to manage many connections at once\. That's the obvious goal for v0\.3 — adding some kind of concurrency, along with the stdlib packages that support it\. If you're interested, take a look at So's[readme](https://github.com/solod-dev/solod#readme)— it has everything you need to get started\. Or[try So online](https://codapi.org/so)without installing anything\. [★ Subscribe](https://antonz.org/subscribe/)to keep up with new posts\.

Similar Articles

Better Sol

Product Hunt

Better Sol is a tool that enables developers to build end-to-end Solana applications using TypeScript.

Zig ELF Linker Improvements Devlog

Hacker News Top

The new Zig ELF linker now supports fast incremental compilation with external libraries and C sources, enabling rebuilds in milliseconds on x86_64 Linux.

QSOE: QNX-inspired OS with dual-kernel architecture

Hacker News Top

QSOE project v0.1 is released, providing a QNX-compatible operating system with two microkernel variants (custom Skimmer kernel and seL4-based), shared userspace, and support for SiFive Unmatched RISC-V hardware.

Stalwart v0.16: A New Foundation

Lobsters Hottest

Stalwart v0.16 is a major release featuring a complete architectural rebuild with a new WebUI, unified JMAP-based management replacing REST API, external OIDC authentication support, and a new CLI tool for administration and infrastructure-as-code workflows.