Patrick Walton, a Mozilla engineer on Servo and Rust, writes about Rust language design, parallel layout, and GPU font rendering.
HN links it for early Rust history such as typestate and garbage collection removal, and for his Pathfinder GPU rasterizer work.
Pretending that modern type systems provide anything near an adequate control of effects is misleading at best. Controlled side effects are an excellent goal
I completely agree. But, as I suspect you’d agree, it’s not an easy problem even to specify the behaviour we would ideally like to see here.
For example, one of the important things about side effects is not only their existence or lack thereof, but their ordering (e.g. you must write to a socket only after you open it and only before you close it).
When I’m designing software, I tend to think of effects at three distinct levels: can effects happen at all, what relative timing (i.e., ordering) do they need to have, and what absolute timing do they need to have?
The most basic question is whether certain effects or effects related to a certain resource happen at all. This includes distinguishing pure functions from code with side-effects, but it could also be about, say, having a function that appears pure from the outside but is implemented using mutable state internally. If that state is stored in internal memory that is only acquired and released within that function and no reference to it is accessible outside the function, then the scope of the effects is limited and the usual advantages of pure functions still apply viewed from outside. If the state is stored externally, say in a file or database, then potentially it could interact with other parts of the system because they could also acquire a reference to it…
The Rust compiler doesn't produce warnings out of zealotry, but rather as a consequence of pre-1.0 historical decisions. Note that Rust doesn't use any syntax in pattern matching contexts to distinguish between bindings and enum variants. In pre-1.0 versions of Rust, this created footguns where an author might think they were matching on an enum, but the compiler was actually parsing it as a catch-all binding that would cause any following match arms to never be executed. This was exacerbated by the prevailing naming conventions of the time (which you can see in this 2012 blog post: https://pcwalton.github.io/_posts/2012-06-03-maximally-minimal-classes-for-rust.html (note the lower-cased enum variants)). So at some point the naming conventions were changed in an attempt to prevent this footgun, and the lint was implemented to nudge people over to the new conventions. However, as time went on the footgun was otherwise fixed by instead causing the compiler to prioritize parsing enum variants rather than bindings, in conjunction with other errors and warnings about non-exhaustive patterns and dead code (which are all desirable in their own right). At this point it's mostly just vestigial, and I highly doubt that anybody really cares about it beyond "our users are accustomed to this warning-by-default, so they might be surprised if we stopped doing this".
> Rust has data race prevention not because it’s a good idea but because the whole language falls apart without it.
In terms of the design goals and evolution of the Rust language, this is exactly backwards. Rust was originally conceived as a garbage-collected, green-threaded language designed for concurrent programming -- think something similar to Go but with a stronger type system and no data races. The ownership model was created, first as foremost, not for memory safety but to prevent data races; and, more broadly, to help programmers reason about the correctness of their code.
Midway through development, as people started using the language & getting a feel for it, the language designers realized that the ownership model was powerful enough to express not just data race freedom but full memory safety without needing a GC (some writing from this stage in Rust's development: https://smallcultfollowing.com/babysteps/blog/2013/06/11/on-the-connection-between-memory-management-and-data-race-freedom/, https://pcwalton.github.io/_posts/2013-06-02-removing-garbage-collection-from-the-rust-language.html). So they removed the GC, and that decision positioned Rust where it is today: a nicer, memory-safe "C/C++ competitor", popular for systems code where the performance overhead or runtime complexity of a garbage collector is considered unacceptable.
> which I believe WebRender targets
OpenGL 3.0 and OpenGL ES 3.0: https://pcwalton.github.io/slides/201808-webrender/
They both support texture arrays.
> texture layer dimensions have to be specified up-front
Indeed, but it's possible to create another larger one, and copy layers between arrays within GPU.
It's the same idea as std::vector on CPUs, most heaps can't resize memory either, they instead allocate new blocks and copy.
There're two wins with texture arrays.
1. Removes area limitation. 2D textures are often limited to rather small size, like 2k^2 pixels. Same limitation applies to arrays, but with multiple layers their total area is only limited by VRAM size. That 2D library I've linked above renders arbitrarily complex 2D graphics with only 2 draw calls, regardless on how many sprites or font glyphs are used in the scene. Two because front-to-back opaque followed by back-to-front translucent.
2. These packing algorithms often scale worse than linearly with available area or count of sprites on the atlas. Using multiple layers of the array takes care of that, new sprites are only packed into the last layer, and layer size is never too large.
https://pcwalton.github.io/_posts/2013-06-02-removing-garbage-collection-from-the-rust-language.html
This came 2 years after C++11 was finalized, which introduced RAII memory management, threads, range loops and much more, and is the basis for modern C++.
Of course, there are pros and cons to keeping backwards compatibility with old code. Personally I think it's an amazing technical achievement that the same code written in 1987 still works. Particularly for numerical work there are a lot of libraries that would be a massive pain to rewrite correctly, just getting them off of Fortran was an achievement.
In terms of new and old standards coexisting in the same code, of course it isn't preferable, but it is possible which can enable many use cases, eg. in place upgrades. Forcing rewite-the-world is what caused the Python 2 --> 3 migration to be such a mess.
And if you really don't want old features, there are linter rules and compiler warnings which do a great job for this.
The greatest mistake IMO is the way float state leaks out of blocks, as this is both extremely unintuitive and undesirable for performance reasons.[1] Floats should've been restricted to inline formatting contexts, with all in-flow blocks behaving as if they had `clear: both' set.
I also don't understand why they never specced the (much simpler) `text-align: -moz-left/-moz-right/-moz-center' which already had precedent in HTML with `<div align=left/right/center>'. It's the saddest part of the "center a div" saga, all the W3C had to do to fix it is to assign a standard keyword to a feature that everybody already implemented, but to this day it still hasn't happened.[2]
[1]: https://pcwalton.github.io/_posts/2014-02-25-revamped-parallel-layout-in-servo.html
[2]: After many long decades, they did finally specify block-level `justify-items'. Two problems: a) it's backwards-incompatible with text-align, b) it still doesn't work in Gecko.