Safe Cppfront is an experiment for a transition towards Safe C++ built on top of Cppfront and its cpp2 syntax. This readme explains the design, but doesn't reflect the current state of the fork which has not implemented all of the design yet.
The plan is to follow these simple steps:
- Bikeshed new so-called "viral" keywords for safe and unsafe and perform all necessary restrictions on what can be done in the safe context, severely restricting expressivity.
- Introduce
safe
andunsafe
function coloring - Introduce
unsafe
blocks - Produce diagnostics for unsafe calls
- Produce diagnostics for other unsafe operations and unsafe constructs
- Introduce
- Start working on core language proposals that reintroduce expressivity in the safe context (ex: sean's choice)
- Start working on library proposals that reintroduce expressivity in the safe context (ex: sean's std2::box)
- Repeat steps 2 and 3 as often as necessary over many different iterations of the standard (C++26, C++29, C++32, etc.)
This is basically the same recipy that worked quite well for constexpr
. Step #1 is the MVP to deliver something. It could be delivered extremely fast. It doesn't even require a working borrow checker, because the safe context can simply disallow pointers and references at first (willingly limiting expressivity until we can restore it with new safe constructs at a later time).
This idea was well received by the reddit crowd over here. And also over here.
The interesting thing about starting with a subset is that it's an idea that could be delivered long before we commit to a specific approach to safety that enables references and pointers. One can safely assume that no borrow checker will be standardized in C++26. If it were to happen with C++29, we'd have the opportunity to pull the proposal from the draft for many years to come if a big enough breakthrough in another area justifies it. And some would say C++29 is still very optimistic, which means we'd get until 2032 to pull a first iteration of borrow checking from the draft before we are committed to an approach. But we could already start the transition towards writing safe code using pure value semantics in C++26 at the earliest, or hopefully C++29 at the latest.
Cppfront allows a mix of Cpp2 and standard C++ source code within the same *.cpp2 file. Cpp2 code is safe by default. Standard C++ is unsafe by default.
Cpp2 functions can be made explicitly safe
or unsafe
. To avoid viral annotations, unsafe
blocks provide an escape hatch that allows safe functions to perform unsafe operations such as calling an unsafe function. Unsafe functions can call anything they like, including safe functions.
Because Cppfront does not currently analyze standard C++ code and simply echoes it back, calling an unknown function from within a safe context is ambiguous:
- It could be that the function doesn't exist at all (not yet written, typo, etc.)
- It could be that an include is missing
- It could be that it is an unsafe standard C++ function
Cppfront usually delegates the first two cases to the C++ compiler that will consume the transpiled standard C++ code. But Safe Cppfront must diagnose the third case. For this reason, Safe Cppfront will bundle all three errors in the same diagnostic. There is no meaningful implementation experience to be gained with regards to a Safe C++ transition by doing the work to disambiguate all three cases.
The initial proof of concept should disallow these unsafe operations and constructs within a safe context:
- Calling an unsafe function
- Calling
std::addressof
or builtinoperator&
- Declaring an uninitialized variable
- Declaring a variable or function parameter of type
T&
orT*
- Declaring a variable with
static
orthread-local
storage - Declaring a variable or function parameter of class type
T
whereT
has at least one of these characteristics:- Has a member variable of trivial type without a default value
- Has a member variable of class type
T
without a default value and whereT
does not provide a default constructor - Has a member variable of type
T&
orT*
- Has a member variable with
static
orthread-local
storage - Has a base class of type
B
that has at least one of these characteristics
- Referencing a global variable
This short list is not exhaustive, and is meant to enable exploration of the impacts of safe function coloring on incremental adoption. While disallowing union
, reinterpret_cast
and many other unsafe constructs is desirable, and can be done, there is no reason to believe it'll provide further insights for a proposal.
Code within safe contexts would enable bound checks by default. Unsafe blocks would be required to opt-out of bounds checking. The same goes for every safety that relies on dynamic checks.
Implementing such dynamic checks is currently out of the scope of this project because there is no reason to believe it'll provide further insights for a proposal.
Opting out of safety can be risky business. And when it becomes necessary to do so, safety first mandates applying a strategy of "least privilege". For this reason, unsafe blocks should provide fine-grained controls over which kind of unsafe operation is allowed rather than open the floodgates to everything unsafe. This way, opting out of bounds checking cannot lead to accidentally calling an unsafe function. And vice versa.