This style resonates with me, even though my work is not a distributed system and not mission critical. I think a lot of it is broadly applicable – on design, assertions, tech debt, naming, …
I like the framing of asserting on positive and negative space. I’d never thought of it that way. Also the idea of asserting a lower and upper bound on loop iterations makes a lot of sense.
Some questions:
You mentioned asserting postconditions in functions. Does that mean you also enforce a single return path? Or use wrapper functions to do the asserting?
It’s an interesting contrast with points of the Go philosophy. You mentioned assertions in the talk. I also saw “Do not abbreviate variable names” in your doc, which Go is well known for. How far do you take that? Do you think Go’s approach has merits in other circumstances, or it’s just subjective?
How is Zig like for implementing your wire format encoding/decoding? Curious as I imagine comptime would be useful there (and since I work on an IDL/wire format at my job, FIDL).
For 2), we take it pretty far, but we also spend a lot of time to find actually good, catchy, short names for the core concepts. Naming-wise, “big endian” naming actually feels more unusual to me, and it is freaking great, I immediately switched my personal style to it, so, eg, I now use item_count rather than n_items, because that aligns with item_whatever so well.
For 3, zigs great, but not due to comptime. Our deserialization is std.mem.sliceAsBytes, which just casts raw bytes to whatever we want to deserialize (after verifying a strong u128 checksum, mind you). The two features of Zig we use here are:
ability to specify pointer alignment and track it in the type system
ability to precisely control layout of structs
The fist one, pointer alignment, is really cool! Just the other day I was writing a small “cast bytes to thing” code in both Rust and Zig, and I realized that my Rust code is UB only when Zig version didn’t compile because of the alignment error.
Great talk. I’ve often had a really strong inkling that we as an industry - and myself personally - don’t do enough up front design.
My question is - what does this upfront design actually look like? Is there a certain end artefact you expect before you start coding? I mean I can sit down and sketch system diagrams or write notes but it all feels a bit ad-hoc, there’s not much process to it.
I don’t work at TigerBeetle, but I have opinions on this. I think it turns out (in order of impact):
Just write code
Consider the inputs and outputs of the system … before you write code
Consider the inputs and outputs of subsystems (modules, classes, other units) … before you write code
Understand the data flow through the system (diagram, or written) … before you write code
Understand the data flow through the system over time. (e.g. sequence diagrams, etc) … before you write code
Formalize the properties by which inputs and outputs are correct (assertions, contracts, etc) … before you write code
Understand (next level, enforce) the invariants of the state of the system internal to the system (e.g. not at the interface boundaries) … before you write code
Formally model the system before you write any code using the invariants and other things discovered above … before you write code
Every additional step is going to reduce design errors, and you can always go back and iterate, but you’ll also end up needing to refactor in some cases…
Looking forward to watching this after work! I’ve enjoyed all the Systems Distributed talks so far.
Thanks Mitchell! Encouraging to hear that!
Just watched it! Also read TIGER_STYLE.md.
This style resonates with me, even though my work is not a distributed system and not mission critical. I think a lot of it is broadly applicable – on design, assertions, tech debt, naming, …
I like the framing of asserting on positive and negative space. I’d never thought of it that way. Also the idea of asserting a lower and upper bound on loop iterations makes a lot of sense.
Some questions:
You mentioned asserting postconditions in functions. Does that mean you also enforce a single return path? Or use wrapper functions to do the asserting?
It’s an interesting contrast with points of the Go philosophy. You mentioned assertions in the talk. I also saw “Do not abbreviate variable names” in your doc, which Go is well known for. How far do you take that? Do you think Go’s approach has merits in other circumstances, or it’s just subjective?
How is Zig like for implementing your wire format encoding/decoding? Curious as I imagine comptime would be useful there (and since I work on an IDL/wire format at my job, FIDL).
(I work on TigerBeetle)
For 1), Zig’s
defer assert
works really well.For 2), we take it pretty far, but we also spend a lot of time to find actually good, catchy, short names for the core concepts. Naming-wise, “big endian” naming actually feels more unusual to me, and it is freaking great, I immediately switched my personal style to it, so, eg, I now use
item_count
rather thann_items
, because that aligns withitem_whatever
so well.For 3, zigs great, but not due to comptime. Our deserialization is std.mem.sliceAsBytes, which just casts raw bytes to whatever we want to deserialize (after verifying a strong u128 checksum, mind you). The two features of Zig we use here are:
The fist one, pointer alignment, is really cool! Just the other day I was writing a small “cast bytes to thing” code in both Rust and Zig, and I realized that my Rust code is UB only when Zig version didn’t compile because of the alignment error.
That was great! Thanks for sharing.
Is the code for the game also public?
The core simulator is: https://github.com/tigerbeetledb/tigerbeetle/blob/main/src/simulator.zig.
But we’re still putting on final touches to the game before sharing it publicly. You’ll be able to run it in your browser from our site.
Awesome! Looking forward to it.
Great talk. I’ve often had a really strong inkling that we as an industry - and myself personally - don’t do enough up front design.
My question is - what does this upfront design actually look like? Is there a certain end artefact you expect before you start coding? I mean I can sit down and sketch system diagrams or write notes but it all feels a bit ad-hoc, there’s not much process to it.
I don’t work at TigerBeetle, but I have opinions on this. I think it turns out (in order of impact):
Every additional step is going to reduce design errors, and you can always go back and iterate, but you’ll also end up needing to refactor in some cases…
I gave a talk about this, more or less.