Browse Source

Destructors (#1154)

Add a way for classes to add custom destructor code:
```
class Class1 {
  // forward declaration
  destructor [me: Self] { ... }
}
// out-of-line definition
destructor Class1 [me: Self] { ... }

class Class2 {
  // can modify `me` as part of destruction
  destructor [addr me: Self*] { ... }
}
base class MyBaseClass {
  virtual destructor [addr me: Self*] { ... }
}
class MyDerivedClass extend MyBaseClass {
  impl destructor [addr me: Self*] { ... }
}
```
and constraints `Concrete`, `Deletable`, `Destructible`, and `TrivialDestructor`.
josh11b 4 năm trước cách đây
mục cha
commit
9b028bb743
3 tập tin đã thay đổi với 537 bổ sung56 xóa
  1. 182 52
      docs/design/classes.md
  2. 35 4
      docs/design/generics/details.md
  3. 320 0
      proposals/p1154.md

+ 182 - 52
docs/design/classes.md

@@ -50,6 +50,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
             -   [Partial facet](#partial-facet)
             -   [Usage](#usage)
         -   [Assignment with inheritance](#assignment-with-inheritance)
+    -   [Destructors](#destructors)
     -   [Access control](#access-control)
         -   [Private access](#private-access)
         -   [Protected access](#protected-access)
@@ -64,7 +65,6 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
         -   [Destructuring in pattern matching](#destructuring-in-pattern-matching)
         -   [Discussion](#discussion)
     -   [Inheritance](#inheritance-1)
-        -   [Destructors](#destructors)
         -   [C++ abstract base classes interoperating with object-safe interfaces](#c-abstract-base-classes-interoperating-with-object-safe-interfaces)
         -   [Overloaded methods](#overloaded-methods)
         -   [Interop with C++ inheritance](#interop-with-c-inheritance)
@@ -74,6 +74,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
     -   [No `static` variables](#no-static-variables)
     -   [Computed properties](#computed-properties)
     -   [Interfaces implemented for data classes](#interfaces-implemented-for-data-classes)
+-   [References](#references)
 
 <!-- tocstop -->
 
@@ -1267,7 +1268,7 @@ The partial facet for a base class type like `MyBaseType` is written
     `MyDerivedClass` extends `MyBaseClass`. The type `partial MyBaseClass`
     specifically means "exactly this and no more." This means we don't need to
     look at the hidden vptr slot, and we can instantiate it even if it doesn't
-    have a virtual destructor.
+    have a virtual [destructor](#destructors).
 -   The keyword `partial` may only be applied to a base class. For final
     classes, there is no need for a second type.
 
@@ -1408,6 +1409,176 @@ implement it for final types. However, following the
 we allow users to also implement assignment on extensible classes, even though
 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
+variable goes out of scope. The destructor for a class may be customized using
+the `destructor` keyword:
+
+```carbon
+class MyClass {
+  destructor [me: Self] { ... }
+}
+```
+
+or:
+
+```carbon
+class MyClass {
+  // Can modify `me` in the body.
+  destructor [addr me: Self*] { ... }
+}
+```
+
+If a class has no `destructor` declaration, it gets the default destructor,
+which is equivalent to `destructor [me: 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
+are destroyed before their base classes, so the order of operations is:
+
+-   derived class' destructor runs,
+-   the data members of the derived class are destroyed, in reverse order of
+    declaration,
+-   the immediate base class' destructor runs,
+-   the data members of the immediate base class are destroyed, in reverse order
+    of declaration,
+-   and so on.
+
+Destructors may be declared in class scope and then defined out-of-line:
+
+```carbon
+class MyClass {
+  destructor [addr me: Self*];
+}
+destructor MyClass [addr me: Self*] { ... }
+```
+
+It is illegal to delete an instance of a derived class through a pointer to one
+of its base classes unless it has a
+[virtual destructor](https://en.wikipedia.org/wiki/Virtual_function#Virtual_destructors).
+An abstract or base class' destructor may be declared virtual using the
+`virtual` introducer, in which case any derived class destructor declaration
+must be `impl`:
+
+```carbon
+base class MyBaseClass {
+  virtual destructor [addr me: Self*] { ... }
+}
+
+class MyDerivedClass extends MyBaseClass {
+  impl destructor [addr me: Self*] { ... }
+}
+```
+
+The properties of a type, whether type is abstract, base, or final, and whether
+the destructor is virtual or non-virtual, determines which
+[type-of-types](/docs/design/generics/terminology.md#type-of-type) it satisfies.
+
+-   Non-abstract classes are `Concrete`. This means you can create local and
+    member variables of this type. `Concrete` types have destructors that are
+    called when the local variable goes out of scope or the containing object of
+    the member variable is destroyed.
+-   Final classes and classes with a virtual destructor are `Deletable`. These
+    may be safely deleted through a pointer.
+-   Classes that are `Concrete`, `Deletable`, or both are `Destructible`. These
+    are types that may be deleted through a pointer, but it might not be safe.
+    The concerning situation is when you have a pointer to a base class without
+    a virtual destructor. It is unsafe to delete that pointer when it is
+    actually pointing to a derived class.
+
+**Note:** The names `Deletable` and `Destructible` are
+[**placeholders**](/proposals/p1154.md#type-of-type-naming) since they do not
+conform to the decision on
+[question-for-leads issue #1058: "How should interfaces for core functionality be named?"](https://github.com/carbon-language/carbon-lang/issues/1058).
+
+| Class    | Destructor  | `Concrete` | `Deletable` | `Destructible` |
+| -------- | ----------- | ---------- | ----------- | -------------- |
+| abstract | non-virtual | no         | no          | no             |
+| abstract | virtual     | no         | yes         | yes            |
+| base     | non-virtual | yes        | no          | yes            |
+| base     | virtual     | yes        | yes         | yes            |
+| final    | any         | yes        | yes         | yes            |
+
+The compiler automatically determines which of these
+[type-of-types](/docs/design/generics/terminology.md#type-of-type) a given type
+satisfies. It is illegal to directly implement `Concrete`, `Deletable`, or
+`Destructible` directly. For more about these constraints, see
+["destructor constraints" in the detailed generics design](/docs/design/generics/details.md#destructor-constraints).
+
+A pointer to `Deletable` types may be passed to the `Delete` method of the
+`Allocator` [interface](/docs/design/generics/terminology.md#interface). To
+deallocate a pointer to a base class without a virtual destructor, which may
+only be done when it is not actually pointing to a value with a derived type,
+call the `UnsafeDelete` method instead. Note that you may not call
+`UnsafeDelete` on abstract types without virtual destructors, it requires
+`Destructible`.
+
+```
+interface Allocator {
+  // ...
+  fn Delete[T:! Deletable, addr me: Self*](p: T*);
+  fn UnsafeDelete[T:! Destructible, addr me: Self*](p: T*);
+}
+```
+
+To pass a pointer to a base class without a virtual destructor to a generic
+function expecting a `Deletable` type, use the `UnsafeAllowDelete`
+[type adapter](/docs/design/generics/details.md#adapting-types).
+
+```
+adapter UnsafeAllowDelete(T:! Concrete) extends T {
+  impl as Deletable {}
+}
+
+// Example usage:
+fn RequiresDeletable[T:! Deletable](p: T*);
+var x: MyExtensible;
+RequiresDeletable(&x as UnsafeAllowDelete(MyExtensible)*);
+```
+
+If a virtual method is transitively called from inside a destructor, the
+implementation from the current class is used, not any overrides from derived
+classes. It will abort the execution of the program if that method is abstract
+and not implemented in the current class.
+
+**Future work:** Allow or require destructors to be declared as taking
+`partial Self` in order to prove no use of virtual methods.
+
+Types satisfy the
+[`TrivialDestructor`](/docs/design/generics/details.md#destructor-constraints)
+type-of-type if:
+
+-   the class declaration does not define a destructor or the class defines the
+    destructor with an empty body `{ }`,
+-   all data members implement `TrivialDestructor`, and
+-   all base classes implement `TrivialDestructor`.
+
+For example, a [struct type](#struct-types) implements `TrivialDestructor` if
+all its members do.
+
+`TrivialDestructor` implies that their destructor does nothing, which may be
+used to generate optimized specializations.
+
+There is no provision for handling failure in a destructor. All operations that
+could potentially fail must be performed before the destructor is called.
+Unhandled failure during a destructor call will abort the program.
+
+**Future work:** Allow or require destructors to be declared as taking
+`[var me: Self]`.
+
+**Alternatives considered:**
+
+-   [Types implement destructor interface](/proposals/p1154.md#types-implement-destructor-interface)
+-   [Prevent virtual function calls in destructors](/proposals/p1154.md#prevent-virtual-function-calls-in-destructors)
+-   [Allow functions to act as destructors](/proposals/p1154.md#allow-functions-to-act-as-destructors)
+-   [Allow private destructors](/proposals/p1154.md#allow-private-destructors)
+-   [Allow multiple conditional destructors](/proposals/p1154.md#allow-multiple-conditional-destructors)
+-   [Don't distinguish safe and unsafe delete operations](/proposals/p1154.md#dont-distinguish-safe-and-unsafe-delete-operations)
+-   [Don't allow unsafe delete](/proposals/p1154.md#dont-allow-unsafe-delete)
+-   [Allow final destructors](/proposals/p1154.md#allow-final-destructors)
+
 ### Access control
 
 By default, all members of a class are fully publicly accessible. Access can be
@@ -1649,56 +1820,6 @@ Some discussion on this topic has occurred in:
 
 ### Inheritance
 
-#### Destructors
-
-We need a syntax for declaring destructors. Provisionally we are considering
-allowing a couple of variations:
-
-```carbon
-class MyClass {
-  destructor [me: Self] { ... }
-}
-```
-
-or:
-
-```carbon
-class MyClass {
-  destructor [addr me: Self*] { ... }
-}
-```
-
-Destructors may be declared `virtual`, or `impl` in the case that a base class
-declares it `virtual`.
-
-```carbon
-base class MyBaseClass {
-  virtual destructor [addr me: Self*] { ... }
-}
-
-class MyDerivedClass {
-  impl destructor [addr me: Self*] { ... }
-}
-```
-
-Destructors may be declared out-of-line:
-
-```carbon
-class MyClass {
-  destructor [addr me: Self*];
-}
-destructor MyClass [addr me: Self*] { ... }
-```
-
-It isn't safe to delete an instance of a base class through a pointer unless it
-has a
-[virtual destructor](https://en.wikipedia.org/wiki/Virtual_function#Virtual_destructors).
-
-An alternative is the Rust approach is there is some interface that is only
-implemented for types with non-trivial destructors. It allows metaprogramming to
-distinguish whether the type needs clean up. Rust allows you to declare `Self`
-as being moved into the destructor, which nicely captures the semantics.
-
 #### C++ abstract base classes interoperating with object-safe interfaces
 
 We want four things so that Carbon's object-safe interfaces may interoperate
@@ -1927,3 +2048,12 @@ comparable to `{.x = 3.14, .y = 2}`. The trick is how to declare the criteria
 that "`T` is comparable to `U` if they have the same field names in the same
 order, and for every field `x`, the type of `T.x` implements `ComparableTo` for
 the type of `U.x`."
+
+## References
+
+-   [#257: Initialization of memory and variables](https://github.com/carbon-language/carbon-lang/pull/257)
+-   [#561: Basic classes: use cases, struct literals, struct types, and future wor](https://github.com/carbon-language/carbon-lang/pull/561)
+-   [#722: Nominal classes and methods](https://github.com/carbon-language/carbon-lang/pull/722)
+-   [#777: Inheritance](https://github.com/carbon-language/carbon-lang/pull/777)
+-   [#981: Implicit conversions for aggregates](https://github.com/carbon-language/carbon-lang/pull/981)
+-   [#1154: Destructors](https://github.com/carbon-language/carbon-lang/pull/1154)

+ 35 - 4
docs/design/generics/details.md

@@ -73,6 +73,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
     -   [Sized types and type-of-types](#sized-types-and-type-of-types)
         -   [Implementation model](#implementation-model-2)
     -   [`TypeId`](#typeid)
+    -   [Destructor constraints](#destructor-constraints)
 -   [Generic `let`](#generic-let)
 -   [Parameterized impls](#parameterized-impls)
     -   [Impl for a parameterized type](#impl-for-a-parameterized-type)
@@ -3398,7 +3399,7 @@ Knowing a type is sized is a precondition to declaring variables of that type,
 taking values of that type as parameters, returning values of that type, and
 defining arrays of that type. Users will not typically need to express the
 `Sized` constraint explicitly, though, since it will usually be a dependency of
-some other constraint the type will need such as `Movable`.
+some other constraint the type will need such as `Movable` or `Concrete`.
 
 **Note:** The compiler will determine which types are "sized", this is not
 something types will implement explicitly like ordinary interfaces.
@@ -3478,14 +3479,44 @@ fn SortByAddress[T:! Type](v: Vector(T*)*) { ... }
 In particular, the compiler should in general avoid monomorphizing to generate
 multiple instantiations of the function in this case.
 
-**Note:** To achieve this goal, the user will not even be allowed to destroy a
-value of type `T` in this case.
-
 **Open question:** Should `TypeId` be
 [implemented externally](terminology.md#external-impl) for types to avoid name
 pollution (`.TypeName`, `.TypeHash`, etc.) unless the function specifically
 requests those capabilities?
 
+### Destructor constraints
+
+There are four type-of-types related to
+[the destructors of types](/docs/design/classes.md#destructors):
+
+-   `Concrete` types may be local or member variables.
+-   `Deletable` types may be safely deallocated by pointer using the `Delete`
+    method on the `Allocator` used to allocate it.
+-   `Destructible` types have a destructor and may be deallocated by pointer
+    using the `UnsafeDelete` method on the correct `Allocator`, but it may be
+    unsafe. The concerning case is deleting a pointer to a derived class through
+    a pointer to its base class without a virtual destructor.
+-   `TrivialDestructor` types have empty destructors. This type-of-type may be
+    used with [specialization](#lookup-resolution-and-specialization) to unlock
+    specific optimizations.
+
+**Note:** The names `Deletable` and `Destructible` are
+[**placeholders**](/proposals/p1154.md#type-of-type-naming) since they do not
+conform to the decision on
+[question-for-leads issue #1058: "How should interfaces for core functionality be named?"](https://github.com/carbon-language/carbon-lang/issues/1058).
+
+The type-of-types `Concrete`, `Deletable`, and `TrivialDestructor` all extend
+`Destructible`. Combinations of them may be formed using
+[the `&` operator](#combining-interfaces-by-anding-type-of-types). For example,
+a generic function that both instantiates and deletes values of a type `T` would
+require `T` implement `Concrete & Deletable`.
+
+Types are forbidden from explicitly implementing these type-of-types directly.
+Instead they use
+[`destructor` declarations in their class definition](/docs/design/classes.md#destructors)
+and the compiler uses them to determine which of these type-of-types are
+implemented.
+
 ## Generic `let`
 
 A `let` statement inside a function body may be used to get the change in type

+ 320 - 0
proposals/p1154.md

@@ -0,0 +1,320 @@
+# Destructors
+
+<!--
+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/1154)
+
+<!-- toc -->
+
+## Table of contents
+
+-   [Problem](#problem)
+-   [Background](#background)
+-   [Proposal](#proposal)
+-   [Rationale based on Carbon's goals](#rationale-based-on-carbons-goals)
+-   [Alternatives considered](#alternatives-considered)
+    -   [Types implement destructor interface](#types-implement-destructor-interface)
+    -   [Prevent virtual function calls in destructors](#prevent-virtual-function-calls-in-destructors)
+    -   [Allow functions to act as destructors](#allow-functions-to-act-as-destructors)
+    -   [Allow private destructors](#allow-private-destructors)
+    -   [Allow multiple conditional destructors](#allow-multiple-conditional-destructors)
+        -   [Allow direct implementation of `TrivialDestructor`](#allow-direct-implementation-of-trivialdestructor)
+    -   [Type-of-type naming](#type-of-type-naming)
+    -   [Other approaches to extensible classes without vtables](#other-approaches-to-extensible-classes-without-vtables)
+        -   [Don't distinguish safe and unsafe delete operations](#dont-distinguish-safe-and-unsafe-delete-operations)
+        -   [Don't allow unsafe delete](#dont-allow-unsafe-delete)
+        -   [Allow final destructors](#allow-final-destructors)
+
+<!-- tocstop -->
+
+## Problem
+
+C++ code supports defining custom
+[destructors](<https://en.wikipedia.org/wiki/Destructor_(computer_programming)>)
+for user-defined types, and C++ developers will expect to use that tool. Carbon
+should support destructors, but should make some changes to protect against
+deleting a value with a derived type through a pointer to the base class when
+the base class destructor is not virtual. We also need some mechanism to allow
+generic code to identify types that are safe to destroy, or have trivial
+destructors and don't need to be explicitly destroyed.
+
+## Background
+
+Destructors may only be customized for nominal classes, which were introduced in
+proposal [#722](https://github.com/carbon-language/carbon-lang/pull/722).
+Destructors interact with inheritance, which was introduced in proposal
+[#777](https://github.com/carbon-language/carbon-lang/pull/777).
+
+Destructors were discussed in open discussion on these dates:
+
+-   [2021-07-12](https://docs.google.com/document/d/1QCdKQ33rki-kCDrxi8UHy3a36dtW0WdMqpUzluGSrz4/edit?resourcekey=0-bZmNUiueOiH_sysJNqnT9A#heading=h.40jlsrcgp8mr)
+-   [2021-08-30](https://docs.google.com/document/d/105GsfmxOwcZ_iHkCXFnALB7e-_R3IgMpGKfeT84h1mc/edit?resourcekey=0-h3uVHObsJwChVg1MdaWfKQ#heading=h.4dobu6v1cdam)
+-   [2021-10-04](https://docs.google.com/document/d/105GsfmxOwcZ_iHkCXFnALB7e-_R3IgMpGKfeT84h1mc/edit?resourcekey=0-h3uVHObsJwChVg1MdaWfKQ#heading=h.a0rffns9r10n)
+    discussed not all types being destructible, and that some types are
+    `TriviallyDestructible`
+-   [2021-10-18](https://docs.google.com/document/d/105GsfmxOwcZ_iHkCXFnALB7e-_R3IgMpGKfeT84h1mc/edit?resourcekey=0-h3uVHObsJwChVg1MdaWfKQ#heading=h.uz59mgk5ezch)
+-   [2022-03-24](https://docs.google.com/document/d/1cRrhRrmaUf2hVi2lFcHsYo2j0jI6t9RGZoYjWhRxp14/edit?resourcekey=0-xWHBEZ8zIqnJiB4yfBSLfA#heading=h.cy8m79pgct1v)
+
+As part of
+[proposal 777: Inheritance](https://github.com/carbon-language/carbon-lang/pull/777),
+we decided to support extensible classes, that is non-abstract base classes,
+[including with non-virtual destructors](p0777.md#no-extensible-objects-with-non-virtual-destructors).
+["Extensible classes" Google doc](https://docs.google.com/document/d/1gbQJN_IMJBnquOUUd2orbHLlAIqZ4pL0Vt7h34DkQjg/edit?resourcekey=0-0lkEvh0umUU206ASFlWc7A#)
+from that time considered options for making deleting extensible classes safer.
+
+## Proposal
+
+This proposal adds two sections to the design:
+
+-   ["Destructors"](/docs/design/classes.md#destructors) to the
+    [classes design](/docs/design/classes.md), and
+-   ["Destructor constraint"](/docs/design/generics/details.md#destructor-constraints)
+    to the [detailed generics design](/docs/design/generics/details.md).
+
+## Rationale based on Carbon's goals
+
+Destructors advance these goals:
+
+-   [Practical safety and testing mechanisms](/docs/project/goals.md#practical-safety-and-testing-mechanisms),
+    since destructors are about making sure clean-up actions are performed by
+    doing them automatically, which is helpful for avoiding safety-related bugs.
+-   [Interoperability with and migration from existing C++ code](/docs/project/goals.md#interoperability-with-and-migration-from-existing-c-code),
+    since destructors are relied upon in C++.
+
+## Alternatives considered
+
+### Types implement destructor interface
+
+We considered letting developers implement a destructor interface, as is done in
+[Rust](https://doc.rust-lang.org/std/ops/trait.Drop.html). This would be more
+consistent with the other customization points for classes, but had some
+downsides that we ultimately decided were too large:
+
+-   Destructors are relatively common, and implementing an interface involves
+    more boilerplate than writing a destructor in C++, so we wanted a concise
+    syntax.
+-   Destructors need access to the private members of a class, so generally
+    would have to be part of the class.
+-   We didn't have any use cases where an author of a type used as a type
+    argument to a class should be able to override the destructor for the class,
+    so users would have to mark destructors as `final` or risk surprises.
+-   Abstract classes without virtual destructors may have code that should run
+    when the type is destroyed, but don't implement any destructor
+    type-of-types.
+-   More generally, we wanted the compiler to enforce that the correct
+    type-of-types were implemented.
+
+This was considered in the open discussions on
+[2021-08-30](https://docs.google.com/document/d/105GsfmxOwcZ_iHkCXFnALB7e-_R3IgMpGKfeT84h1mc/edit?resourcekey=0-h3uVHObsJwChVg1MdaWfKQ#heading=h.4dobu6v1cdam)
+and
+[2021-10-18](https://docs.google.com/document/d/105GsfmxOwcZ_iHkCXFnALB7e-_R3IgMpGKfeT84h1mc/edit?resourcekey=0-h3uVHObsJwChVg1MdaWfKQ#heading=h.uz59mgk5ezch).
+We decided to go with the proposed approach in
+[the open discussion on 2022-03-24](https://docs.google.com/document/d/1cRrhRrmaUf2hVi2lFcHsYo2j0jI6t9RGZoYjWhRxp14/edit?resourcekey=0-xWHBEZ8zIqnJiB4yfBSLfA#heading=h.cy8m79pgct1v).
+
+### Prevent virtual function calls in destructors
+
+We considered disallowing virtual calls to be made while executing the
+destructor. This would avoid having to assign to the vtable pointer between each
+level of the class hierarchy's destructor calls. This seemed like an avoidable
+expense since you can't call a derived method implementation from a destructor
+anyway.
+
+We observed that this assignment, which happens in C++, is unsynchronized and
+could cause race conditions when there was synchronization inside a destructor.
+In Carbon, we expect to mitigate that somewhat by doing the assignment at the
+end of the destructor instead of the beginning, which is safe since we won't
+have multiple inheritance. This means that at least the most derived class'
+destructor can acquire locks before any unsynchronized writes occur.
+
+We didn't decide to include this approach in this proposal, since we felt like
+it would likely be too burdensome to prevent calling member functions from a
+destructor unless the developer did some extra work to mark all functions it
+transtively calls. We would consider this as a potential future extension. As
+noted in the design, this could be achieved by using the `partial` type.
+
+This was considered in the open discussions on
+[2021-08-30](https://docs.google.com/document/d/105GsfmxOwcZ_iHkCXFnALB7e-_R3IgMpGKfeT84h1mc/edit?resourcekey=0-h3uVHObsJwChVg1MdaWfKQ#heading=h.4dobu6v1cdam)
+and
+[2021-10-18](https://docs.google.com/document/d/105GsfmxOwcZ_iHkCXFnALB7e-_R3IgMpGKfeT84h1mc/edit?resourcekey=0-h3uVHObsJwChVg1MdaWfKQ#heading=h.uz59mgk5ezch).
+
+### Allow functions to act as destructors
+
+[We considered, on 2021-08-30](https://docs.google.com/document/d/105GsfmxOwcZ_iHkCXFnALB7e-_R3IgMpGKfeT84h1mc/edit?resourcekey=0-h3uVHObsJwChVg1MdaWfKQ#heading=h.4dobu6v1cdam),
+how we might support returning a value, for example a failure, from a
+destructor. This would require that the destructor be called explicitly, rather
+than implicitly as part of a `Allocator.Delete()` call or a variable leaving
+scope. This led to a design where you could have "named destructors" that were
+ordinary methods except that they consumed their `me` parameter, as is possible
+in Rust. As a result, the `me` parameter would be marked with a keyword like
+`destroy` or `consume`, like:
+
+```
+class MyClass {
+  fn MyDestroyer[destroy me: Self](x: i32) -> bool;
+}
+```
+
+We would need some way to make the ending of the lifetime visible in the caller,
+perhaps:
+
+```
+var c: MyClass = ...;
+// `~c` indicates lifetime of `c` is ending.
+var b: bool = (~c).MyDestroyer(3);
+```
+
+There were questions about how this would interact with unformed state.
+
+We decided to keep this idea in mind, but there is not a pressing need for this
+feature now since this doesn't mirror a capability of C++. We might reconsider
+as part of error handling strategy, or a desire to model resources that can be
+consumed.
+
+### Allow private destructors
+
+C++ allows destructors to be marked `private`, which has some use cases such as
+controlling deletes of a reference-counted value. This introduces
+[context-sensitivity](/docs/project/principles/low_context_sensitivity.md). It
+is particularly concerning if whether a type implements a constraint depends on
+which function is the enclosing scope.
+
+This was considered in the
+[open discussion on 2021-08-30](https://docs.google.com/document/d/105GsfmxOwcZ_iHkCXFnALB7e-_R3IgMpGKfeT84h1mc/edit?resourcekey=0-h3uVHObsJwChVg1MdaWfKQ#heading=h.4dobu6v1cdam).
+
+### Allow multiple conditional destructors
+
+A parameterized type might want to specialize a destructor based on properties
+of the parameter. For example, it might do something more efficient if its
+parameter is a `TriviallyDestructible` type. An approach consistent with
+[conditional methods](/docs/design/generics/details.md#conditional-methods) and
+the [prioritization rule](/docs/design/generics/details.md#prioritization-rule)
+would be to write something like:
+
+```
+class Vector(T:! Movable) {
+  // Prioritize using `match_first`
+  match_first {
+    // Express conditions by using a
+    // specific `Self` type.
+    destructor [U:! TriviallyDestructible,
+                me: Vector(U)];
+    // If first doesn't apply:
+    destructor [me: Self];
+  }
+}
+```
+
+#### Allow direct implementation of `TrivialDestructor`
+
+For the specific case where the developer wants to specialize the destructor to
+be trivial under certain circumstances, we could allow the developer to directly
+implement `TrivialDestructor`. That implementation would include the criteria
+when the trivial destructor should be used. For example, we might express that
+`Optional` has a trivial destructor when its argument does by writing:
+
+```
+class Optional(T:! Concrete) {
+  var value: Storage(T);
+  var holds_value: bool;
+
+  // It's perfectly safe, I assure you
+  impl [U:! TrivialDestructor] Optional(U) as TrivialDestructor {}
+
+  destructor [me: Self] { if (me.holds_value) value.Destroy(); }
+}
+```
+
+### Type-of-type naming
+
+We considered other names for the type-of-types introduced in this proposal.
+
+The name `Deletable` is inconsistent with
+[the decision on #1058](https://github.com/carbon-language/carbon-lang/issues/1058),
+so we considered alternatives like `Delete`, `CanDelete`, `DeleteInterface`,
+`DeleteConstraint`, or `SafeToDelete`. We agreed that it should match whatever
+term was used in the allocation interface, and finalizing that was out of scope
+for this proposal. So we left it at `Deletable` for now, with the understanding
+that this was only a placeholder and not the final name.
+
+Instead of `Destructible`, which is inconsistent with
+[the decision on #1058](https://github.com/carbon-language/carbon-lang/issues/1058),
+we considered:
+
+-   `DestructorConstraint`: a bit too long
+-   `HasDestructor`: means something slightly different
+-   `CanDestroy`: also inconsistent with
+    [the decision on #1058](https://github.com/carbon-language/carbon-lang/issues/1058)
+
+We ultimately decided that the best name is likely `Unsafe` followed by the name
+we chose in place of `Deletable`, and would wait until that is decided.
+
+Originally `TrivialDestructor` was spelled `TriviallyDestructible`, but the word
+"destructor" was more aligned with the `destructor` keyword being used in
+declarations. We also considered replacing "trivial" with something like "no-op"
+or "empty" to emphasize that the destructor does literally nothing and can be
+completely omitted, but decided to stay consistent with C++ for now.
+
+Instead of `Concrete`, we also considered `Instantiable`, but this choice is
+both more concise and more clearly the opposite of "abstract."
+
+### Other approaches to extensible classes without vtables
+
+There were a few alternatives we considered that were specifically concerned
+about how to handle the unsafe case of deleting a pointer to a base class that
+does not have a virtual destructor, but may be pointing to a derived value. We
+[decided to allow this case](p0777.md#no-extensible-objects-with-non-virtual-destructors)
+as part of
+[proposal 777: Inheritance](https://github.com/carbon-language/carbon-lang/pull/777).
+We decided to go with the proposed approach in
+[the open discussion on 2022-03-24](https://docs.google.com/document/d/1cRrhRrmaUf2hVi2lFcHsYo2j0jI6t9RGZoYjWhRxp14/edit?resourcekey=0-xWHBEZ8zIqnJiB4yfBSLfA#heading=h.cy8m79pgct1v).
+
+#### Don't distinguish safe and unsafe delete operations
+
+We could, like C++, have a single operation that includes both the safe and
+unsafe cases. We decided we would like to try the safer option to see if it is
+viable or if it causes problems with interoperation with C++. We expect that
+migrated C++ code can fall-back to using `UnsafeDelete` if using `Delete` is too
+burdensome.
+
+This option was considered in the
+["Option: C++ DWIM model" section of "Extensible classes" doc](https://docs.google.com/document/d/1gbQJN_IMJBnquOUUd2orbHLlAIqZ4pL0Vt7h34DkQjg/edit?resourcekey=0-0lkEvh0umUU206ASFlWc7A#heading=h.d2ybn2szry11).
+
+#### Don't allow unsafe delete
+
+For even more safety, we considered preventing deletion of extensible classes
+without virtual destructors entirely. However, extensible classes without
+virtual destructors were not that rare in C++ code we examined. It did not seem
+likely this would provide enough C++ interop, and there were concerns that C++
+developers would generally want to have an escape hatch for performing
+operations that they could prove to themselves were safe even if the compiler
+could not.
+
+This option was considered in the
+["Option: Like C++ but forbid unsafe" section of "Extensible classes" doc](https://docs.google.com/document/d/1gbQJN_IMJBnquOUUd2orbHLlAIqZ4pL0Vt7h34DkQjg/edit?resourcekey=0-0lkEvh0umUU206ASFlWc7A#heading=h.718ogac3yb9l).
+
+#### Allow final destructors
+
+One option we
+[considered early on](https://docs.google.com/document/d/1QCdKQ33rki-kCDrxi8UHy3a36dtW0WdMqpUzluGSrz4/edit?resourcekey=0-bZmNUiueOiH_sysJNqnT9A#heading=h.40jlsrcgp8mr)
+for extensible classes with non-virtual destructors, was to require the same
+thing we do from non-virtual methods. That is, require that the implementation
+in the base class is appropriate for derived classes. We ultimately decided on a
+different approach, but we could still provide that as an option. You would
+declare the destructor as `final`, and that would impose the necessary
+restrictions on any derived class:
+
+-   No custom destructor code.
+-   Either no added data members, or, if we are willing to support unsized
+    deletes, no added data members with non-trivial destructors.
+
+Base classes with `final` destructors would be `Deletable`, like base classes
+with `virtual` destructors. This would be a safe option without the overhead of
+including a vtable in your object, but fairly restrictive in its applicability.
+
+[We decided](https://discord.com/channels/655572317891461132/708431657849585705/958595054707023964)
+is viable but not pressing since this isn't a feature present in C++, and we
+would wait and see if this would fill a common need.