Skip to content

Commit 9308e4c

Browse files
committed
Add the #[manually_drop] attribute.
This attribute is equivalent to `#[lang = "manually_drop"]`, and the behavior of both are extended to allow the type to define `Drop`, in which case, that will still be run. Only the field drop glue is suppressed. This PR should essentially fully implement #100344. Some additional notes: `#[manually_drop]` means "do not destroy the fields automatically during destruction". `ManuallyDrop`, is (or could be) "just" `#[manually_drop] struct ManuallyDrop<T>(T);`. The intent is, per the MCP, to allow customization of the drop order, without making the interface for initializing or accessing the fields of the struct clumsier than a normal Rust type. It also -- and people did seem excited about this part -- lifts `ManuallyDrop` from being a special language supported feature to just another user of `#[manually_drop]`. There is the question of how this interacts with "insignificant" drop. I had trouble understanding the comments, but insignificant drop appears to just be a static analysis tool, and not something that changes behavior. (For example, it's used to detect if a language change will reorder drops in a meaningful way -- meaning, reorder the significant drops, not the insignificant ones.) Since it's unlikely to be used for `#[manually_drop]` types, I don't think it matters a whole lot. And where a destructor is defined, it would seem to make sense for `#[manually_drop]` types to match exactly the behavior of `union`, since they both have the shared property that field drop glue is suppressed. I looked for all locations that queried for `is_manually_drop` in any form, and found two difficult cases which are hardcoded for `ManuallyDrop` in particular. The first is a clippy lint for redundant clones. I don't understand why it special-cases `ManuallyDrop`, and it's almost certainly wrong for it to special-case `#[manually_drop]` types in general. However, without understanding the original rationale, I have trouble figuring the right fix. Perhaps it should simply be deleted altogether. The second is unions -- specifically, the logic for disabling `DerefMut`. `my_union.x.y = z` will automatically dereference `my_union.x` if it implements `DerefMut`, unless it is `ManuallyDrop`, in which case it will not. This is because `ManuallyDrop` is a pointer back to its content, and so this will automatically call `drop` on a probably uninitialized field, and is unsafe. This is true of `ManuallyDrop`, but not necessarily any other `#[manually_drop]` type. I believe the correct fix would, instead, be a way to mark and detect types which are a smart pointer whose pointee is within `self`. See, for example, this playground link: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=76fb22a6214ce453538fc18ec35a839d But that needs to wait for a separate RFC. For now, we apply exactly the same restriction for `ManuallyDrop` and for any other `#[manually_drop]` type, even though it may be confusing. 1. Delete `#[lang = "manually_drop"]`. I'm not sure if anything special needs to be done here other than replacing it with `#[manually_drop]` -- is there a compatibility guarantee that must be upheld? 2. (Optional) Fix the redundant clone check to correctly handle `#[manually_drop]` structs that aren't `ManuallyDrop`. 3. When there is more experience with the feature (e.g. in Crubit) create a full RFC based on the MCP, and go through the remainder of the stabilization process. (Also, do things like generalize the union error messages to not specifically mention `ManuallyDrop`, but also mention `#[manually_drop]`. For as long as the feature is unstable, the error messages would be confusing if they referenced it...)
1 parent 7ff69b4 commit 9308e4c

File tree

26 files changed

+372
-30
lines changed

26 files changed

+372
-30
lines changed

compiler/rustc_feature/src/active.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -442,6 +442,8 @@ declare_features! (
442442
(active, lint_reasons, "1.31.0", Some(54503), None),
443443
/// Give access to additional metadata about declarative macro meta-variables.
444444
(active, macro_metavar_expr, "1.61.0", Some(83527), None),
445+
/// Allows `#[manually_drop]` on type definitions.
446+
(active, manually_drop_attr, "1.64.0", Some(100344), None),
445447
/// Allows `#[marker]` on certain traits allowing overlapping implementations.
446448
(active, marker_trait_attr, "1.30.0", Some(29864), None),
447449
/// A minimal, sound subset of specialization intended to be used by the

compiler/rustc_feature/src/builtin_attrs.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -480,6 +480,10 @@ pub const BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[
480480
deprecated_safe, Normal, template!(List: r#"since = "version", note = "...""#), ErrorFollowing,
481481
experimental!(deprecated_safe),
482482
),
483+
// lang-team MCP 135
484+
gated!(
485+
manually_drop, Normal, template!(Word), WarnFollowing, manually_drop_attr, experimental!(manually_drop),
486+
),
483487

484488
// `#[collapse_debuginfo]`
485489
gated!(

compiler/rustc_hir_analysis/src/check/check.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,8 +112,10 @@ fn check_union_fields(tcx: TyCtxt<'_>, span: Span, item_def_id: LocalDefId) -> b
112112
allowed_union_field(*elem, tcx, param_env)
113113
}
114114
_ => {
115-
// Fallback case: allow `ManuallyDrop` and things that are `Copy`.
116-
ty.ty_adt_def().is_some_and(|adt_def| adt_def.is_manually_drop())
115+
// Fallback case: if there is no destructor (including field drop glue), because it is
116+
// `Copy` or is `#[manually_drop]` with no `Drop`, then allow it.
117+
ty.ty_adt_def()
118+
.is_some_and(|adt_def| adt_def.is_manually_drop() && !adt_def.has_dtor(tcx))
117119
|| ty.is_copy_modulo_regions(tcx, param_env)
118120
}
119121
}

compiler/rustc_hir_typeck/src/place_op.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -330,12 +330,18 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
330330
}
331331
// If this is a union field, also throw an error for `DerefMut` of `ManuallyDrop` (see RFC 2514).
332332
// This helps avoid accidental drops.
333+
//
334+
// FIXME: This is not correct for `#[manually_drop]` in general, only when the struct is
335+
// a smart pointer whose pointee is at the same address, and whose pointee implements `Drop`.
336+
// Instead of checking for `#[manually_drop]`, this should likely be a more restricted check
337+
// for such types, or else union really should special-case and only permit `ManuallyDrop`, and
338+
// not `#[manually_drop]` types in general.
333339
if inside_union
334340
&& source.ty_adt_def().map_or(false, |adt| adt.is_manually_drop())
335341
{
336342
let mut err = self.tcx.sess.struct_span_err(
337343
expr.span,
338-
"not automatically applying `DerefMut` on `ManuallyDrop` union field",
344+
"not automatically applying `DerefMut` on manually dropped union field",
339345
);
340346
err.help(
341347
"writing to this reference calls the destructor for the old value",

compiler/rustc_middle/src/ty/adt.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,7 @@ impl AdtDefData {
241241
if Some(did) == tcx.lang_items().owned_box() {
242242
flags |= AdtFlags::IS_BOX;
243243
}
244-
if Some(did) == tcx.lang_items().manually_drop() {
244+
if Some(did) == tcx.lang_items().manually_drop() || tcx.has_attr(did, sym::manually_drop) {
245245
flags |= AdtFlags::IS_MANUALLY_DROP;
246246
}
247247
if Some(did) == tcx.lang_items().unsafe_cell_type() {

compiler/rustc_mir_dataflow/src/elaborate_drops.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -443,8 +443,7 @@ where
443443
});
444444
}
445445

446-
let skip_contents =
447-
adt.is_union() || Some(adt.did()) == self.tcx().lang_items().manually_drop();
446+
let skip_contents = adt.is_union() || adt.is_manually_drop();
448447
let contents_drop = if skip_contents {
449448
(self.succ, self.unwind)
450449
} else {

compiler/rustc_passes/src/check_attr.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,7 @@ impl CheckAttrVisitor<'_> {
193193
sym::no_mangle => self.check_no_mangle(hir_id, attr, span, target),
194194
sym::deprecated => self.check_deprecated(hir_id, attr, span, target),
195195
sym::macro_use | sym::macro_escape => self.check_macro_use(hir_id, attr, target),
196+
sym::manually_drop => self.check_manually_drop(hir_id, attr, span, target),
196197
sym::path => self.check_generic_attr(hir_id, attr, target, Target::Mod),
197198
sym::plugin_registrar => self.check_plugin_registrar(hir_id, attr, target),
198199
sym::macro_export => self.check_macro_export(hir_id, attr, target),
@@ -2091,6 +2092,17 @@ impl CheckAttrVisitor<'_> {
20912092
}
20922093
}
20932094

2095+
fn check_manually_drop(&self, hir_id: HirId, attr: &Attribute, span: Span, target: Target) {
2096+
if !matches!(target, Target::Struct | Target::Enum) {
2097+
self.tcx.emit_spanned_lint(
2098+
UNUSED_ATTRIBUTES,
2099+
hir_id,
2100+
attr.span,
2101+
errors::ManuallyDropShouldBeAppliedToStructEnum { defn_span: span },
2102+
);
2103+
}
2104+
}
2105+
20942106
fn check_plugin_registrar(&self, hir_id: HirId, attr: &Attribute, target: Target) {
20952107
if target != Target::Fn {
20962108
self.tcx.emit_spanned_lint(
@@ -2370,6 +2382,7 @@ fn check_invalid_crate_level_attr(tcx: TyCtxt<'_>, attrs: &[Attribute]) {
23702382
sym::rustc_main,
23712383
sym::unix_sigpipe,
23722384
sym::derive,
2385+
sym::manually_drop,
23732386
sym::test,
23742387
sym::test_case,
23752388
sym::global_allocator,

compiler/rustc_passes/src/errors.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -639,6 +639,13 @@ pub struct MacroUse {
639639
pub name: Symbol,
640640
}
641641

642+
#[derive(LintDiagnostic)]
643+
#[diag(passes_should_be_applied_to_struct_enum)]
644+
pub struct ManuallyDropShouldBeAppliedToStructEnum {
645+
#[label]
646+
pub defn_span: Span,
647+
}
648+
642649
#[derive(LintDiagnostic)]
643650
#[diag(passes_macro_export)]
644651
pub struct MacroExport;

compiler/rustc_span/src/symbol.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -902,6 +902,7 @@ symbols! {
902902
main,
903903
managed_boxes,
904904
manually_drop,
905+
manually_drop_attr,
905906
map,
906907
marker,
907908
marker_trait_attr,

compiler/rustc_trait_selection/src/traits/query/dropck_outlives.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,9 @@ pub fn trivial_dropck_outlives<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> bool {
4949
}
5050

5151
ty::Adt(def, _) => {
52-
if Some(def.did()) == tcx.lang_items().manually_drop() {
53-
// `ManuallyDrop` never has a dtor.
52+
if def.is_manually_drop() && !def.has_dtor(tcx) {
53+
// A `#[manually_drop]` type without a Drop impl (e.g. `ManuallyDrop`)
54+
// does not run any code at all when dropped.
5455
true
5556
} else {
5657
// Other types might. Moreover, PhantomData doesn't

0 commit comments

Comments
 (0)