Hello everyone,
I'm trying to use PhantomData to correctly inform the drop checker about lifetimes for a struct that holds a raw pointer. I've run into some surprising behavior that seems inconsistent, and I'm not sure if it is desired behavior or a known/unknown compiler issue.
I have a simple struct, NoRefContainer<'a, T>, that holds a *const T and uses PhantomData<&'a T> to signal that it logically borrows a T for the lifetime 'a. My expectation is that the compiler should prevent the borrowed data from being dropped before the container itself.
This works as expected when I use a new() function, but not when I initialize the struct directly.
Case 1: Direct Initialization (Compiles Successfully)
When I build the struct directly within a function, the compiler allows me to drop the owned data before dropping the container that logically borrows it. This seems like it should be an error.
Case 2: new() Function Initialization (Fails to Compile)
When I wrap the exact same initialization logic in a new() function, the compiler correctly catches the invalid drop order and produces a borrow-checking error, as expected.
Just to be clear, the issue still happens even if the pointer is accessed in the destructor.
Minimal example, can be reproduced on rustc 1.90.0 (1159e78c4 2025-09-14) and rustc 1.92.0-nightly (dc2c3564d 2025-09-29)
#![allow(dead_code)]
use std::{fmt::{self, Debug}, marker::PhantomData};
#[derive(Debug)]
struct NonCopyint(i32);
struct NoRefContainer<'a, T: 'a + fmt::Debug> {
pointer: *const T,
_marker: PhantomData<&'a T>,
}
impl<'a, T: 'a + fmt::Debug> NoRefContainer<'a, T> {
fn new(value: &'a T) -> Self {
Self {
pointer: value as *const T,
_marker: PhantomData,
}
}
}
impl<'a, T: 'a + fmt::Debug> Drop for NoRefContainer<'a, T> {
fn drop(&mut self) {
unsafe {
println!("Dropping NoRefContainer: {:?}", *self.pointer);
}
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn direct_initialization_compiles() {
let secret;
let container;
secret = NonCopyint(42);
container = NoRefContainer {
pointer: &secret,
_marker: PhantomData,
};
// This compiles but it is incorrect
drop(secret); // `secret` is dropped...
drop(container); // ...while `container` still logically borrows it.
}
#[test]
fn new_function_fails_to_compile() {
let secret;
let container;
secret = NonCopyint(42);
container = NoRefContainer::new(&secret);
// This won't compile! (uncomment to see error)
// drop(secret);
// drop(container);
// Correct order - this compiles
drop(container);
drop(secret);
}
}