IO operation type values usually need to perform some cleanup work before being destroyed, such as flushing cached data to disk, closing files, closing remote connections gracefully, etc. The natural way to think of is to add these cleanup operations to the Drop implementation . However, if an error occurs when the value is dropped, we cannot pass the error handling to the caller. For this, we usually provide an explicit destructor — this method needs to take ownership of the value self, returning Result<_ , _>. Be sure to highlight this explicit destructor in the documentation so that the caller must call the method to release resources gracefully. However, adding an explicit destructor has two problems:
- Fields of this type cannot be removed in this destructor, because the Drop::drop method will still be called after calling this destructor, and the drop method is responsible for removing fields, which requires that fields of this type have not been moved go;
- The drop method uses a mutable reference to self, i.e. &mut self, not self. Therefore, the explicit destructor cannot be called inside the drop method.
There are three ways to solve the above problem,:
- The first way is to wrap this (inner) type with Option, creating a new top-level type. Both custom destructors and drop methods can be used
Option::take, and the explicit destructor of the inner type is called only when the inner type has not been taken. Since the inner type doesn’t implement Drop, you can take ownership of all the fields in it. The downside of this approach is that all methods provided on the top-level type must now access the fields on the inner type through an Option (which is always Some since drop hasn’t been called yet).
- The second method is to make each field takable. You can use the take method of Option to replace the value with None, or you can use the std::mem::take method to replace the collection with a default value (such as Vec , MashMap) . This method is especially useful when your type fields have reasonably null values. The downside is that accessing each field must determine whether the Option has a value, which is tedious.
- The third method is to wrap the inner type T with the ManuallyDrop<T> type. Using ManuallyDrop does not require unwrap, and dereferences directly to the inner value. It can also be used in drop to
Manually::taketake ownership. The disadvantage is that it
ManuallyDrop::takeis unsafe, because you may call Taking multiple times, or calling take and continuing to use other methods of ManuallyDrop to use internal values.
Ultimately, you should choose which method works best for your application. I generally start with the second approach, and switch to the other two when I see too many Option fields in my types. ManuallyDrop is the best approach if you can easily ensure that your code is safe.