The prior post, When Sum Types Inherit, shows that we can (and should) use the magic of inheritance to enrich sum types to offer as powerful a capability as traditional classes. Graph-based, variant node data structures benefit from variant nodes being able to support common fields, methods, and virtual dispatch. That result, however, raises a new question: do we really need two separate language abstractions that offer very similar capabilities?
Inheritance makes it easier than any other mechanism (e.g. generics, macros, composition/delegation) to define a type that reuses the state and some methods of other types. After reading my inheritance posts, I hope you are convinced that simplifying inheritance to a namespace-based mechanism ensures we obtain this convenient reuse capability, while avoiding most of the complexity and coupling dangers of traditional inheritance. However, you might still wonder whether real-world code needs inheritance’s reuse capability.
After removing the interface, inversion of control, and protected access capabilities from traditional inheritance, what do we have left (besides composition)? This is what we have: placing a few extra tokens on a derived class causes all named fields and methods of one or more base classes to be absorbed as if explicitly incorporated. Further, certain inherited methods can be customized (overridden) with their own implementation. The primary selling point for inheritance has always been this sort of code reuse.
Anti-inheritance advocates are likely to enthusiastically support this post. It promotes the most useful feature of traditional inheritance (Interfaces), turning it into a more valuable abstraction that is largely independent of inheritance. It discards two other traditional features of inheritance, Inversion of Control and Protected Access, as both unnecessary and dangerous. Let’s examine each in turn… Interfaces An interface is “an abstract type that contains no data but defines behaviors as method signatures.
I need to decide what sort of inheritance capability Cone will offer. None! I can hear some of you insist. “Inheritance has recently fallen out of favor as a programming design solution,” claims the Rust language book. “Favor object composition over class inheritance,” recommends the Design Patterns book in 1994. “Inheritance is Evil.” insists Nicolò Pignatelli. It is not hard to find cogent, hard-hitting critiques of inheritance, complaining about costs incurred from fragile base classes, excessive coupling, and broken encapsulation.
The execution of a program unfolds over some interval of time. The lifetime of every temporary resource (e.g., variable or object) is the time span between that resource’s “creation” and “destruction”. This lifetime is wholly contained within the typically-longer lifetime of the program. The goal of this post is to explore how versatile lifetime analysis has increasingly become in managing memory efficiently, safely and with better performance. By the end of this post, we will explore exciting new ways to apply lifetime analysis, beyond their current support in Rust.
Note: You can read Russian translation here. I have always wanted the Cone compiler to be fast. Faster build times make it easier and more pleasant for the programmer to iterate and experiment quickly. Compilers built for speed, such as Turbo Pascal, D and Go, win praise and loyalty. Languages that struggle more with this, like C++ or Rust, get regular complaints and requests to speed up build times.
The decades-long golden age of Moore’s Law is fading. Slow, bloated software will find it increasingly difficult to hide behind the skirt of ever-faster computer hardware. Given that businesses and users will not yield on their demand for speedy software, developers will need to get a lot better at architecting for performance. In his excellent article on Performance Culture, Joe Duffy begins: “Performance is one of the key pillars of software engineering, and is something that’s hard to do right, and sometimes even difficult to recognize.
To complete our three-part series on permissions, which began with Race-Safe Strategies, let’s talk about the transitional nature of reference permissions. When are permissions transitional? When we can safely create a copy of a reference which has a different permission than the reference it copied from. There are several ways in which this can happen, which this diagram summarizes (and the following sections explain): The following sections describe the nature of several one-way transitions that flow downward in the diagram.
In my last post, Race-safe Strategies, one footnote stated “safety issues which look suspiciously similar to race conditions can crop up when a language supports the creation of “interior references” to shared, mutable values of certain types”. Let’s explore that now. I will begin by recapitulating Manish Goregaokar’s excellent post “The Problem With Single-Threaded Shared Mutable”. His post clearly explains why the Rust language wishes to steer developers towards RefCell for shared references over use of Cell, its inflexible shared, mutable counterpart.