mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-09-24 15:10:12 +01:00
Destructor syntax (#5017)
Fix destructor syntax ambiguity by switching to `fn destroy` mirroring standard function syntax. This is a purely syntactic change, maintaining destructor semantics. This comes from leads question #4999 --------- Co-authored-by: Chandler Carruth <chandlerc@gmail.com>
This commit is contained in:
co-authored by
Chandler Carruth
parent
d6b6ba17fa
commit
10a87c045a
@@ -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*]() { ... }
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
+17
-10
@@ -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)
|
||||
|
||||
@@ -61,7 +61,7 @@ The following words are interpreted as keywords:
|
||||
- `constraint`
|
||||
- `continue`
|
||||
- `default`
|
||||
- `destructor`
|
||||
- `destroy`
|
||||
- `else`
|
||||
- `export`
|
||||
- `extend`
|
||||
|
||||
@@ -0,0 +1,175 @@
|
||||
# Destructor syntax
|
||||
|
||||
<!--
|
||||
Part of the Carbon Language project, under the Apache License v2.0 with LLVM
|
||||
Exceptions. See /LICENSE for license information.
|
||||
SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
-->
|
||||
|
||||
[Pull request](https://github.com/carbon-language/carbon-lang/pull/5017)
|
||||
|
||||
<!-- toc -->
|
||||
|
||||
## 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)
|
||||
|
||||
<!-- tocstop -->
|
||||
|
||||
## 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.
|
||||
Reference in New Issue
Block a user