Skip to content

Can't specialize Drop #46893

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
Rantanen opened this issue Dec 20, 2017 · 9 comments
Open

Can't specialize Drop #46893

Rantanen opened this issue Dec 20, 2017 · 9 comments
Labels
A-specialization Area: Trait impl specialization C-enhancement Category: An issue proposing an enhancement or a PR with one. F-specialization `#![feature(specialization)]` T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

Comments

@Rantanen
Copy link
Contributor

The Drop trait has checks to ensure that the impl can't add new restrictions on generic parameters.

struct S<T> { ... }
impl<T: Foo> Drop for S<T> { ... }

The above won't compile (as intended?), as the S would have a Drop that's conditional based on the type parameter.

However with specialization we could have something like:

struct S<T> { ... }
default impl<T> Drop for S<T> { ... }
impl <T: Foo> Drop S<T> { ... }

Both of these examples yield the same error

error[E0367]: The requirement `T: Foo` is added only by the Drop impl.

My first instinct was that this had something to do with the type information getting lost on destructors, but that doesn't seem to be the case as the issue can be worked around by specializing a different trait, to which drop delegates to:

struct S<T> { ... }

// Specialized Drop implementation.
trait SpecialDrop { fn drop( &mut self ); }
default impl<T> SpecialDrop for S<T> { ... }
impl<T: Foo> SpecialDrop for S<T> { ... }

// Drop delegates to SpecialDrop.
impl<T> Drop S<T> { fn drop(&mut self) {
    (self as &mut SpecialDrop).drop()
}

Linking to #31844

@Manishearth
Copy link
Member

Basically we should skip this check entirely if there's a default impl that covers this one (and rely on the check giving an error on the default impl itself if there's a problem there). Unsure if there's an easy, efficient way to fetch the list of impls at that stage.

@pietroalbini pietroalbini added C-enhancement Category: An issue proposing an enhancement or a PR with one. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. A-specialization Area: Trait impl specialization labels Jan 30, 2018
@passcod
Copy link
Contributor

passcod commented Feb 17, 2020

Someone in the Rust Community Discord just got error[E0366]: Drop impls cannot be specialized and then we found this issue.

As a clarification: does this issue and especially the comment by @Manishearth imply that specialized Drop is a thing that could be done and just hasn't been implemented/discussed/designed, or will specialized Drop always be impossible (and is there an explanation somewhere as to why)?

@Manishearth
Copy link
Member

Yes, it could be done. That said, in general Drop is already so special-cased in the type system this does sound like a recipe for bugs.

@SoniEx2
Copy link
Contributor

SoniEx2 commented Feb 6, 2021

(Disregard this if it's inappropriate)

This would be nice, without specialization, for RAII/scoped/transaction guards. Mostly because of generic churn:

// horrible, look at those Deref(Mut)!
struct SubtreeHelper<'r, 's, P: Borrow<str> + Ord, O: Borrow<str> + Ord, T: PatternTypes> where Self: 'r {
    root: &'r mut Parser<'s, P, O, T>,
}

impl<'r, 's, P: Borrow<str> + Ord, O: Borrow<str> + Ord, T: PatternTypes> SubtreeHelper<'r, 's, P, O, T> where Self: 'r {
    fn start(value: &'r mut Parser<'s, P, O, T>) -> Self {
        value.consts.protos.push(Default::default());
        Self {
            root: value,
        }
    }

    fn commit(mut self) -> usize {
        let mut self_ = ManuallyDrop::new(self);
        let proto = self_.root.consts.protos.pop().unwrap();
        let id = self_.root.closed_subtrees.next().unwrap();
        self_.root.consts.protos.insert(id, proto);
        id
    }
}

impl<'r, 's, P: Borrow<str> + Ord, O: Borrow<str> + Ord, T: PatternTypes> std::ops::Deref for SubtreeHelper<'r, 's, P, O, T> where Self: 'r {
    type Target = Parser<'s, P, O, T>;

    fn deref(&self) -> &Self::Target {
        &*self.root
    }
}

impl<'r, 's, P: Borrow<str> + Ord, O: Borrow<str> + Ord, T: PatternTypes> std::ops::DerefMut for SubtreeHelper<'r, 's, P, O, T> where Self: 'r {
    fn deref_mut(&mut self) -> &mut Self::Target {
        self.root
    }
}

impl<'r, 's, P: Borrow<str> + Ord, O: Borrow<str> + Ord, T: PatternTypes> Drop for SubtreeHelper<'r, 's, P, O, T> where Self: 'r {
    fn drop(&mut self) {
        // remove "partial" proto
        self.root.consts.protos.pop().expect("SubtreeHelper");
    }
}
// cleaner?
struct SubtreeHelper<'r, T> where Self: 'r {
    root: &'r mut T,
}

impl<'r, 's, P: Borrow<str> + Ord, O: Borrow<str> + Ord, T: PatternTypes> SubtreeHelper<'r, Parser<'s, P, O, T>> where Self: 'r {
    fn start(value: &'r mut Parser<'s, P, O, T>) -> Self {
        value.consts.protos.push(Default::default());
        Self {
            root: value,
        }
    }

    fn commit(mut self) -> usize {
        let mut self_ = ManuallyDrop::new(self);
        let proto = self_.root.consts.protos.pop().unwrap();
        let id = self_.root.closed_subtrees.next().unwrap();
        self_.root.consts.protos.insert(id, proto);
        id
    }
}

impl<'r, T> std::ops::Deref for SubtreeHelper<'r, T> where Self: 'r {
    type Target = T;

    fn deref(&self) -> &Self::Target {
        &*self.root
    }
}

impl<'r, T> std::ops::DerefMut for SubtreeHelper<'r, T> where Self: 'r {
    fn deref_mut(&mut self) -> &mut Self::Target {
        self.root
    }
}

impl<'r, 's, P: Borrow<str> + Ord, O: Borrow<str> + Ord, T: PatternTypes> Drop for SubtreeHelper<'r, Parser<'s, P, O, T>> where Self: 'r {
    fn drop(&mut self) {
        // remove "partial" proto
        self.root.consts.protos.pop().expect("SubtreeHelper");
    }
}

@3xau1o
Copy link

3xau1o commented May 10, 2025

Can someone explain why drop cannot be used on specialized generic structs, all type information it's available at compile time

#[derive(Debug)]
pub struct LikeStruct<T> {
    pub r: *mut T
}

// ERROR: `Drop` impls cannot be specialized, `SDL_Renderer` is not a generic parameter
impl Drop for LikeStruct<SDL_Renderer> {
    fn drop(&mut self) {
        unsafe { SDL_DestroyRenderer(self.r) }
    }
}

@Manishearth
Copy link
Member

@3xau10 essentially because Drop is strange and whether or not something has Drop affects type system stuff (including negative reasoning around Copy), so having it possible for needs_drop to be true or false depending on generics complicates things in ways that could easily lead to unsoundness.

(it's hard to explain, but, it is a deliberate decision)

@3xau1o
Copy link

3xau1o commented May 12, 2025

@Manishearth, thanks for the reply

whether or not something has Drop affects type system stuff

Sounds like something to avoid, also more complex than just generic type monomorphization

(it's hard to explain, but, it is a deliberate decision)

Sounds reasonable, these people takes language design very seriously, besides there are limitations in all systems

@3xau1o
Copy link

3xau1o commented May 12, 2025

Its interesting because it is indeed possible to have a Drop trait for generic structs, just not directly, related #8142

This Fails

#[derive(Debug)]
pub struct LikeStruct<T> {
   pub r: *mut T
}

// ERROR: `Drop` impls cannot be specialized, `SDL_Renderer` is not a generic parameter
impl Drop for LikeStruct<SDL_Renderer> {
   fn drop(&mut self) {
       unsafe { SDL_DestroyRenderer(self.r) }
   }
}

But drop can be implemented for any artbitrary generic struct using an intermediary trait

// intermediary drop like trait
pub trait LikeDrop {
    fn drop(&mut self);
}

#[derive(Debug)]
pub struct LikeStruct<T: LikeDrop> {
    pub r: T,
}


// Compiles just fine
impl<T: LikeDrop> Drop for LikeStruct<T> {
    fn drop(&mut self) {
        // drop using the intermidary drop like fn
        self.r.drop();
    }
}

impl LikeDrop for  *mut SDL_Renderer {
    fn drop(&mut self) {
        unsafe {
            SDL_DestroyRenderer(*self);
        }
    }
}

based on the example above seems that needs_drop already depends/resolves on generics @Manishearth

so having it possible for needs_drop to be true or false depending on generics complicates things

@Manishearth
Copy link
Member

Not really, in that case it is impossible to have LikeStruct<Foo> where Foo does not implement that trait. So you can still assume `LikeStruct always implements drop. Well-formedness is an implicit constraint here.

Anyway, this is not the place for extended discussion on this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-specialization Area: Trait impl specialization C-enhancement Category: An issue proposing an enhancement or a PR with one. F-specialization `#![feature(specialization)]` T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.
Projects
None yet
Development

No branches or pull requests

7 participants