A Stable Modular ABI for Rust

I applaud any attempt to address some of the use cases commonly associated with "ABI stability" without affecting code that doesn't actually need "ABI stability". Sadly I am too short in time to delve into implementation details, but I can (and feel the need to) give a feedback that I often give then the topic of "stable ABI" comes up: this is a overloaded term, and it's important to be precise about which of the several meanings we have in mind at any given point, because they differ greatly in what they enable and what they require from the compiler and from the user.

More concretely: there are broadly two clusters of meanings for "stable ABI", the first referring to how language concepts are mapped machine code (e.g. data structure layout, calling conventions) and the latter to the ability to change (upgrade) a software component without rebuilding all the software that interacts with this component. The second is ultimately the responsibility of programmers (e.g. deleting a function from a library breaks both source compatibility and ABI compatibility), but since it's required for many of the benefits ascribed to "stable ABI" and requires compiler support too, anyone pondering this subject should either explicitly acknowledge that aspect (and everything that it enables) as out of scope, or think about what it entails -- which goes far beyond "freezing" data structure layouts, calling conventions, etc.

In the C world, there is a relatively simple relation between these two aspects: everything you put into ("public") headers is part of the ABI, and if you want the ability to e.g. change the layout of a type without breaking ABI compatibility, then you need to make that struct an opaque type in the headers and e.g. expose getter/setter functions for fields whose existence you want to guarantee.

In a richer language, however, it's more difficult. The way Rust compiles generics, for example, necessarily inlines lots of "internal" library code (full of hard-coded field offsets, type sizes, etc.) into consumers of the library. Even in non-generic code, innocent changes such as adding an extra field to a struct often invalidate all code that deals with that struct in any way. In contrast, Swift has similar features but achieves dynamic linking anyway by adopting different compilation strategies and a host of neat tricks that Rust lacks. There is a huge design space here: what subset of Rust can we make usable for libraries with stable ABIs? what sorts of changes can be made non-ABI-breaking? What changes can we make to the compiler to allow a bigger subset of Rust and more kinds of changes? How can we communicate these rules to programmers who author libraries with stable ABIs, and how can we make it easy for them to adhere to the rules? What escape hatches should we have for trading off resilience and performance (like Swift's @frozen structs)? etc. etc.

These kinds of questions are crucial if you want to reap the benefits Swift and C are reaping from their "stable ABI", but are only apparent if you go beyond viewing ABI stability as just about the choices the compiler makes while compiling code. You can focus exclusively on that aspect if you want, but in that case you still need to think very carefully to avoid over-stating what use cases your proposal will actually enable.

47 Likes