【Rust中级教程】2.8. API计划原则之灵活性(flexible) Pt.4:显式析构函数 ...

打印 上一主题 下一主题

主题 872|帖子 872|积分 2616

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

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

2.8.1. 显式析构函数的问题

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


  • 当某一范例实现了Drop,在析构函数中无法将该范例的任何字段移出。因为在显式析构函数运行后,drop()仍会被调用,它担当&mut self,要求self的全部部门都没被移动。
  • Drop接收的是&mut self而不是self,因此Drop无法实现简单地调用显式析构函数并忽略其结果(因为Drop不拥有self)
以上一篇文章的例子为基础,如果我们加上既实现了Drop trait,又写了close方法:
  1. use std::os::fd::AsRawFd;  
  2. use std::fs::{File as StdFile, OpenOptions, metadata};  
  3. use std::io::Error;  
  4.   
  5. /// 一个表示文件句柄的类型  
  6. struct File {  
  7.     /// 文件名  
  8.     name: String,  
  9.     /// 文件描述符  
  10.     fd: i32,  
  11. }  
  12.   
  13. impl File {  
  14.     /// 一个构造函数,打开一个文件并返回一个 File 实例  
  15.     fn open(name: &str) -> Result<File, Error> {  
  16.         // 使用 OpenOptions 打开文件,具备读写权限  
  17.         let file: StdFile = OpenOptions::new()  
  18.             .read(true)  
  19.             .write(true)  
  20.             .open(name)?;  
  21.   
  22.         // 获取文件描述符  
  23.         let fd: i32 = file.as_raw_fd();  
  24.   
  25.         // 返回一个 File 实例  
  26.         Ok(File {  
  27.             name: name.to_string(),  
  28.             fd,  
  29.         })  
  30.     }  
  31.   
  32.     /// 一个显式的析构函数,关闭文件并返回任何错误  
  33.     fn close(self) -> Result<(), Error> {  
  34.         // 使用 FromRawFd 将 fd 转换回 File        
  35.         let file: std::fs::File = unsafe {   
  36.             std::os::unix::io::FromRawFd::from_raw_fd(self.fd)   
  37.         };  
  38.   
  39.         // 刷新文件数据到磁盘  
  40.         file.sync_all()?;  
  41.   
  42.         // 将文件截断为 0 字节  
  43.         file.set_len(0)?;  
  44.   
  45.         // 再次刷新文件  
  46.         file.sync_all()?;  
  47.   
  48.         // 丢弃文件实例,它会自动关闭文件  
  49.         drop(file);  
  50.   
  51.         // 返回成功  
  52.         Ok(())  
  53.     }  
  54. }  
  55.   
  56. //实现drop trait  
  57. impl Drop for File {  
  58.     fn drop(&mut self) {  
  59.         let _ = self.close();  //调用close方法来丢弃  
  60.         println!("File dropped");  
  61.     }  
  62. }  
  63.   
  64. fn main() {  
  65.     // 创建一个名为 "test.txt" 的文件,并写入一些内容  
  66.     std::fs::write("test.txt", "Hello, world!").unwrap();  
  67.   
  68.     // 打开文件并获取 File 实例  
  69.     let file: File = File::open("test.txt").unwrap();  
  70.   
  71.     // 打印文件名和 fd   
  72.     println!("File name: {}, fd: {}", file.name, file.fd);  
  73.   
  74.     // 关闭文件并处理任何错误  
  75.     match file.close() {  
  76.         Ok(()) => println!("File closed successfully"),  
  77.         Err(e) => println!("Error closing file: {}", e),  
  78.     }  
  79.   
  80.     // 检查关闭后的文件大小  
  81.     let metadata = metadata("test.txt").unwrap();  
  82.     println!("File size: {} bytes", metadata.len());  
  83. }
复制代码
输出:
  1. error[E0507]: cannot move out of `*self` which is behind a mutable reference
  2.   --> src/main.rs:59:17
  3.    |
  4. 59 |         let _ = self.close();  //调用close方法来丢弃
  5.    |                 ^^^^ ------- `*self` moved due to this method call
  6.    |                 |
  7.    |                 move occurs because `*self` has type `File`, which does not implement the `Copy` trait
  8.    |
  9. note: `File::close` takes ownership of the receiver `self`, which moves `*self`
  10.   --> src/main.rs:33:14
  11.    |
  12. 33 |     fn close(self) -> Result<(), Error> {
  13.    |              ^^^^
  14. note: if `File` implemented `Clone`, you could clone the value
  15.   --> src/main.rs:6:1
  16.    |
  17. 6  | struct File {
  18.    | ^^^^^^^^^^^ consider implementing `Clone` for this type
  19. ...
  20. 59 |         let _ = self.close();  //调用close方法来丢弃
  21.    |                 ---- 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结构体的字段
  1. /// 一个表示文件句柄的类型  
  2. struct InnerFile {  
  3.     /// 文件名  
  4.     name: String,  
  5.     /// 文件描述符  
  6.     fd: i32,  
  7. }  
  8.   
  9. /// 给InnerFile套了一个壳  
  10. struct File {  
  11.     /// 把InnerFile包装在Option<T>中  
  12.     inner: Option<InnerFile>,  
  13. }
复制代码
步骤2:修改File上的方法

File上有两个方法,我们都需要添加通过Option<T>这层壳来获取内部范例上字段的代码。
首先是open方法:
  1. /// 一个构造函数,打开一个文件并返回一个 File 实例  
  2. fn open(name: &str) -> Result<File, Error> {  
  3.     // 使用 OpenOptions 打开文件,具备读写权限  
  4.     let file: StdFile = OpenOptions::new()  
  5.         .read(true)  
  6.         .write(true)  
  7.         .open(name)?;  
  8.   
  9.     // 获取文件描述符  
  10.     let fd: i32 = file.as_raw_fd();  
  11.   
  12.     // 返回一个 File 实例  
  13.     Ok(File {  
  14.         inner: Some( InnerFile {  
  15.             name: name.to_string(),  
  16.             fd,  
  17.         })  
  18.     })  
  19. }
复制代码


  • 由于这个代码只有返回值计划了File结构体,以是也只有返回值需要改
接下来是close方法:
  1. /// 一个显式的析构函数,关闭文件并返回任何错误  
  2. fn close(mut self) -> Result<(), Error> { // 记得self要变为mut self,否则take不了  
  3.     // 使用模式匹配提取出字段的值  
  4.     if let Some(inner) = self.inner.take() {  
  5.         let name = inner.name;  
  6.         let fd = inner.fd;  
  7.         println!("Closing file: {} with fd: {}", name, fd);  
  8.          
  9.         // 使用 FromRawFd 将 fd 转换回 File        
  10.         let file: std::fs::File = unsafe {  
  11.             std::os::unix::io::FromRawFd::from_raw_fd(fd)  
  12.         };  
  13.   
  14.         // 刷新文件数据到磁盘  
  15.         file.sync_all()?;  
  16.   
  17.         // 将文件截断为 0 字节  
  18.         file.set_len(0)?;  
  19.   
  20.         // 再次刷新文件  
  21.         file.sync_all()?;  
  22.   
  23.         // 丢弃文件实例,它会自动关闭文件  
  24.         drop(file);  
  25.   
  26.         // 返回成功  
  27.         Ok(())  
  28.     } else {  
  29.         // 如果inner字段是None,说明文件已经被关闭或丢弃,返回错误  
  30.         Err(Error::new(  
  31.             std::io::ErrorKind::Other,  
  32.             "File is already closed",  
  33.         ))  
  34.     }  
  35. }
复制代码


  • 参数传进去之后先得通过模式匹配来获取字段的值
  • 如果如果inner字段是None,也就是模式匹配不乐成的环境下,我们需要自己写一个错误返回
步骤3:修改Drop trait的实现

Drop::drop方法需要修改:
  1. fn drop(&mut self) {  
  2.     // 使用模式匹配获取字段值  
  3.     if let Some(inner) = self.inner.take() {  
  4.         let name = inner.name;  
  5.         let fd = inner.fd;  
  6.         println!("Dropping file: {} (fd: {})", name, fd);  
  7.   
  8.         // 使用 FromRawFd 将 fd 转换回 File        
  9.         let file: std::fs::File = unsafe {  
  10.             std::os::unix::io::FromRawFd::from_raw_fd(fd)  
  11.         };  
  12.          
  13.         // 丢弃file实例  
  14.         drop(file);  
  15.     } else {  
  16.         // 如果inner字段是None的话,说明文件已经被丢弃或关闭,不做任何操作  
  17.     }  
  18. }
复制代码


  • 参数传进去之后先得通过模式匹配来获取字段的值
  • 如果inner字段是None的话,说明文件已经被丢弃或关闭,不做任何操作
步骤4:微修主函数

主函数中需要提取字段值的地方需要修改:
  1. fn main() {
  2.         // ...前文无修改,已省略
  3.         // 打印文件名和 fd (这里需要修改)  
  4.         println!("File name: {}, fd: {}",   
  5.             file.inner.as_ref().unwrap().name,  
  6.             file.inner.as_ref().unwrap().fd  
  7.         );
  8.         // ...后文无修改,已省略
  9. }
复制代码


  • 原始范例是 Option<InnerFile>,调用.as_ref()后变成Option<&InnerFile>
  • 变成了Option<&InnerFile>再使用unwrap提取出来的值就是引用而不是全部的值
  • file.inner是一个Option<InnerFile>范例。而直接访问Option的值需要转移全部权或匹配处理(例如通过 take() 或 unwrap()),这会销毁Option的内部值,以是得要as_ref
团体代码

  1. use std::os::fd::AsRawFd;  use std::fs::{File as StdFile, OpenOptions, metadata};  use std::io::Error;    /// 一个表示文件句柄的类型  
  2. struct InnerFile {  
  3.     /// 文件名  
  4.     name: String,  
  5.     /// 文件描述符  
  6.     fd: i32,  
  7. }  
  8.   
  9. /// 给InnerFile套了一个壳  
  10. struct File {  
  11.     /// 把InnerFile包装在Option<T>中  
  12.     inner: Option<InnerFile>,  
  13. }
  14.     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>:
  1. /// 一个表示文件句柄的类型  
  2. struct File {  
  3.     /// 文件名  
  4.     name: Option<String>,  
  5.     /// 文件描述符  
  6.     fd: Option<i32>,  
  7. }
复制代码
步骤2:修改File上的方法

File上有两个方法,我们都需要添加通过Option<T>这层壳来获取内部范例上字段的代码。
首先是open方法:
  1. /// 一个构造函数,打开一个文件并返回一个 File 实例  
  2. fn open(name: &str) -> Result<File, Error> {  
  3.     // 使用 OpenOptions 打开文件,具备读写权限  
  4.     let file: StdFile = OpenOptions::new()  
  5.         .read(true)  
  6.         .write(true)  
  7.         .open(name)?;  
  8.   
  9.     // 获取文件描述符  
  10.     let fd: i32 = file.as_raw_fd();  
  11.   
  12.     // 返回一个 File 实例  
  13.     Ok(File {  
  14.         name: Some(name.to_string()),  
  15.         fd: Some(fd),  
  16.     })  
  17. }
复制代码


  • open方法的参数没有涉及到File结构体,以是接收参数部门不用修改
  • open方法的返回值涉及到了File,得为每个字段添上Some变体
其次是close方法:
  1. /// 一个显式的析构函数,关闭文件并返回任何错误  
  2. fn close(mut self) -> Result<(), Error> {  
  3.     // 模式匹配,并使用std::mem::take取出name字段的值  
  4.     if let Some(name) = std::mem::take(&mut self.name) {  
  5.         //模式匹配,并使用std::mem::take取出fd字段的值  
  6.         if let Some(fd) = std::mem::take(&mut self.fd) {
  7.                 // 打印
  8.                 println!("Closing file: {} with fd: {}", name, fd);
  9.                   
  10.             // 使用 FromRawFd 将 fd 转换回 File            
  11.             let file: std::fs::File = unsafe {  
  12.                 std::os::unix::io::FromRawFd::from_raw_fd(fd)  
  13.             };  
  14.   
  15.             // 刷新文件数据到磁盘  
  16.             file.sync_all()?;  
  17.   
  18.             // 将文件截断为 0 字节  
  19.             file.set_len(0)?;  
  20.   
  21.             // 再次刷新文件  
  22.             file.sync_all()?;  
  23.   
  24.             // 丢弃文件实例,它会自动关闭文件  
  25.             drop(file);  
  26.   
  27.             // 返回成功  
  28.             Ok(())  
  29.         } else {  
  30.             // 如果fd字段是None,说明文件已经被关闭或丢弃,返回一个错误  
  31.             Err(Error::new(  
  32.                 std::io::ErrorKind::Other,  
  33.                 "File descriptor already dropped or taken",  
  34.             ))  
  35.         }  
  36.     } else {  
  37.         // 如果name字段是None,说明文件已经被关闭或丢弃,返回一个错误  
  38.         Err(Error::new(  
  39.             std::io::ErrorKind::Other,  
  40.             "File name already dropped or taken",  
  41.         ))  
  42.     }  
  43. }
复制代码


  • 参数要先经过模式匹配,并使用std::mem::take取出里面的值
  • 如果任意字段是None,说明文件已经被关闭或丢弃,返回一个错误
步骤3:修改Drop trait的实现

  1. fn drop(&mut self) {  
  2.     // 使用模式匹配获取字段值  
  3.     if let Some(name) = self.name.take() {  
  4.         if let Some(fd) = self.fd.take() {  
  5.             println!("Dropping file: {} (fd: {})", name, fd);  
  6.   
  7.             // 使用 FromRawFd 将 fd 转换回
  8.             Filelet file: std::fs::File = unsafe {  
  9.                 std::os::unix::io::FromRawFd::from_raw_fd(fd)  
  10.             };  
  11.   
  12.             // 丢弃file实例   
  13.                         drop(file);  
  14.         } else {  
  15.             // 如果fd字段是None,说明文件已经被关闭或丢弃,不做任何操作  
  16.         }  
  17.     } else {  
  18.         // 如果name字段是None,说明文件已经被关闭或丢弃,不做任何操作  
  19.     }  
  20. }
复制代码


  • 参数要先经过模式匹配,并使用std::mem::take取出里面的值
  • 如果任意字段是None,说明文件已经被关闭或丢弃,不做任何操作
步骤4:微修主函数

  1. fn main() {
  2.         // ...前文无修改,已省略
  3.         // 打印文件名和 fd (这里需要修改)  
  4.         println!("File name: {}, fd: {}",   
  5.          file.name.as_ref().unwrap(),  
  6.          file.fd.as_ref().unwrap()  
  7.         );
  8.         // ...后文无修改,已省略
  9. }
复制代码


  • 原始范例被Option<T>包裹,调用.as_ref()后获得里面值的引用
  • 变成了引用之后再使用unwrap提取出来的值就是引用而不是全部的值
  • 而直接访问Option的值需要转移全部权或匹配处理(例如通过 take() 或 unwrap()),这会销毁Option的内部值,以是得要as_ref
团体代码

  1. use std::os::fd::AsRawFd;  use std::fs::{File as StdFile, OpenOptions, metadata};  use std::io::Error;    /// 一个表示文件句柄的类型  
  2. struct File {  
  3.     /// 文件名  
  4.     name: Option<String>,  
  5.     /// 文件描述符  
  6.     fd: Option<i32>,  
  7. }
  8.     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>:
  1. /// 一个表示文件句柄的类型  
  2. struct File {  
  3.     /// 文件名  
  4.     name: ManuallyDrop<String>,  
  5.     /// 文件描述符  
  6.     fd: ManuallyDrop<i32>,  
  7. }
复制代码
步骤2:修改File上的方法

File上有两个方法,我们都需要添加通过Option<T>这层壳来获取内部范例上字段的代码。
首先是open方法:
  1. /// 一个构造函数,打开一个文件并返回一个 File 实例  
  2. fn open(name: &str) -> Result<File, Error> {  
  3.     // 使用 OpenOptions 打开文件,具备读写权限  
  4.     let file: StdFile = OpenOptions::new()  
  5.         .read(true)  
  6.         .write(true)  
  7.         .open(name)?;  
  8.   
  9.     // 获取文件描述符  
  10.     let fd: i32 = file.as_raw_fd();  
  11.   
  12.     // 返回一个 File 实例  
  13.     Ok(File {  
  14.         name: ManuallyDrop::new(name.to_string()),  
  15.         fd: ManuallyDrop::new(fd),  
  16.     })  
  17. }
复制代码


  • open方法的参数没有涉及到File结构体,以是接收参数部门不用修改
  • open方法的返回值涉及到了File,每个字段都得用ManuallyDrop::new来传值
其次是close方法:
  1. /// 一个显式的析构函数,关闭文件并返回任何错误  
  2. fn close(mut self) -> Result<(), Error> {  
  3.     // 使用std::mem::replace将name字段替换为一个空字符串,把原来的值给name  
  4.     let name =   
  5.         std::mem::replace(&mut self.name, ManuallyDrop::new("".to_string()));  
  6.       
  7.     // 使用std::mem::replace将fd字段替换为一个无效的值(-1),把原来的值给fd
  8.     let fd =   
  9.         std::mem::replace(&mut self.fd, ManuallyDrop::new(0));  
  10.       
  11.     // 打印  
  12.     println!("Closing file: {:?} with fd: {:?}", name, fd);  
  13.       
  14.     // 使用 FromRawFd 将 fd 转换回 File   
  15.     let file: std::fs::File = unsafe {  
  16.         std::os::unix::io::FromRawFd::from_raw_fd(*fd) //这里fd要先解引用  
  17.     };  
  18.   
  19.     // 刷新文件数据到磁盘  
  20.     file.sync_all()?;  
  21.   
  22.     // 将文件截断为 0 字节  
  23.     file.set_len(0)?;  
  24.   
  25.     // 再次刷新文件  
  26.     file.sync_all()?;  
  27.   
  28.     // 丢弃文件实例,它会自动关闭文件  
  29.     drop(file);  
  30.   
  31.     // 返回成功  
  32.     Ok(())  
  33. }
复制代码


  • 使用std::mem::replace将name字段替换为一个空字符串,把原来的值给name
  • 使用std::mem::replace将fd字段替换为一个无效的值(-1),把原来的值给fd
  • std:s::unix::io::FromRawFd::from_raw_fd(*fd)中参数要先解引用,也就是写*fd
步骤3:修改Drop trait的实现

  1. fn drop(&mut self) {  
  2.     // 使用ManuallyDrop::take取出name字段的值,并检查是否是空字符串  
  3.     let name = unsafe { ManuallyDrop::take(&mut self.name) };  
  4.       
  5.     // 使用ManuallyDrop::take取出fd字段的值,并检查是否是无效的值  
  6.     let fd = unsafe { ManuallyDrop::take(&mut self.fd) };  
  7.       
  8.     //打印  
  9.     println!("Dropping file: {:?} (fd: {:?})", name, fd);  
  10.       
  11.     // 如果fd字段不是无效的值,说明文件还没有被关闭或丢弃,需要执行丢弃操作  
  12.     if fd != -1 || !name.is_empty() {  
  13.             let file = unsafe { std::fs::File::from_raw_fd(fd) };  
  14.             // 丢弃  
  15.             drop(file);  
  16.         }
  17. }
复制代码


  • 使用ManuallyDrop::take取出name和fd字段的值,并查抄是否是空字符串或无效的值
  • 如果fd字段不是无效的值(不是-1),或是name字段不是空字符串,就说明文件还没有被关闭或丢弃,需要实行丢弃操作
  • 其实这里不用两个判断条件(fd != -1 || !name.is_empty()),一个就够了,因为name和fd字段的值的变化是一起的,一个无效就代表着整个结构体都还未被清理。
步骤4:微修主函数

  1. fn main() {
  2.         // ...前文无修改,已省略
  3.         // 打印文件名和 fd (这里需要修改)  
  4.         println!("File name: {}, fd: {}", *file.name, *file.fd);
  5.         // ...后文无修改,已省略
  6. }
复制代码


  • 使用解引用来打印值
团体代码

  1. use std::os::fd::{AsRawFd, FromRawFd};  use std::fs::{File as StdFile, OpenOptions, metadata};  use std::io::Error;  use std::mem::ManuallyDrop;    /// 一个表示文件句柄的类型  
  2. struct File {  
  3.     /// 文件名  
  4.     name: ManuallyDrop<String>,  
  5.     /// 文件描述符  
  6.     fd: ManuallyDrop<i32>,  
  7. }
  8.     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方案也是非常好的。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

水军大提督

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表