Skip to content

Drop impls ignore actual type parameters when running drop glue #16491

Closed
@lilyball

Description

@lilyball

When implementing Drop on a type that has type parameters (which requires #[unsafe_destructor]), if the Drop impl is specialized to some concrete type parameter, the specialization is ignored in the drop glue and the Drop impl is instead run on all values of the outer type regardless of type parameters.

Example:

#![feature(unsafe_destructor)]

extern crate debug;

struct DropMe<T> {
    _x: T
}

struct ImplDrop;

#[unsafe_destructor]
impl Drop for DropMe<ImplDrop> {
    fn drop(&mut self) {
        println!("DropMe<ImplDrop>.Drop: {:?}", *self);
    }
}

fn main() {
    let x = DropMe::<ImplDrop> { _x: ImplDrop };
    println!("Dropping DropMe<ImplDrop>");
    drop(x);

    let y = DropMe::<int> { _x: 32 };
    println!("Dropping DropMe<int>");
    drop(y);
}

This should print

Dropping DropMe<int>
Dropping DropMe<ImplDrop>
DropMe<ImplDrop>.Drop: DropMe<ImplDrop>{_x: ImplDrop}

Instead it prints

Dropping DropMe<int>
DropMe<ImplDrop>.Drop: DropMe<ImplDrop>{_x: ImplDrop}
Dropping DropMe<ImplDrop>
DropMe<ImplDrop>.Drop: DropMe<ImplDrop>{_x: ImplDrop}

Note that it's calling the Drop impl on DropMe<int>, even though the Drop impl itself believes that the receiver type is DropMe<ImplDrop>.


It gets worse. If you add a second specialized Drop impl, that completely overrides the first:

#![feature(unsafe_destructor)]

extern crate debug;

struct DropMe<T> {
    _x: T
}

struct ImplDrop;

#[unsafe_destructor]
impl Drop for DropMe<ImplDrop> {
    fn drop(&mut self) {
        println!("DropMe<ImplDrop>.Drop: {:?}", *self);
    }
}

#[unsafe_destructor]
impl Drop for DropMe<int> {
    fn drop(&mut self) {
        println!("DropMe<int>.Drop: {:?}", *self);
    }
}

fn main() {
    let x = DropMe::<int> { _x: 32 };
    println!("Dropping DropMe<int>");
    drop(x);

    let y = DropMe::<ImplDrop> { _x: ImplDrop };
    println!("Dropping DropMe<ImplDrop>");
    drop(y);
}

This prints:

Dropping DropMe<int>
DropMe<int>.Drop: DropMe<int>{_x: 32}
Dropping DropMe<ImplDrop>
DropMe<int>.Drop: DropMe<int>{_x: 140734783283201}

It gets worse. If you use a type parameter with bounds, it incorrectly believes that it needs to call the Drop impl regardless of parameterization (as it did before), even though it knows that the type doesn't conform to the bounds. At least this time it's a compile-time error:

#![feature(unsafe_destructor)]

extern crate debug;

struct DropMe<T> {
    _x: T
}

struct ImplDrop;
trait Foo {}
impl Foo for ImplDrop {}

#[unsafe_destructor]
impl<T: Foo> Drop for DropMe<T> {
    fn drop(&mut self) {
        println!("DropMe<T: Foo>.Drop: {:?}", *self);
    }
}

fn main() {
    let x = DropMe::<int> { _x: 32 };
    println!("Dropping DropMe<int>");
    drop(x);

    let y = DropMe::<ImplDrop> { _x: ImplDrop };
    println!("Dropping DropMe<ImplDrop>");
    drop(y);
}
unnamed.rs:15:5: 17:6 error: failed to find an implementation of trait Foo for int
unnamed.rs:15     fn drop(&mut self) {
unnamed.rs:16         println!("DropMe<T: Foo>.Drop: {:?}", *self);
unnamed.rs:17     }

And of course if you add an impl Drop for DropMe<int> then it complains about duplicate implementations.

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-destructorsArea: Destructors (`Drop`, …)

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions