Experience porting 4.5k loc of C to Go (Facebook's CSS flexbox implementation Yoga)

Experience porting 4.5k loc of C to Go (Facebook's CSS flexbox implementation Yoga)

part of Go Cookbook
This article describes my 2017 port of a particular Yoga revision. It is a record of that work, not a claim that the port implements today’s Yoga API or passes its current test suite.
I was working on desktop GUI apps for Windows in Go.
The apps used native Win32 controls, so I did not have to implement buttons, list boxes, and other controls.
However, Win32 doesn’t have anything to help you lay things out on the screen. Manual positioning is painful.
I started looking into writing a layout engine. There are many systems to be inspired by.
I had done some work in WinForms so I could copy their ideas.
I could look into copying Android’s layout logic.
I could look into implementing constraint-based layout like in iOS.
The option I liked best was CSS flexbox. It’s not the simplest, it’s not the most complicated, but the biggest win is that many people, including myself, are already familiar with it.
I started looking for an implementation in Go. I found one in the shiny project, but it’s a simplified implementation.
The implementation I chose was Facebook’s Yoga, which at the time was written in C, with bindings to several languages.
I could write cgo bindings but I prefer my Go pure so I decided to manually port the C code to Go, all 4.5k lines of it.
The result is the flex package.
Here’s how the porting process went.

Phase zero - picking a name for the package

It was tempting to pick a name that would be some combination of yoga and go e.g. goyoga or yoga-go.
That would be a bad idea. Including go in a package or repository name is bad (but unfortunately happens too often).
My rules for a good name for a Go library are:
Therefore github.com/kjk/flex was born.
I could use yoga, to better highlight the connection with the original project, but it’s not descriptive.
I could use flexbox to better highlight the connection with CSS flexbox, but it’s a bit long.

Phase one - getting the Go port to compile

You can’t port 4.5k lines of code in one sitting so it was important to have a strategy that allows for making incremental progress.
The important part was keeping track of porting progress. For that I cloned Yoga’s repository. After converting a piece of C code to Go I would delete it from my fork. That way I would keep track of code that still needs to be ported.
It’s important to get an early boost so I started with the simplest parts: structures, enums.
The porting process was boring but relatively uneventful.
Some porting comments:
It took me 2 days to port the C code to Go, get it compiling and passing some tests. But some tests were failing.

Phase two - fixing bugs

Yoga had many tests, which gave me confidence that porting them would help verify the Go implementation.
After porting the code, I started porting the tests, and they were failing.
I was in a pickle.
Two days were enough to mechanically port the code but not even close enough to understand it.
At the time, I converted a failing test into a standalone program to debug it. Today, Delve supports dlv test, and the Go extension for VS Code can debug individual tests directly.
I was pleasantly surprised that the debugger in Visual Studio Code worked well.
I found one porting mistake by stepping through the code but fixing it didn’t fix test failures.
I spent a bit more time stepping through the code but not knowing what results to expect I decided that it was not a promising approach.
I decided to re-check every line of code.
Initially I ported the code in random order, so while rechecking it I also rearranged the code to match the order of the C code. That will help when porting Yoga improvements in the future.
It was predictably boring. It took another day, and I found two more porting bugs.
The tests were still failing and I was a bit stuck.
I did another pass in the debugger and fortunately inspiration struck.
Yoga used NaN to represent undefined. I noticed that the fmaxf function that I wrote returned NaN when any argument was NaN.
I compared my fmaxf implementation with the C implementation, and it turns out that if one of the arguments is NaN but the other one isn’t, it should return a non-NaN value.
It was easy to fix and that fixed all the tests.

Phase three - Go-ifying the API

The code was working but it was far from idiomatic Go code.
The next phase was tweaking the API to be more Go-like.
The majority of changes were:
It’s a mechanical, boring process.
Some transformations could be done with regex search & replace. The rest required manual edits.
Having lots of tests really helped in making sure that the code is still correct.

The status

The port was finished and passed the tests from the Yoga revision I ported.
The API is still a bit awkward by Go standards. Too many methods, not enough direct access to variables.
Partly it’s because I wanted to stay close to C code so that future changes to Yoga can be ported easily.
Partly it’s because of implementation choices and the nature of the problem.
There are many flexbox properties, but only some are specified for a given element. The rest take default values, which might be undefined.
How to represent that in Go? Ideally, the zero value of the type would represent the default value so that a style definition can be constructed as &Style{} and then we can set the properties that are non-default.
Unfortunately, that doesn’t work well for properties that are numbers because zero might be a valid value.
Yoga uses float32 for numbers and NaN for the undefined value of a CSS property.
Another option is used in the database/sql package for types like sql.NullString that need to indicate nullability. They are represented as a struct that combines a value and a bool field, Valid. The zero value of a bool is false, so by default values start as invalid (undefined).
That makes setting and reading values more explicit. Since Go 1.18, an application can express this pattern once as a generic Optional[T] struct. Go 1.22 also added sql.Null[T] for nullable database values; a UI model need not depend on database/sql to use the same idea.
At the end of the day it’s better to have solid, working code with awkward API than not having the code at all. A full flexbox implementation is not trivial to write.
The resulting flex package met my needs at the time. Check its supported behavior against your requirements before using it for a new layout engine.

Notes on automatic translation

Go is so close to C. Wouldn’t it be great if there was a program that could take C code and turn it into Go code?
There were a few attempts to do that:
I did not use these tools for the 2017 port. Their capabilities have changed since then, so this list is historical rather than a current comparison. For a new translation, test the tool against the actual C code and validate the result against the original implementation.
#go
Sep 5 2026

Related articles

Feedback about page:

Feedback:
Optional: your email if you want me to get back to you: