diff --git a/docs/design/README.md b/docs/design/README.md index 88009d25b629..26a0e3a7fa28 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 84e67b994aba..756910b6db36 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*]() { ... } } ``` @@ -2285,9 +2285,15 @@ the type of `U.x`." - [No unqualified lookup when defining outside a scope](/proposals/p2287.md#no-unqualified-lookup-when-defining-outside-a-scope) - [#2760: Consistent `class` and `interface` syntax](https://github.com/carbon-language/carbon-lang/pull/2760) + - [Use `extends` instead of `extend`](/proposals/p2760.md#use-extends-instead-of-extend) - [List base class in class declaration](/proposals/p2760.md#list-base-class-in-class-declaration) +- [#5017: Destructor syntax](https://github.com/carbon-language/carbon-lang/pull/5017) + + - [Destructor syntax options](/proposals/p5017.md#destructor-syntax-options) + - [Destructor name options](/proposals/p5017.md#destructor-name-options) + ## References - [#257: Initialization of memory and variables](https://github.com/carbon-language/carbon-lang/pull/257) @@ -2300,3 +2306,4 @@ the type of `U.x`." - [#2107: Clarify rules around `Self` and `.Self`](https://github.com/carbon-language/carbon-lang/pull/2107) - [#2287: Allow unqualified name lookup for class members](https://github.com/carbon-language/carbon-lang/pull/2287) - [#2760: Consistent `class` and `interface` syntax](https://github.com/carbon-language/carbon-lang/pull/2760) +- [#5017: Destructor syntax](https://github.com/carbon-language/carbon-lang/pull/5017) diff --git a/docs/design/lexical_conventions/words.md b/docs/design/lexical_conventions/words.md index 43f0aff81ee6..b10ddcaa5c27 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 000000000000..558fe537e5b3 --- /dev/null +++ b/proposals/p5017.md @@ -0,0 +1,175 @@ +# 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) + - [Extend syntax to allow explicit marking of _trivial_ destructors](#extend-syntax-to-allow-explicit-marking-of-trivial-destructors) + - [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 + +### 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](/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. + +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`. + +Non-keyword names were considered as part of proposal +[#1154: Destructors](https://github.com/carbon-language/carbon-lang/pull/1154), +and the trade-off considerations still apply.