From 0db7620b194c34f7ca54edfa1194c0f2ba8d8740 Mon Sep 17 00:00:00 2001 From: jonmeow Date: Tue, 25 Feb 2025 14:48:07 -0800 Subject: [PATCH] Destructor syntax proposal --- docs/design/README.md | 10 +- docs/design/classes.md | 20 +-- docs/design/lexical_conventions/words.md | 2 +- proposals/p5017.md | 166 +++++++++++++++++++++++ 4 files changed, 182 insertions(+), 16 deletions(-) create mode 100644 proposals/p5017.md diff --git a/docs/design/README.md b/docs/design/README.md index 88009d25b6292..26a0e3a7fa288 100644 --- a/docs/design/README.md +++ b/docs/design/README.md @@ -1954,13 +1954,13 @@ names resolvable by the compiler, and don't act like forward declarations. #### Destructors A destructor for a class is custom code executed when the lifetime of a value of -that type ends. They are defined with the `destructor` keyword followed by -either `[self: Self]` or `[addr self: Self*]` (as is done with -[methods](#methods)) and the block of code in the class definition, as in: +that type ends. They are defined with `fn destroy` followed by either +`[self: Self]` or `[addr self: Self*]` (as is done with [methods](#methods)) and +the block of code in the class definition, as in: ```carbon class MyClass { - destructor [self: Self] { ... } + fn destroy[self: Self]() { ... } } ``` @@ -1969,7 +1969,7 @@ or: ```carbon class MyClass { // Can modify `self` in the body. - destructor [addr self: Self*] { ... } + fn destroy[addr self: Self*]() { ... } } ``` diff --git a/docs/design/classes.md b/docs/design/classes.md index 84e67b994abab..f0a30e8aa8434 100644 --- a/docs/design/classes.md +++ b/docs/design/classes.md @@ -1563,13 +1563,13 @@ it can lead to [slicing](https://en.wikipedia.org/wiki/Object_slicing). ### Destructors Every non-abstract type is _destructible_, meaning has a defined destructor -function called when the lifetime of a value of that type ends, such as when a +method called when the lifetime of a value of that type ends, such as when a variable goes out of scope. The destructor for a class may be customized using -the `destructor` keyword: +the `destroy` method: ```carbon class MyClass { - destructor [self: Self] { ... } + fn destroy[self: Self]() { ... } } ``` @@ -1578,12 +1578,12 @@ or: ```carbon class MyClass { // Can modify `self` in the body. - destructor [addr self: Self*] { ... } + fn destroy[addr self: Self*]() { ... } } ``` -If a class has no `destructor` declaration, it gets the default destructor, -which is equivalent to `destructor [self: Self] { }`. +If a class has no `destroy` method, it gets the default destructor, which is +equivalent to `fn destroy[self: Self] { }`. The destructor for a class is run before the destructors of its data members. The data members are destroyed in reverse order of declaration. Derived classes @@ -1601,9 +1601,9 @@ Destructors may be declared in class scope and then defined out-of-line: ```carbon class MyClass { - destructor [addr self: Self*]; + fn destroy[addr self: Self*](); } -destructor MyClass [addr self: Self*] { ... } +fn MyClass.destroy[addr self: Self*]() { ... } ``` It is illegal to delete an instance of a derived class through a pointer to one @@ -1615,12 +1615,12 @@ must be `impl`: ```carbon base class MyBaseClass { - virtual destructor [addr self: Self*] { ... } + virtual fn destroy[addr self: Self*]() { ... } } class MyDerivedClass { extend base: MyBaseClass; - impl destructor [addr self: Self*] { ... } + impl fn destroy[addr self: Self*]() { ... } } ``` diff --git a/docs/design/lexical_conventions/words.md b/docs/design/lexical_conventions/words.md index 43f0aff81ee61..b10ddcaa5c27a 100644 --- a/docs/design/lexical_conventions/words.md +++ b/docs/design/lexical_conventions/words.md @@ -61,7 +61,7 @@ The following words are interpreted as keywords: - `constraint` - `continue` - `default` -- `destructor` +- `destroy` - `else` - `export` - `extend` diff --git a/proposals/p5017.md b/proposals/p5017.md new file mode 100644 index 0000000000000..d4b8229e68160 --- /dev/null +++ b/proposals/p5017.md @@ -0,0 +1,166 @@ +# Destructor syntax + + + +[Pull request](https://github.com/carbon-language/carbon-lang/pull/5017) + + + +## Table of contents + +- [Abstract](#abstract) +- [Problem](#problem) +- [Background](#background) +- [Proposal](#proposal) + - [Not directly callable](#not-directly-callable) +- [Future work](#future-work) + - [Decide whether to desugar destructors to interfaces](#decide-whether-to-desugar-destructors-to-interfaces) + - [Copy and move functions](#copy-and-move-functions) +- [Rationale](#rationale) +- [Alternatives considered](#alternatives-considered) + - [Destructor syntax options](#destructor-syntax-options) + - [Destructor name options](#destructor-name-options) + + + +## 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](https://github.com/carbon-language/carbon-lang/blob/trunk/docs/design/classes.md#destructors) +includes out-of-line definitions such as: + +```carbon +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: + +```carbon +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](https://github.com/carbon-language/carbon-lang/pull/1154) +- Leads question + [#4999: Out-of-line destructor syntax ambiguity](https://github.com/carbon-language/carbon-lang/issues/4999) +- [2025-02-25 Toolchain minutes](https://docs.google.com/document/d/1Iut5f2TQBrtBNIduF4vJYOKfw7MbS8xH_J01_Q4e6Rk/edit?resourcekey=0-mc_vh5UzrzXfU4kO-3tOjA&tab=t.0#heading=h.vootuzze8e8e) + +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](#problem): + +```carbon +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](/proposals/p1154.md#allow-functions-to-act-as-destructors) +for details. + +## 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](/docs/project/goals.md#software-and-language-evolution) + - Eliminates ambiguity in `destructor` syntax, by creating consistency + with `fn` syntax. + - Claiming `destroy` as 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. +- [Code that is easy to read, understand, and write](/docs/project/goals.md#code-that-is-easy-to-read-understand-and-write) + - Consistency with `fn` syntax should improve readability. + - Features that impact data layout are consistently written like member + declarations. + +## 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, The +ambiguity of the out-of-line form (`fn destructor MyClass [...]` is a +significant + +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`](https://en.cppreference.com/w/cpp/memory/allocator/destroy), +or visible +[searching LLVM code](https://github.com/search?q=repository%3Allvm%2Fllvm-project+language%3Ac%2B%2B+symbol%3A%2F%28%3F-i%29%5Edestroy%24%2F&type=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`. + +Note that non-keyword names were considered as part of proposal +[#1154: Destructors](https://github.com/carbon-language/carbon-lang/pull/1154), +and