Let's build a browser engine, Part 6: Block layout
limpet.net
limpet.net
match (width == auto, margin_left == auto, margin_right == auto) {
(false, false, false) => { ... }
(false, false, true) => { ... }
(false, true, false) => { ... }
(true, _, _) => { ... }
(false, true, true) => { ... }
}
C-style switch isn't powerful enough to handle this due to the _ wildcards, so in a language without pattern matching you'd have little choice but to fall back to a chains of ifs and Boolean operators, which would make the code hard to read (and CSS layout code is notoriously time-consuming to debug, so anything that makes code easier to read is extremely valuable in practice). The compiler checks to make sure all cases are handled too.This sort of thing has made Servo's layout much easier to understand.
#define WIDTH 4
#define MARGIN_LEFT 2
#define MARGIN_RIGHT 1
...
unsigned int flags =
(width ? WIDTH : 0) |
(margin_left ? MARGIN_LEFT : 0) |
(margin_right ? MARGIN_RIGHT : 0);
switch (flags) {
case 0: //...
case MARGIN_RIGHT: //...
case MARGIN_LEFT: //...
case WIDTH: // fallthrough
case WIDTH | MARGIN_RIGHT: // fallthrough
case WIDTH | MARGIN_LEFT: // fallthrough
case WIDTH | MARGIN_RIGHT | MARGIN_LEFT:
//...
case MARGIN_LEFT | MARGIN_RIGHT: //...
}
Although you probably already knew it, and I totally admit it's much uglier and "workaround-ish". It's just that after reading "isn't powerful enough [...]" I felt a compulsory urge to show that It Can Be Done, and couldn't resist, sorry :) (false, false, false) => { ... } // 0
(false, false, true ) => { ... } // 1
(false, true , false) => { ... } // 2
(false, true , true ) => { ... } // 3
(true , _ , _ ) => { ... } // 4-7 (could be the default case)
Here's a possible small improvement that could, depending on the compiler, get rid of a few branches: #define WIDTH_BIT 2
#define WIDTH (1<<WIDTH_BIT)
#define MARGIN_LEFT_BIT 1
#define MARGIN_LEFT (1<<MARGIN_LEFT_BIT)
#define MARGIN_RIGHT_BIT 0
#define MARGIN_RIGHT (1<<MARGIN_RIGHT_BIT)
...
unsigned int flags = !!width << WIDTH_BIT
| !!margin_left << MARGIN_LEFT_BIT
| !!margin_right << MARGIN_RIGHT_BIT;
...
I'm not too familiar with Rust and Google isn't being helpful, so does anyone know how the Rust compiler implements pattern matching? Does it do something like the above or does it resort to if/else conversion?C++11 (or a near enough draft): 5.3.1 p9: The operand of the logical negation operator ! is contextually converted to bool (Clause 4); its value is true if the converted operand is false and false otherwise. The type of the result is bool.
and 4.7 p4 on integral conversion: If the source type is bool, the value false is converted to zero and the value true is converted to one.
Both seem to suggest that when you apply !, you end up with something that is 0 or 1 when used as a number, and adding more ! won't get you out of that.
No, I didn't. Clever! I love Hacker News sometimes :)
So what about the following?:
match (width, margin_left, margin_right) {
(auto, _, _) => { ... }
(_, auto, auto) => { ... }
(_, auto, _) => { ... }
(_, _, auto) => { ... }
(_, _, _) => { ... }
}
Now that I write this, I can understand this style is trickier as the order here is critical.
The "debate" between `match` and `if` reminds me of the same one that exists in Erlang, where I saw people more often use `case of` with booleans instead of `if`.Moreover, is there any performance issue with matching auto for every tuple?
static width_is_auto: bool = true;
should work. if (width == auto) {
...
}
else if (margin_left == auto && margin_right == auto) {
...
}
else if (margin_left == auto) {
...
}
else if (margin_right == auto) {
...
}
else {
...
}
Perhaps I'm not very used to the pattern matching notation.Doesn't fallthrough enable you to implement wildcards? You still have to exhaustively list every option, but that's not hard here.
in a language like javascript, this is could one use for objects;
o = {}
o[true, true, true] = function(){something} ... and so on
then do
o[[width, margin_left, margin_right]]()
on second thought:
the biggest strength i see in rust is the wild card, making the switch statement much more useful than in other languages
Really nice job!