We've talked about adding the title to the filename several times over the years and it seems really valuable. This requires us to compute a "slug" for the title spelling that can be part of the filename. Beyond that, we crossed 7000 recently, and so it seems likely that we will need to add digits sooner rather than later here, so this goes ahead and moves us to 6 digits so we don't have to adjust again for a reasonable length of time. To implement this and ensure we can sustain it going forward this adds a tool to our pre-commit that validates (and corrects if needed) the filename. In order to update everything and keep links working, there are a _lot_ of changes, but the most interesting for direct review are in `proposals/scripts`. Assisted-by: Antigravity with Gemini --------- Co-authored-by: Richard Smith <richard@metafoo.co.uk>
6.8 KiB
Destructor syntax
Table of contents
Abstract
Fix destructor syntax ambiguity by switching to fn destroy mirroring standard
function syntax. This is a purely syntactic change, maintaining destructor
semantics.
Problem
The accepted destructor syntax includes out-of-line definitions such as:
class MyClass {
destructor [addr self: Self*];
}
destructor MyClass [addr self: Self*] { ... }
The implicit parameter here could be interpreted as either an implicit parameter
for MyClass or an implicit parameter for the destructor. How should
ambiguities like this be resolved?
For comparison, note a generic might look like:
class GenericClass[T:! type](N:! T) { ... }
destructor GenericClass[T:! type](N:! T) [addr self: Self*] { ... }
The toolchain is able to parse this in constant time, but only because the lexer
will pair brackets, so we can do lookahead at the bracket in GenericClass[ for
the closing ], and look past that for the ( versus {. However, this is
arbitrary lookahead and may be significantly less efficient in other parsers
that people might want to use with Carbon, such as tree-sitter.
Background
- Proposal #1154: Destructors
- Leads question #4999: Out-of-line destructor syntax ambiguity
- 2025-02-25 Toolchain minutes
In particular, we are discussing destruction as possibly similar to copy and move syntax, and trying to create a consistency between the functions.
Proposal
Destructor syntax will use standard function syntax, with destroy as a keyword
for the function name.
For example, in contrast with problem examples:
class MyClass {
fn destroy[addr self: Self*]();
}
fn MyClass.destroy[addr self: Self*]() { ... }
class GenericClass[T:! type](N:! T) { ... }
fn GenericClass[T:! type](N:! T).destroy[addr self: Self*]() { ... }
It is invalid to add other implicit or explicit parameters to the destroy
function.
Not directly callable
Although the syntax of fn destroy looks similar to a regular function, the
functions are not designed to be directly callable. This does not add support
for my_var.destroy(). See Proposal #1154, alternative
Allow functions to act as destructors
for details.
Future work
Extend syntax to allow explicit marking of trivial destructors
Discussion has indicated potential utility in syntax to make the expectation of a trivial destructor explicit. This would allow a declarative way of ensuring no member accidentally caused a type to have non-trivial destruction.
Still, this requires a further extension of syntax that isn't proposed at this time. Both determining syntax for such a feature and motivating it fully are left as future work.
Decide whether to desugar destructors to interfaces
Under this proposal, fn destroy remains a special function. We may want to
make it desugar to an interface implementation, but even if we do so, the terse
destructor syntax seems likely to remain. There are concerns about the
ergonomics of requiring an impl in order to add a destructor to a type, and
decisions would need to be made for how virtual destructors should be handled.
Copy and move functions
This proposal is set up for consistency with a possible fn copy and fn move,
but those will be evaluated as part of copy and move semantics.
Rationale
- Software and language evolution
- Eliminates ambiguity in
destructorsyntax, by creating consistency withfnsyntax. - Claiming
destroyas a keyword is considered to be a good balance. - Syntax choices, particularly with the keyword as a function name, should not create a barrier for desugaring to an interface approach for destructions.
- Eliminates ambiguity in
- Code that is easy to read, understand, and write
- Consistency with
fnsyntax should improve readability. - Features that impact data layout are consistently written like member declarations.
- Consistency with
Alternatives considered
Destructor syntax options
The ambiguity between destructor MyClass [...] out-of-line destructor syntax
and implicit parameters for generics is a sufficient barrier to change syntax.
We do not want parsing Carbon to require arbitrary lookahead.
fn destroy was preferred because it builds on existing fn syntax.
Although adding a ., as in destructor MyClass.[...], was brought up, it
didn't present interesting advantages over fn destroy.
Destructor name options
We expect more name conflicts with C++ code using the destroy keyword than
with the destructor keyword, for example with
std::allocator::destroy,
or visible
searching LLVM code.
Still, the phrasing of destroy, particularly if we have copy and move to
match, is preferred. Raw identifier syntax (r#destroy) is expected to be
sufficient for name conflicts.
fn delete was mentioned as an option reusing current keywords, but declined
due to the "heap allocated" implication of delete.
Non-keyword names were considered as part of proposal #1154: Destructors, and the trade-off considerations still apply.