为什么Drop需要使用&mut self而不是self?

48
为什么Drop的方法签名是fn drop(&mut self)而不是fn drop(self)?这使得从字段中移动值变得困难,例如self.join_handle.join()std::mem::drop(self.file)(错误:无法移动定义了Drop trait的类型X)。
3个回答

20

让我们看一下std::mem::drop是如何实现的:

pub fn drop<T>(_x: T) { }

没错:这是一个空函数!这是因为它利用移动语义获取其参数的所有权。如果T实现了Drop,编译器会自动在函数末尾插入对Drop::drop(_x)的调用。这发生在通过值接收的所有参数上(事实上是所有参数,但放弃引用不会放弃引用对象)。

现在考虑一下如果Drop::drop按值接收其参数会发生什么:编译器将尝试在Drop::drop内部调用参数的Drop::drop - 这将导致堆栈溢出!当然,您可以调用mem::drop来处理参数,但这也会尝试递归调用Drop::drop


24
可以的,但编译器可以很容易地特殊处理这种情况。 - Lucretiel
8
这个回答存在问题。std::mem::drop 存在的唯一原因是为了调用者在调用 drop 前强制放弃所有权。如果 drop 直接接受 self,则不需要 std::mem::drop - GManNickG
@GManNickG:std::mem::drop 没有 任何特殊之处。它存在的唯一原因是为了方便,因为有时您需要删除某些内容(例如锁保护),而 drop(guard);let _x = guard; 更漂亮。std::ops::Drop::dropstd::mem::drop 具有相同的名称基本上是巧合。 - Chris Morgan

8
实际上,Drop::drop 不需要获取值的所有权。
在 Rust 中,所有权会被自动处理,并且编译器会确保正确实现所有权语义;因此当 Foo { a: int, b: String } 超出其作用域时,编译器将自动通过删除其内部字段来删除 Foo
因此,Drop::drop 不需要删除这些字段!
实际上,在调用 Foo 上的 Drop::drop 之后,编译器会自己使用 mem::drop 删除不同的字段(这也可能会调用定义了它的字段的 Drop::drop,例如这里的 b: String)。
那么,Drop::drop 到底是做什么的?
它用于在编译器所执行的基础上实现额外的逻辑;以您的 JoinHandle 示例为例:
#[stable(feature = "rust1", since = "1.0.0")]
#[unsafe_destructor]
impl<T> Drop for JoinHandle<T> {
    fn drop(&mut self) {
        if !self.0.joined {
            unsafe { imp::detach(self.0.native) }
        }
    }
}

这里使用了Drop::drop来分离线程,例如。

在像Vec::vec这样的集合中:

#[unsafe_destructor]
#[stable(feature = "rust1", since = "1.0.0")]
impl<T> Drop for Vec<T> {
    fn drop(&mut self) {
        // This is (and should always remain) a no-op if the fields are
        // zeroed (when moving out, because of #[unsafe_no_drop_flag]).
        if self.cap != 0 && self.cap != mem::POST_DROP_USIZE {
            unsafe {
                for x in &*self {
                    ptr::read(x);
                }
                dealloc(*self.ptr, self.cap)
            }
        }
    }
}

在这里,由于原始内存被以对编译器不透明的方式操作,因此该实现负责:
  1. 删除向量中保存的每个元素
  2. 释放内存

5
抱歉,我的表述可能不够清晰。我并不打算手动删除每个字段。我的意图是调用一些函数来使用我的某些字段,以便我可以等待线程或记录任何错误(我承认我不应该提到 std::mem::drop,因为它无法告诉您发生了什么错误)。 - yonran
1
@yonran:好的,像你这样询问理由是一个有效的问题,所以我不愿意告诉你编辑它并完全更改它,相反,我建议你简单地提出另一个问题,询问如何做你想做的事情,最好在 playpen 上提供一个 MCVE,我们可以调整以确保我们的解决方案可以编译。 - Matthieu M.
1
@yonran:你有没有问过另一个问题?我和你一样遇到了完全相同的问题;具体来说是在Drop()中使用WavWriter::finalize() - Timmmm
1
@Timmmm,抱歉,我没有。我认为我只是使用了一个 Option<T> 来在 drop 过程中消费它。 - yonran

8
尽管上面的答案很好,但我仍然需要澄清。这就是我所得到的内容...
Drop trait是一个特性,详情请见此处
pub trait Drop {
    pub fn drop(&mut self);
}

要求使用可变引用而不是移动操作,因为drop函数不旨在释放内存,而仅执行释放之前的准备工作。因此,更好的命名方式是prepare_to_drop

实际的内存释放由std::mem模块中的一个函数执行,该函数实际上接受一个 self,并定义如下:

pub fn drop<T>(_x: T) { }

这个函数显然使用了编译器的生命周期管理语义,按预期接管了 T 并隐式地丢弃了它。如果我们假设编译器对于所有没有实现自己 Drop 特性的类型隐式派生一个空的 Drop 特性,那么对这个特定函数更详细的编码揭示了编译器的技巧...

//Note: this is a bare function (no Self parameter)

pub fn drop<T: Drop>(x: T) { 
    x.drop();    //prepare_to_drop
}                //release memory since argument is owned
                 //  and scope is at end.

为了完整起见,Drop特性要求使用可变引用&mut T,而不是共享引用&T,原因是准备工作可能涉及到更改T的内容,比如通过将值与默认值交换来重新分配所拥有的值或引用(参见Option.take),因为在共享引用上不允许进行更改操作。

2
但是你只能通过std::mem::swapOption::take()或类似的方式移出。你不能直接从&mut引用中移出(而不进行替换)。 - Pavel Šimerda

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接