Change:
Consider this code: I believe it should not compile (by failing dropcheck). It, however, compiles and runs on 1.42 and 1.31. On 1.24 it indeed fails as I expect. The equivalent code, where is replaced with rightfully fails to compiles: . So, dropchk somehow observes the difference between and ? Either I misunderstand how is supposed to work, or this is a stable-to-stable regression and a soundless hole.
It fails to compile with the 2015 edition on 1.31, so it's probably caused by NLL. I suspect the issue is something like: never "needs drop", so your struct also doesn't, so no terminator is created that could access the borrowed value.
Adding an unrelated field that has a destructor makes the error appear again
Regression from 1.36:
cc
Assigning to this bug and leaving the nomination, this could have been too. This was .
Should this error, though, if *no code actually runs? .
We definitely need to check into this, but I think is correct that the special role of only comes into play when actually implements ; otherwise, the compiler is allowed to know that dropping is a no-op (except in so far as it drops the contents of ).
Injected between +nightly-2019-04-22 and +nightly-2019-04-23 So current guess is that this was fallout from PR
And I have now confirmed that if you enable edition=2018, you will see this behavior going all the way back to Rust version 1.31.0 (i.e. back when we first deployed stable support for the 2018 edition). Ugh, said exactly this ; sorry for overlooking that! So now I just want to confirm the running hypothesis that this is logical according to NLL.
At this point I agree with the logic put forth by and up above: There is no destructor operating on the type that holds the (i.e. in this example), so it is sound to treat dropping as a no-op. I do worry a little bit about the user's mental model for this case, however. The claim " is just like a " doesn't quite hold up. (not that it ever did ...) More specifically, here is the current text from the std library docs for , section : So, it seems at the very least we have a documentation bug to resolve here...
Does play any role whatsoever when there is no for a type containing one? The way I understand it, and if I'm right, that's a solution for the documentation, is that, when there is no field containing an actual or a type that may drop a , there are three cases: no explicit impl no instance of type may be used by the drop glue, hence it is fine for to dangle. Although it could, there is no reason for to play any role explicit impl, which is thus generic over (thus leading to being part of the drop glue). In that case, if dangles, there will be errors, whether there is a or not (by that I mean that one can replace by or and there will still be errors). So plays no role here explicit impl, which is thus generic over , but this time the type parameter has the (and unstable) annotation on it. In that case, if does dangle, errors will happen if and only if there is an "owned" in the type definition, and is counted as owned. . So here is the situation where interacts with . So I think the simpler phrasing w.r.t. the + interaction, is that, instead, a field interacts with . and the difference between the field + maydangle, and a classic drop impl is that the classic drop impl does not allow a dangling , even when has no drop glue (e.g., ) whereas the + does allow it. But both do deny . __ So, back to the OP, the question is: could / should the case be merged into the case ? And when phrased like that, it looks like there is no special reason for it. In stable Rust we can always go from (and ) to by having a classic impl (even an empty one), but it is true that going from to (and thus from to ) requires nightly Rust.
Yes. First of all, it is also relevant for variance. Secondly, it is relevant for a "normal" even without , because then the compiler assumes that this will call and dropck's accordingly. For example: This is unsound because of the lack of , no matter whether the drop impl is or not.
Could you provide an example of an implementation being unsound that does not use ? The following (case from my previous post), for instance, fails to compile: _ The way I see it, was created to allow this kind of situations, where, since has no drop glue, it is sound to it. So, the way to express to Rust that the only usage of a type that happens in an explicit impl is , is to: first, -annotate the type parameter ("Hey Rust, I am not using ..."),then, ensure the type contains a field that -owns a , such as a ("... except for my -ing it") So, without step , I see no difference between and .
Hm... okay things don't work entirely as I thought they would. To rephrase what you said, a drop may not call any methods on except for dropping it, and moreover if it drops it we also need the so that rustc can check that? But without I may call 's drop even if I don't have a ? Interesting. I still have an idea for a counterexample but it's getting tricky: a type not having in its parameters at all (some kind oft type erasure going on) would have to still drop a . The would be remembered by an outer wrapper type that however does not have a destructor. This needs some awful unsafe code to extract the destructor from the vtable... I don't think I'll write all this right now, not sure what that would accomplish.
Yeah that actually sounds like an interesting and potentially useful data structure (a "view" into untyped memory responsible for calling destructors on it before handing it back). Beyond the soundness issue, I also don't like this because it makes the rules more complicated. "treat PhantomData<Tlike T during structural recursion" is a simple rule that applies to other "auto traits" (to the extent that inherent drop impls are auto trait implementations), and I was hoping to use it for dropck as well.
Are you sure dropck isn't using that rule, though? Your demonstrates that dropck works field-by-field. Isn't it just doing that here, too? (EDIT: Hm okay, in the original example replacing by changes behavior... so I guess it is violating that rule, I am just still confused about how exactly dropck works.) Arguably, dropck needs a special case for because otherwise it would never have any effect, given that it never needs dropping...
Or special case for to depend on the inner . is documented to be only an optimization, so changing it's value to true is allowed:
I think I have managed to implement what you were talking about: I have crafted a custom that works as except for it expressing that it owns and thus may drop a independently of whether the containing struct impls any whatsoever (I have achieved this by making by a containing struct by itself, with its own dummy ).I have attempted to write an unsound program, which ought to be sound with the expected ownership semantics described in this thread (which carries, but which does not), and the former fails to compile, whereas the latter does not.I find it unclear whether it is the library's design fault of it is 's, so I'll let you be the judge.
Yeah, something like that is what I was imagining... though your code still has on the same type that also has the . I think I was imagining putting the function pointer and impl down into another type, so that and are on two different types. I am not sure why would be needed?
I'm still confused about: This is unsound because of the lack of , no matter whether the drop impl is or not. From my understanding of dropck, considering impl no Drop: If it construct without , the dropck would be: (strictly) with : where If you want to construct a ub from the former, it is equivalent to finding a that satisfies but not , and causes ub when it destructs. But I tried to construct such a and failed. I can't even find a counterexample for . And I'm not sure how this violates dropck:
Wow this is an ancient thread, and I lost all context. Could you link to the context where I said that? Also I recently opened , does that help?
Re-reading the thread above, it seems to be about the same fundamental question as Curious. My PR fixes the documentation paragraph that quoted above They also wrote which is still true and would require pretty fundamental changes to NLL at this point.
I know only works on needsdrop types dropping. I'm just curious how the following code causes ub, and how it violates dropck rules:
Ah, you mean . A link would have been helpful. :) Note that I go on saying clarifies the misunderstanding me and many others had -- is never needed for unless you use .
Oh, I will note that :D. But as the example in , still works for "normal" dropping.(without )
Yeah I read that thread after this one.^^ The summary is "it's complicated, hopefully we'll figure it out in ". Let's not re-post everything in two spots. :)

Discussion:
/// /// ## Ownership and the drop check /// <del> /// Adding a field of type `PhantomData<T>` indicates that your /// type owns data of type `T`. This in turn implies that when your /// type is dropped, it may drop one or more instances of the type /// `T`. This has bearing on the Rust compiler's [drop check] /// analysis. </del> <ins> /// The exact interaction of `PhantomData` with drop check **may change in the future**. </ins> /// <del> /// If your struct does not in fact *own* the data of type `T`, it is /// better to use a reference type, like `PhantomData<&'a T>` /// (ideally) or `PhantomData<*const T>` (if no lifetime applies), so /// as not to indicate ownership. </del> <ins> /// Currently, adding a field of type `PhantomData<T>` indicates that your type *owns* data of type /// `T` in very rare circumstances. This in turn has effects on the Rust compiler's [drop check] /// analysis. For the exact rules, see the [drop check] documentation. </ins> /// /// ## Layout ///

/// * `size_of::<PhantomData<T>>() == 0` /// * `align_of::<PhantomData<T>>() == 1` /// <del> /// [drop check]: ../../nomicon/dropck.html </del> <ins> /// [drop check]: Drop#drop-check </ins> #[lang = "phantom_data"] #[stable(feature = "rust1", since = "1.0.0")] pub struct PhantomData<T: ?Sized>;

/// are `Copy` get implicitly duplicated by the compiler, making it very /// hard to predict when, and how often destructors will be executed. As such, /// these types cannot have destructors. <ins> /// /// ## Drop check /// /// Dropping interacts with the borrow checker in subtle ways: when a type `T` is being implicitly /// dropped as some variable of this type goes out of scope, the borrow checker needs to ensure that /// calling `T`'s destructor at this moment is safe. In particular, it also needs to be safe to /// recursively drop all the fields of `T`. For example, it is crucial that code like the following /// is being rejected: /// /// ```compile_fail,E0597 /// use std::cell::Cell; /// /// struct S<'a>(Cell<Option<&'a S<'a>>>, Box<i32>); /// impl Drop for S<'_> { ///     fn drop(&mut self) { ///         if let Some(r) = self.0.get() { ///             // Print the contents of the `Box` in `r`. ///             println!("{}", r.1); ///         } ///     } /// } /// /// fn main() { ///     // Set up two `S` that point to each other. ///     let s1 = S(Cell::new(None), Box::new(42)); ///     let s2 = S(Cell::new(Some(&s1)), Box::new(42)); ///     s1.0.set(Some(&s2)); ///     // Now they both get dropped. But whichever is the 2nd one ///     // to be dropped will access the `Box` in the first one, ///     // which is a use-after-free! /// } /// ``` /// /// The Nomicon discusses the need for [drop check in more detail][drop check]. /// /// To reject such code, the "drop check" analysis determines which types and lifetimes need to /// still be live when `T` gets dropped. The exact details of this analysis are not yet /// stably guaranteed and **subject to change**. Currently, the analysis works as follows: /// - If `T` has no drop glue, then trivially nothing is required to be live. This is the case if ///   neither `T` nor any of its (recursive) fields have a destructor (`impl Drop`). [`PhantomData`] ///   and [`ManuallyDrop`] are considered to never have a destructor, no matter their field type. /// - If `T` has drop glue, then, for all types `U` that are *owned* by any field of `T`, ///   recursively add the types and lifetimes that need to be live when `U` gets dropped. The set of ///   owned types is determined by recursively traversing `T`: ///   - Recursively descend through `PhantomData`, `Box`, tuples, and arrays (including arrays of ///     length 0). ///   - Stop at reference and raw pointer types as well as function pointers and function items; ///     they do not own anything. ///   - Stop at non-composite types (type parameters that remain generic in the current context and ///     base types such as integers and `bool`); these types are owned. ///   - When hitting an ADT with `impl Drop`, stop there; this type is owned. ///   - When hitting an ADT without `impl Drop`, recursively descend to its fields. (For an `enum`, ///     consider all fields of all variants.) /// - Furthermore, if `T` implements `Drop`, then all generic (lifetime and type) parameters of `T` ///   must be live. /// /// In the above example, the last clause implies that `'a` must be live when `S<'a>` is dropped, /// and hence the example is rejected. If we remove the `impl Drop`, the liveness requirement /// disappears and the example is accepted. /// /// There exists an unstable way for a type to opt-out of the last clause; this is called "drop /// check eyepatch" or `may_dangle`. For more details on this nightly-only feature, see the /// [discussion in the Nomicon][nomicon]. /// /// [`ManuallyDrop`]: crate::mem::ManuallyDrop /// [`PhantomData`]: crate::marker::PhantomData /// [drop check]: ../../nomicon/dropck.html /// [nomicon]: ../../nomicon/phantom-data.html#an-exception-the-special-case-of-the-standard-library-and-its-unstable-may_dangle </ins> #[lang = "drop"] #[stable(feature = "rust1", since = "1.0.0")] #[const_trait]

<ins> // check-pass //! This test checks that dropck knows that ManuallyDrop does not drop its field. #![feature(dropck_eyepatch)] use std::mem::ManuallyDrop; struct S<T>(ManuallyDrop<T>); unsafe impl<#[may_dangle] T> Drop for S<T> { fn drop(&mut self) {} } struct NonTrivialDrop<'a>(&'a str); impl<'a> Drop for NonTrivialDrop<'a> { fn drop(&mut self) {} } fn main() { let s = String::from("string"); let _t = S(ManuallyDrop::new(NonTrivialDrop(&s))); drop(s); } </ins>