【Rust中级教程】2.8. API设计原则之灵活性(flexible) Pt.4:显式析构函数的问题及3种解决方案

喜欢的话别忘了点赞、收藏加关注哦(加关注即可阅读全文),对接下来的教程有兴趣的可以关注专栏。谢谢喵!(=^・ω・^=)

说句题外话,这篇文章一共5721个字,是我截至目前写的最长的一篇文章,看我这么努力,还不点赞、收藏加关注?

2.8.1. 显式析构函数的问题

添加显式析构函数时会遇到问题:

  • 当某一类型实现了Drop,在析构函数中无法将该类型的任何字段移出。因为在显式析构函数运行后,drop()仍会被调用,它接受&mut self,要求self的所有部分都没被移动。
  • Drop接收的是&mut self而不是self,因此Drop无法实现简单地调用显式析构函数并忽略其结果(因为Drop不拥有self)

以上一篇文章的例子为基础,如果我们加上既实现了Drop trait,又写了close方法:

rust 复制代码
use std::os::fd::AsRawFd;  
use std::fs::{File as StdFile, OpenOptions, metadata};  
use std::io::Error;  
  
/// 一个表示文件句柄的类型  
struct File {  
    /// 文件名  
    name: String,  
    /// 文件描述符  
    fd: i32,  
}  
  
impl File {  
    /// 一个构造函数,打开一个文件并返回一个 File 实例  
    fn open(name: &str) -> Result<File, Error> {  
        // 使用 OpenOptions 打开文件,具备读写权限  
        let file: StdFile = OpenOptions::new()  
            .read(true)  
            .write(true)  
            .open(name)?;  
  
        // 获取文件描述符  
        let fd: i32 = file.as_raw_fd();  
  
        // 返回一个 File 实例  
        Ok(File {  
            name: name.to_string(),  
            fd,  
        })  
    }  
  
    /// 一个显式的析构函数,关闭文件并返回任何错误  
    fn close(self) -> Result<(), Error> {  
        // 使用 FromRawFd 将 fd 转换回 File        
        let file: std::fs::File = unsafe {   
            std::os::unix::io::FromRawFd::from_raw_fd(self.fd)   
        };  
  
        // 刷新文件数据到磁盘  
        file.sync_all()?;  
  
        // 将文件截断为 0 字节  
        file.set_len(0)?;  
  
        // 再次刷新文件  
        file.sync_all()?;  
  
        // 丢弃文件实例,它会自动关闭文件  
        drop(file);  
  
        // 返回成功  
        Ok(())  
    }  
}  
  
//实现drop trait  
impl Drop for File {  
    fn drop(&mut self) {  
        let _ = self.close();  //调用close方法来丢弃  
        println!("File dropped");  
    }  
}  
  
fn main() {  
    // 创建一个名为 "test.txt" 的文件,并写入一些内容  
    std::fs::write("test.txt", "Hello, world!").unwrap();  
  
    // 打开文件并获取 File 实例  
    let file: File = File::open("test.txt").unwrap();  
  
    // 打印文件名和 fd    
    println!("File name: {}, fd: {}", file.name, file.fd);  
  
    // 关闭文件并处理任何错误  
    match file.close() {  
        Ok(()) => println!("File closed successfully"),  
        Err(e) => println!("Error closing file: {}", e),  
    }  
  
    // 检查关闭后的文件大小  
    let metadata = metadata("test.txt").unwrap();  
    println!("File size: {} bytes", metadata.len());  
}

输出:

error[E0507]: cannot move out of `*self` which is behind a mutable reference
  --> src/main.rs:59:17
   |
59 |         let _ = self.close();  //调用close方法来丢弃
   |                 ^^^^ ------- `*self` moved due to this method call
   |                 |
   |                 move occurs because `*self` has type `File`, which does not implement the `Copy` trait
   |
note: `File::close` takes ownership of the receiver `self`, which moves `*self`
  --> src/main.rs:33:14
   |
33 |     fn close(self) -> Result<(), Error> {
   |              ^^^^
note: if `File` implemented `Clone`, you could clone the value
  --> src/main.rs:6:1
   |
6  | struct File {
   | ^^^^^^^^^^^ consider implementing `Clone` for this type
...
59 |         let _ = self.close();  //调用close方法来丢弃
   |                 ---- you could clone this value

报错信息显示无法从*self中移出值,因为它位于&mut self后面。

2.8.2. 解决方案

首先需要说明的是没有完美的解决方案,我们只能尽力弥补。


解决方案1:把结构体包装Option<T>里然后再套一层结构体

我们可以将顶层方案作为包装了Option<T>的新类型,这样Option<T>内部持有一个类型,这个类型包含所有的字段。

这个时候我们就需要两个析构函数,外边一个里面一个。在这两个析构函数中使用Option::take函数来获取数据所有权从而移除值。

由于内部类型没有实现Drop,所以你可以获取所有字段的所有权。

缺点:想在顶层类型上提供的所有方法,现在都必须添加通过Option<T>这层壳来获取内部类型上字段的代码。

我们在上文的代码例基础上进行修改:

步骤1:改掉File的定义,套一层壳

首先我们得把两个字段移到另一个结构体里,套在Option<T>中作为File结构体的字段

rust 复制代码
/// 一个表示文件句柄的类型  
struct InnerFile {  
    /// 文件名  
    name: String,  
    /// 文件描述符  
    fd: i32,  
}  
  
/// 给InnerFile套了一个壳  
struct File {  
    /// 把InnerFile包装在Option<T>中  
    inner: Option<InnerFile>,  
}

步骤2:修改File上的方法

File上有两个方法,我们都需要添加通过Option<T>这层壳来获取内部类型上字段的代码。

首先是open方法:

rust 复制代码
/// 一个构造函数,打开一个文件并返回一个 File 实例  
fn open(name: &str) -> Result<File, Error> {  
    // 使用 OpenOptions 打开文件,具备读写权限  
    let file: StdFile = OpenOptions::new()  
        .read(true)  
        .write(true)  
        .open(name)?;  
  
    // 获取文件描述符  
    let fd: i32 = file.as_raw_fd();  
  
    // 返回一个 File 实例  
    Ok(File {  
        inner: Some( InnerFile {  
            name: name.to_string(),  
            fd,  
        })  
    })  
}
  • 由于这个代码只有返回值设计了File结构体,所以也只有返回值需要改

接下来是close方法:

rust 复制代码
/// 一个显式的析构函数,关闭文件并返回任何错误  
fn close(mut self) -> Result<(), Error> { // 记得self要变为mut self,否则take不了  
    // 使用模式匹配提取出字段的值  
    if let Some(inner) = self.inner.take() {  
        let name = inner.name;  
        let fd = inner.fd;  
        println!("Closing file: {} with fd: {}", name, fd);  
          
        // 使用 FromRawFd 将 fd 转换回 File        
        let file: std::fs::File = unsafe {  
            std::os::unix::io::FromRawFd::from_raw_fd(fd)  
        };  
  
        // 刷新文件数据到磁盘  
        file.sync_all()?;  
  
        // 将文件截断为 0 字节  
        file.set_len(0)?;  
  
        // 再次刷新文件  
        file.sync_all()?;  
  
        // 丢弃文件实例,它会自动关闭文件  
        drop(file);  
  
        // 返回成功  
        Ok(())  
    } else {  
        // 如果inner字段是None,说明文件已经被关闭或丢弃,返回错误  
        Err(Error::new(  
            std::io::ErrorKind::Other,  
            "File is already closed",  
        ))  
    }  
}
  • 参数传进去之后先得通过模式匹配来获取字段的值
  • 如果如果inner字段是None,也就是模式匹配不成功的情况下,我们需要自己写一个错误返回

步骤3:修改Drop trait的实现

Drop::drop方法需要修改:

rust 复制代码
fn drop(&mut self) {  
    // 使用模式匹配获取字段值  
    if let Some(inner) = self.inner.take() {  
        let name = inner.name;  
        let fd = inner.fd;  
        println!("Dropping file: {} (fd: {})", name, fd);  
  
        // 使用 FromRawFd 将 fd 转换回 File        
        let file: std::fs::File = unsafe {  
            std::os::unix::io::FromRawFd::from_raw_fd(fd)  
        };  
          
        // 丢弃file实例  
        drop(file);  
    } else {  
        // 如果inner字段是None的话,说明文件已经被丢弃或关闭,不做任何操作  
    }  
}
  • 参数传进去之后先得通过模式匹配来获取字段的值
  • 如果inner字段是None的话,说明文件已经被丢弃或关闭,不做任何操作

步骤4:微修主函数

主函数中需要提取字段值的地方需要修改:

rust 复制代码
fn main() {
	// ...前文无修改,已省略

	// 打印文件名和 fd (这里需要修改)  
	println!("File name: {}, fd: {}",   
	    file.inner.as_ref().unwrap().name,  
	    file.inner.as_ref().unwrap().fd  
	);

	// ...后文无修改,已省略
}
  • 原始类型是 Option<InnerFile>,调用.as_ref()后变成Option<&InnerFile>
  • 变成了Option<&InnerFile>再使用unwrap提取出来的值就是引用而不是所有的值
  • file.inner是一个Option<InnerFile>类型。而直接访问Option的值需要转移所有权或匹配处理(例如通过 take()unwrap()),这会销毁Option的内部值,所以得要as_ref

整体代码

rust 复制代码
use std::os::fd::AsRawFd;  
use std::fs::{File as StdFile, OpenOptions, metadata};  
use std::io::Error;  
  
/// 一个表示文件句柄的类型  
struct InnerFile {  
    /// 文件名  
    name: String,  
    /// 文件描述符  
    fd: i32,  
}  
  
/// 给InnerFile套了一个壳  
struct File {  
    /// 把InnerFile包装在Option<T>中  
    inner: Option<InnerFile>,  
}  
  
impl File {  
    /// 一个构造函数,打开一个文件并返回一个 File 实例  
    fn open(name: &str) -> Result<File, Error> {  
        // 使用 OpenOptions 打开文件,具备读写权限  
        let file: StdFile = OpenOptions::new()  
            .read(true)  
            .write(true)  
            .open(name)?;  
  
        // 获取文件描述符  
        let fd: i32 = file.as_raw_fd();  
  
        // 返回一个 File 实例  
        Ok(File {  
            inner: Some( InnerFile {  
                name: name.to_string(),  
                fd,  
            })  
        })  
    }  
  
    /// 一个显式的析构函数,关闭文件并返回任何错误  
    fn close(mut self) -> Result<(), Error> { // 记得self要变为mut self,否则take不了  
        // 使用模式匹配提取出字段的值  
        if let Some(inner) = self.inner.take() {  
            let name = inner.name;  
            let fd = inner.fd;  
            println!("Closing file: {} with fd: {}", name, fd);  
              
            // 使用 FromRawFd 将 fd 转换回 File            
            let file: std::fs::File = unsafe {  
                std::os::unix::io::FromRawFd::from_raw_fd(fd)  
            };  
  
            // 刷新文件数据到磁盘  
            file.sync_all()?;  
  
            // 将文件截断为 0 字节  
            file.set_len(0)?;  
  
            // 再次刷新文件  
            file.sync_all()?;  
  
            // 丢弃文件实例,它会自动关闭文件  
            drop(file);  
  
            // 返回成功  
            Ok(())  
        } else {  
            // 如果inner字段是None,说明文件已经被关闭或丢弃,返回错误  
            Err(Error::new(  
                std::io::ErrorKind::Other,  
                "File is already closed",  
            ))  
        }  
    }  
}  
  
// 实现drop trait,用于在值离开作用域时运行的一些代码  
impl Drop for File {  
    fn drop(&mut self) {  
        // 使用模式匹配获取字段值  
        if let Some(inner) = self.inner.take() {  
            let name = inner.name;  
            let fd = inner.fd;  
            println!("Dropping file: {} (fd: {})", name, fd);  
  
            // 使用 FromRawFd 将 fd 转换回 File            
            let file: std::fs::File = unsafe {  
                std::os::unix::io::FromRawFd::from_raw_fd(fd)  
            };  
              
            // 丢弃file实例  
            drop(file);  
        } else {  
            // 如果inner字段是None的话,说明文件已经被丢弃或关闭,不做任何操作  
        }  
    }  
}  
  
fn main() {  
    // 创建一个名为 "test.txt" 的文件,并写入一些内容  
    std::fs::write("test.txt", "Hello, world!").unwrap();  
  
    // 打开文件并获取 File 实例  
    let file: File = File::open("test.txt").unwrap();  
  
    // 打印文件名和 fd (这里需要修改)  
    println!("File name: {}, fd: {}",   
        file.inner.as_ref().unwrap().name,  
        file.inner.as_ref().unwrap().fd  
    );  
  
    // 关闭文件并处理任何错误  
    match file.close() {  
        Ok(()) => println!("File closed successfully"),  
        Err(e) => println!("Error closing file: {}", e),  
    }  
  
    // 检查关闭后的文件大小  
    let metadata = metadata("test.txt").unwrap();  
    println!("File size: {} bytes", metadata.len());  
}

解决方案2:把字段包装在Option<T>

我们也可以保持结构体不变,把每个字段的值都套在Option<T>里,需要获取所有权使用时用Option::take就可以,需要引用时用.as_ref().unwrap()就可以。

如果类型具有合理的空值,那么效果很好。

缺点:如果你必须将几乎每个字段都包装在Option中,然后对这些字段的每次访问都进行匹配的unwrap就会使代码变得很繁琐。

我们在上文的代码例基础上进行修改:

步骤1:改掉File的定义

为每一个字段添加一层Option<T>

rust 复制代码
/// 一个表示文件句柄的类型  
struct File {  
    /// 文件名  
    name: Option<String>,  
    /// 文件描述符  
    fd: Option<i32>,  
}

步骤2:修改File上的方法

File上有两个方法,我们都需要添加通过Option<T>这层壳来获取内部类型上字段的代码。

首先是open方法:

rust 复制代码
/// 一个构造函数,打开一个文件并返回一个 File 实例  
fn open(name: &str) -> Result<File, Error> {  
    // 使用 OpenOptions 打开文件,具备读写权限  
    let file: StdFile = OpenOptions::new()  
        .read(true)  
        .write(true)  
        .open(name)?;  
  
    // 获取文件描述符  
    let fd: i32 = file.as_raw_fd();  
  
    // 返回一个 File 实例  
    Ok(File {  
        name: Some(name.to_string()),  
        fd: Some(fd),  
    })  
}
  • open方法的参数没有涉及到File结构体,所以接收参数部分不用修改
  • open方法的返回值涉及到了File,得为每个字段添上Some变体

其次是close方法:

rust 复制代码
/// 一个显式的析构函数,关闭文件并返回任何错误  
fn close(mut self) -> Result<(), Error> {  
    // 模式匹配,并使用std::mem::take取出name字段的值  
    if let Some(name) = std::mem::take(&mut self.name) {  
        //模式匹配,并使用std::mem::take取出fd字段的值  
        if let Some(fd) = std::mem::take(&mut self.fd) {
	        // 打印
	        println!("Closing file: {} with fd: {}", name, fd);
	          
            // 使用 FromRawFd 将 fd 转换回 File            
            let file: std::fs::File = unsafe {  
                std::os::unix::io::FromRawFd::from_raw_fd(fd)  
            };  
  
            // 刷新文件数据到磁盘  
            file.sync_all()?;  
  
            // 将文件截断为 0 字节  
            file.set_len(0)?;  
  
            // 再次刷新文件  
            file.sync_all()?;  
  
            // 丢弃文件实例,它会自动关闭文件  
            drop(file);  
  
            // 返回成功  
            Ok(())  
        } else {  
            // 如果fd字段是None,说明文件已经被关闭或丢弃,返回一个错误  
            Err(Error::new(  
                std::io::ErrorKind::Other,  
                "File descriptor already dropped or taken",  
            ))  
        }  
    } else {  
        // 如果name字段是None,说明文件已经被关闭或丢弃,返回一个错误  
        Err(Error::new(  
            std::io::ErrorKind::Other,  
            "File name already dropped or taken",  
        ))  
    }  
}
  • 参数要先经过模式匹配,并使用std::mem::take取出里面的值
  • 如果任意字段是None,说明文件已经被关闭或丢弃,返回一个错误

步骤3:修改Drop trait的实现

rust 复制代码
fn drop(&mut self) {  
    // 使用模式匹配获取字段值  
    if let Some(name) = self.name.take() {  
        if let Some(fd) = self.fd.take() {  
            println!("Dropping file: {} (fd: {})", name, fd);  
  
            // 使用 FromRawFd 将 fd 转换回 
            Filelet file: std::fs::File = unsafe {  
                std::os::unix::io::FromRawFd::from_raw_fd(fd)  
            };  
  
            // 丢弃file实例    
			drop(file);  
        } else {  
            // 如果fd字段是None,说明文件已经被关闭或丢弃,不做任何操作  
        }  
    } else {  
        // 如果name字段是None,说明文件已经被关闭或丢弃,不做任何操作  
    }  
}
  • 参数要先经过模式匹配,并使用std::mem::take取出里面的值
  • 如果任意字段是None,说明文件已经被关闭或丢弃,不做任何操作

步骤4:微修主函数

rust 复制代码
fn main() {
	// ...前文无修改,已省略

	// 打印文件名和 fd (这里需要修改)  
	println!("File name: {}, fd: {}",   
         file.name.as_ref().unwrap(),  
         file.fd.as_ref().unwrap()  
	);

	// ...后文无修改,已省略
}
  • 原始类型被Option<T>包裹,调用.as_ref()后获得里面值的引用
  • 变成了引用之后再使用unwrap提取出来的值就是引用而不是所有的值
  • 而直接访问Option的值需要转移所有权或匹配处理(例如通过 take()unwrap()),这会销毁Option的内部值,所以得要as_ref

整体代码

rust 复制代码
use std::os::fd::AsRawFd;  
use std::fs::{File as StdFile, OpenOptions, metadata};  
use std::io::Error;  
  
/// 一个表示文件句柄的类型  
struct File {  
    /// 文件名  
    name: Option<String>,  
    /// 文件描述符  
    fd: Option<i32>,  
}  
  
impl File {  
    /// 一个构造函数,打开一个文件并返回一个 File 实例  
    fn open(name: &str) -> Result<File, Error> {  
        // 使用 OpenOptions 打开文件,具备读写权限  
        let file: StdFile = OpenOptions::new()  
            .read(true)  
            .write(true)  
            .open(name)?;  
  
        // 获取文件描述符  
        let fd: i32 = file.as_raw_fd();  
  
        // 返回一个 File 实例  
        Ok(File {  
            name: Some(name.to_string()),  
            fd: Some(fd),  
        })  
    }  
  
    /// 一个显式的析构函数,关闭文件并返回任何错误  
    fn close(mut self) -> Result<(), Error> {  
        // 模式匹配,并使用使用std::mem::take取出name字段的值  
        if let Some(name) = std::mem::take(&mut self.name) {  
            //模式匹配,并使用使用std::mem::take取出fd字段的值  
            if let Some(fd) = std::mem::take(&mut self.fd) {
	            // 打印
	            println!("Closing file: {} with fd: {}", name, fd);
	              
                // 使用 FromRawFd 将 fd 转换回 File                
                let file: std::fs::File = unsafe {  
                    std::os::unix::io::FromRawFd::from_raw_fd(fd)  
                };  
  
                // 刷新文件数据到磁盘  
                file.sync_all()?;  
  
                // 将文件截断为 0 字节  
                file.set_len(0)?;  
  
                // 再次刷新文件  
                file.sync_all()?;  
  
                // 丢弃文件实例,它会自动关闭文件  
                drop(file);  
  
                // 返回成功  
                Ok(())  
            } else {  
                // 如果fd字段是None,说明文件已经被关闭或丢弃,返回一个错误  
                Err(Error::new(  
                    std::io::ErrorKind::Other,  
                    "File descriptor already dropped or taken",  
                ))  
            }  
        } else {  
            // 如果name字段是None,说明文件已经被关闭或丢弃,返回一个错误  
            Err(Error::new(  
                std::io::ErrorKind::Other,  
                "File name already dropped or taken",  
            ))  
        }  
    }  
}  
  
// 实现drop trait,用于在值离开作用域时运行的一些代码  
impl Drop for File {  
    fn drop(&mut self) {  
        // 使用模式匹配获取字段值  
        if let Some(name) = self.name.take() {  
            if let Some(fd) = self.fd.take() {  
                println!("Dropping file: {} (fd: {})", name, fd);  
  
                // 使用 FromRawFd 将 fd 转换回 
                Filelet file: std::fs::File = unsafe {  
                    std::os::unix::io::FromRawFd::from_raw_fd(fd)  
                };  
  
                // 丢弃file实例    
				drop(file);  
            } else {  
                // 如果fd字段是None,说明文件已经被关闭或丢弃,不做任何操作  
            }  
        } else {  
            // 如果name字段是None,说明文件已经被关闭或丢弃,不做任何操作  
        }  
    }  
}  
  
fn main() {  
    // 创建一个名为 "test.txt" 的文件,并写入一些内容  
    std::fs::write("test.txt", "Hello, world!").unwrap();  
  
    // 打开文件并获取 File 实例  
    let file: File = File::open("test.txt").unwrap();  
  
    // 打印文件名和 fd (这里需要修改)   
	println!("File name: {}, fd: {}",   
             file.name.as_ref().unwrap(),  
             file.fd.as_ref().unwrap()  
    );  
  
    // 关闭文件并处理任何错误  
    match file.close() {  
        Ok(()) => println!("File closed successfully"),  
        Err(e) => println!("Error closing file: {}", e),  
    }  
  
    // 检查关闭后的文件大小  
    let metadata = metadata("test.txt").unwrap();  
    println!("File size: {} bytes", metadata.len());  
}

方法3:将数据持有在ManuallyDrop类型内

将数据持有在ManuallyDrop类型内,它会解引用内部类型,不必再使用unwrap

drop中进行销毁时,可使用ManuallyDrop::take来获取所有权。

缺点:ManuallyDrop::take是不安全的,需要放在unsafe块中。

我们在上文的代码例基础上进行修改:

步骤1:改掉File的定义

为每一个字段添加一层Option<T>

rust 复制代码
/// 一个表示文件句柄的类型  
struct File {  
    /// 文件名  
    name: ManuallyDrop<String>,  
    /// 文件描述符  
    fd: ManuallyDrop<i32>,  
}

步骤2:修改File上的方法

File上有两个方法,我们都需要添加通过Option<T>这层壳来获取内部类型上字段的代码。

首先是open方法:

rust 复制代码
/// 一个构造函数,打开一个文件并返回一个 File 实例  
fn open(name: &str) -> Result<File, Error> {  
    // 使用 OpenOptions 打开文件,具备读写权限  
    let file: StdFile = OpenOptions::new()  
        .read(true)  
        .write(true)  
        .open(name)?;  
  
    // 获取文件描述符  
    let fd: i32 = file.as_raw_fd();  
  
    // 返回一个 File 实例  
    Ok(File {  
        name: ManuallyDrop::new(name.to_string()),  
        fd: ManuallyDrop::new(fd),  
    })  
}
  • open方法的参数没有涉及到File结构体,所以接收参数部分不用修改
  • open方法的返回值涉及到了File,每个字段都得用ManuallyDrop::new来传值

其次是close方法:

rust 复制代码
/// 一个显式的析构函数,关闭文件并返回任何错误  
fn close(mut self) -> Result<(), Error> {  
    // 使用std::mem::replace将name字段替换为一个空字符串,把原来的值给name  
    let name =   
        std::mem::replace(&mut self.name, ManuallyDrop::new("".to_string()));  
      
    // 使用std::mem::replace将fd字段替换为一个无效的值(-1),把原来的值给fd 
    let fd =   
        std::mem::replace(&mut self.fd, ManuallyDrop::new(0));  
      
    // 打印  
    println!("Closing file: {:?} with fd: {:?}", name, fd);  
      
    // 使用 FromRawFd 将 fd 转换回 File    
    let file: std::fs::File = unsafe {  
        std::os::unix::io::FromRawFd::from_raw_fd(*fd) //这里fd要先解引用  
    };  
  
    // 刷新文件数据到磁盘  
    file.sync_all()?;  
  
    // 将文件截断为 0 字节  
    file.set_len(0)?;  
  
    // 再次刷新文件  
    file.sync_all()?;  
  
    // 丢弃文件实例,它会自动关闭文件  
    drop(file);  
  
    // 返回成功  
    Ok(())  
}
  • 使用std::mem::replacename字段替换为一个空字符串,把原来的值给name
  • 使用std::mem::replacefd字段替换为一个无效的值(-1),把原来的值给fd
  • std::os::unix::io::FromRawFd::from_raw_fd(*fd)中参数要先解引用,也就是写*fd

步骤3:修改Drop trait的实现

rust 复制代码
fn drop(&mut self) {  
    // 使用ManuallyDrop::take取出name字段的值,并检查是否是空字符串  
    let name = unsafe { ManuallyDrop::take(&mut self.name) };  
      
    // 使用ManuallyDrop::take取出fd字段的值,并检查是否是无效的值  
    let fd = unsafe { ManuallyDrop::take(&mut self.fd) };  
      
    //打印  
    println!("Dropping file: {:?} (fd: {:?})", name, fd);  
      
    // 如果fd字段不是无效的值,说明文件还没有被关闭或丢弃,需要执行丢弃操作  
    if fd != -1 || !name.is_empty() {  
	    let file = unsafe { std::fs::File::from_raw_fd(fd) };  
	    // 丢弃  
	    drop(file);  
	}
}
  • 使用ManuallyDrop::take取出namefd字段的值,并检查是否是空字符串或无效的值
  • 如果fd字段不是无效的值(不是-1),或是name字段不是空字符串,就说明文件还没有被关闭或丢弃,需要执行丢弃操作
  • 其实这里不用两个判断条件(fd != -1 || !name.is_empty()),一个就够了,因为namefd字段的值的变化是一起的,一个无效就代表着整个结构体都还未被清理。

步骤4:微修主函数

rust 复制代码
fn main() {
	// ...前文无修改,已省略

	// 打印文件名和 fd (这里需要修改)  
	println!("File name: {}, fd: {}", *file.name, *file.fd);

	// ...后文无修改,已省略
}
  • 使用解引用来打印值

整体代码

rust 复制代码
use std::os::fd::{AsRawFd, FromRawFd};  
use std::fs::{File as StdFile, OpenOptions, metadata};  
use std::io::Error;  
use std::mem::ManuallyDrop;  
  
/// 一个表示文件句柄的类型  
struct File {  
    /// 文件名  
    name: ManuallyDrop<String>,  
    /// 文件描述符  
    fd: ManuallyDrop<i32>,  
}  
  
impl File {  
    /// 一个构造函数,打开一个文件并返回一个 File 实例  
    fn open(name: &str) -> Result<File, Error> {  
        // 使用 OpenOptions 打开文件,具备读写权限  
        let file: StdFile = OpenOptions::new()  
            .read(true)  
            .write(true)  
            .open(name)?;  
  
        // 获取文件描述符  
        let fd: i32 = file.as_raw_fd();  
  
        // 返回一个 File 实例  
        Ok(File {  
            name: ManuallyDrop::new(name.to_string()),  
            fd: ManuallyDrop::new(fd),  
        })  
    }  
  
    /// 一个显式的析构函数,关闭文件并返回任何错误  
    fn close(mut self) -> Result<(), Error> {  
        // 使用std::mem::replace将name字段替换为一个空字符串,把原来的值给name  
        let name =   
            std::mem::replace(&mut self.name, ManuallyDrop::new("".to_string()));  
          
        // 使用std::mem::replace将fd字段替换为一个无效的值(-1),把原来的值给fd  
        let fd =   
            std::mem::replace(&mut self.fd, ManuallyDrop::new(-1));  
          
        // 打印  
        println!("Closing file: {:?} with fd: {:?}", name, fd);  
          
        // 使用 FromRawFd 将 fd 转换回 File        
        let file: std::fs::File = unsafe {  
            std::os::unix::io::FromRawFd::from_raw_fd(*fd) //这里fd要先解引用  
        };  
  
        // 刷新文件数据到磁盘  
        file.sync_all()?;  
  
        // 将文件截断为 0 字节  
        file.set_len(0)?;  
  
        // 再次刷新文件  
        file.sync_all()?;  
  
        // 丢弃文件实例,它会自动关闭文件  
        drop(file);  
  
        // 返回成功  
        Ok(())  
    }  
}  
  
// 实现drop trait,用于在值离开作用域时运行的一些代码  
impl Drop for File {  
    fn drop(&mut self) {  
        // 使用ManuallyDrop::take取出name字段的值,并检查是否是空字符串  
        let name = unsafe { ManuallyDrop::take(&mut self.name) };  
          
        // 使用ManuallyDrop::take取出fd字段的值,并检查是否是无效的值  
        let fd = unsafe { ManuallyDrop::take(&mut self.fd) };  
          
        //打印  
        println!("Dropping file: {:?} (fd: {:?})", name, fd);  
          
        // 如果fd字段不是无效的值,说明文件还没有被关闭或丢弃,需要执行丢弃操作  
        if fd != -1 || !name.is_empty() {  
            let file = unsafe { std::fs::File::from_raw_fd(fd) };  
            // 丢弃  
            drop(file);  
        }  
    }  
}  
  
fn main() {  
    // 创建一个名为 "test.txt" 的文件,并写入一些内容  
    std::fs::write("test.txt", "Hello, world!").unwrap();  
  
    // 打开文件并获取 File 实例  
    let file: File = File::open("test.txt").unwrap();  
  
    // 打印文件名和 fd (这里需要修改)   
println!("File name: {}, fd: {}", *file.name, *file.fd);  
  
    // 关闭文件并处理任何错误  
    match file.close() {  
        Ok(()) => println!("File closed successfully"),  
        Err(e) => println!("Error closing file: {}", e),  
    }  
  
    // 检查关闭后的文件大小  
    let metadata = metadata("test.txt").unwrap();  
    println!("File size: {} bytes", metadata.len());  
}

三种方案的选择

这三种方案的选择要根据实际情况,通常第二个方案。但是如果真的字段太多要写的unwrap太多的话就需要考虑其他的方案。

如果代码足够简单,可以轻松检查代码的安全性,那么第三种ManuallyDrop方案也是非常好的。

相关推荐
m0_7263658322 分钟前
某宝同款百度盘不限速后台系统源码_卡密系统_Java账号管理系统部署方案
java·开发语言
zr想努力26 分钟前
Lua语言入门(自用)
开发语言·lua
半桔26 分钟前
C++入门
c语言·开发语言·数据结构·c++·vscode·青少年编程
yuanpan29 分钟前
23种设计模式之《外观模式(Facade)》在c#中的应用及理解
开发语言·设计模式·c#·外观模式
奔跑吧邓邓子1 小时前
【Python爬虫(64)】从“听”开始:Python音频爬虫与语音数据处理全解析
开发语言·爬虫·python·音频·语音识别
_nut_2 小时前
手撕跳表/数据结构
java·开发语言·数据结构
小猪咪piggy2 小时前
【数据结构】(12) 反射、枚举、lambda 表达式
java·开发语言·数据结构
web147862107232 小时前
数据库系统架构与DBMS功能探微:现代信息时代数据管理的关键
java·开发语言·数据库
嵌入式修炼师3 小时前
深入理解 QVectorQString:Qt 中动态数组的强大力量
开发语言·qt
浪子西科3 小时前
【数据结构】(Python)第六章:图
开发语言·数据结构·python