【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方法:

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结构体的字段

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

步骤2:修改File上的方法

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

首先是open方法:

/// 一个构造函数,打开一个文件并返回一个 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方法:

/// 一个显式的析构函数,关闭文件并返回任何错误  
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方法需要修改:

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:微修主函数

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

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

整体代码

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>

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

步骤2:修改File上的方法

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

首先是open方法:

/// 一个构造函数,打开一个文件并返回一个 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方法:

/// 一个显式的析构函数,关闭文件并返回任何错误  
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的实现

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:微修主函数

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

整体代码

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>

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

步骤2:修改File上的方法

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

首先是open方法:

/// 一个构造函数,打开一个文件并返回一个 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方法:

/// 一个显式的析构函数,关闭文件并返回任何错误  
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的实现

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:微修主函数

fn main() {// ...前文无修改,已省略// 打印文件名和 fd (这里需要修改)  println!("File name: {}, fd: {}", *file.name, *file.fd);// ...后文无修改,已省略
}
  • 使用解引用来打印值

整体代码

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方案也是非常好的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/23673.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

git 克隆及拉取github项目到本地微信开发者工具,微信开发者工具通过git commit、git push上传代码到github仓库

git 克隆及拉取github项目到本地微信开发者工具&#xff0c;微信开发者工具通过git commit、git push上传代码到github仓库 git 克隆及拉取github项目到本地 先在自己的用户文件夹新建一个项目文件夹&#xff0c;取名为项目名 例如这样 C:\Users\HP\yzj-再打开一个终端页面&…

gitlab初次登录为什么登不上去

今天又写了一次gitlab安装后&#xff0c;第一次登录的问题。 gitlab工作笔记_gitlab默认用户名密码-CSDN博客 因为又掉这个坑里了。 # 为什么第一次登录这么难&#xff1f; 第一是因为gitlab启动的时间很长&#xff0c;有时候以为装错了。 第二是初始密码&#xff0c;如果…

华为认证考试证书下载步骤(纸质+电子版)

华为考试证书可以通过官方渠道下载相应的电子证书&#xff0c;部分高级认证如HCIE还支持申请纸质证书。 一、华为电子版证书申请步骤如下&#xff1a; ①访问华为培训与认证网站 打开浏览器&#xff0c;登录华为培训与认证官方网站 ②登录个人账号 在网站首页&#xff0c;点…

【UCB CS 61B SP24】Lecture 11 - Inheritance 4: Iterators, Object Methods学习笔记

本文内容为集合&#xff08;Set&#xff09;的介绍与使用&#xff0c;并通过数组手动实现集合&#xff0c;接着介绍了迭代器&#xff0c;使用迭代器我们能够更方便地遍历集合中的元素。 1. Set 1.1 Set介绍与Java实现类的使用 集合&#xff08;Set&#xff09;是一种常见的数…

sessionStorage问题的思考和解决

通过前端访问成功&#xff0c;直接访问后端接口失败。思考的过程、问题的解决Session和sessionStorage 通过前端访问成功&#xff0c;直接访问后端接口失败。 做黑马点评的使用Redis代替Session实现短信登录的功能时&#xff0c;遇到了一个问题&#xff1a; 就是我设计好代码后…

YOLO11改进-模块-引入混合结构模块Mix Structure Block 提高多尺度、小目标

在图像去雾领域&#xff0c;传统的基于卷积神经网络&#xff08;CNN&#xff09;和 Transformer 的方法存在局限性。CNN 方法大多存在感受野不足的问题&#xff0c;限制了单个像素在神经网络中的参考范围&#xff0c;部分考虑大感受野的 CNN 方法又忽略了图像的多尺度特性&…

MySQL主从架构

MySQL主从架构 MySQL REPLICATION 在实际生产环境中&#xff0c;如果对数据库的读和写都在一个数据库服务器中操作。无论是在安全性、高可用性&#xff0c;还是高并发等各个方面都是完全不能满足实际需求的&#xff0c;因此&#xff0c;一般来说都是通过主从复制&#xff08;…

6层高速PCB设计入门第1~10讲

第一讲 课程介绍 无痛入门&#xff01;6层高速PCB设计&#xff01;_哔哩哔哩_bilibili 第二讲 逻辑派原理图分析 开发板资料文档&#xff1a;https://wiki.lckfb.com/zh-hans/fpga-ljpi/ 最需要注意的信号就是FPGA与DDR3、HDMI交互的信号&#xff0c;其次是GD32读写TF Card的…

Mesh自组网技术及应用

前言&#xff1a; Mesh自组网随着无线技术发展&#xff0c;在消费领域最近比较有热度。当然应用的场景不限于普通消费领域&#xff0c;在工业、军事领域被也是越来越重要。 一、什么是无线Mesh技术 1.1 无线自组网概念 无线Mesh是一种智能、自组织、多跳、移动、对等、去中心…

Python游戏编程之赛车游戏6-3

1 “敌人”汽车类的创建 在创建玩家汽车类之后&#xff0c;接下来创建“敌人”汽车类。“敌人”汽车类与玩家类一样&#xff0c;也是包含两个方法&#xff0c;一个是__init__()&#xff0c;另一个是move()。 1.1 __init__()方法 “敌人”汽车类的__init__()方法代码如图1所示…

垂类大模型微调(二):使用LLaMA-Factory

上一篇博文和大家一起安装了LLaMA-Factory工具,并下载了大模型在上面进行了简单的加载和推理,今天尝试通过LoRa技术对大模型进行微调; 一、训练集准备 1.1 介绍训练集结构 这里演示对Qwen2.5-0.5B-Instruct-GPTQ-Int4模型进行LoRA微调, 大家可以根据垂类大模型微调(一)…

什么是MySql的主从复制(主从同步)?

主页还有其他面试题总结&#xff0c;有需要的可以去看一下&#xff0c;喜欢的就留个三连再走吧~ 1.什么是MySql的主从复制原理&#xff1f; 主从复制的核心就是二进制binlog&#xff08;DDL&#xff08;数据定义语言&#xff09;语句和DML&#xff08;数据操纵语言&#xff09…

坐标变换及视图变换和透视变换(相机透视模型)

文章目录 2D transformationScaleReflectionShear&#xff08;切变&#xff09;Rotation around originTranslationReverse变换顺序复杂变换的分解 齐次坐标&#xff08;Homogenous Coordinates&#xff09;3D transformationScale&TranslationRotation Viewing / Camera t…

文字语音相互转换

目录 1.介绍 2.思路 3.安装python包 3.程序&#xff1a; 4.运行结果 1.介绍 当我们使用一些本地部署的语言模型的时候&#xff0c;往往只能进行文字对话&#xff0c;这一片博客教大家如何实现语音转文字和文字转语音&#xff0c;之后接入ollama的模型就能进行语音对话了。…

Unity Shader 学习13:屏幕后处理 - 使用高斯模糊的Bloom辉光效果

目录 一、基本的后处理流程 - 以将画面转化为灰度图为例 1. C#调用shader 2. Shader实现效果 二、Bloom辉光效果 1. 主要变量 2. Shader效果 &#xff08;1&#xff09;提取较亮区域 - pass1 &#xff08;2&#xff09;高斯模糊 - pass2&3 &#xff08;3&#xff…

PING命令TTL解析

在 ping 命令中&#xff0c;TTL&#xff08;Time to Live&#xff0c;生存时间&#xff09; 是 IP 数据包的核心字段之一&#xff0c;用于控制数据包在网络中的生命周期。以下是针对 TTL 的简明解析&#xff1a; 1. TTL 的核心作用 防循环机制&#xff1a;TTL 是一个计数器&a…

Linux 第三次脚本作业

源码编译安装httpd 2.4&#xff0c;提供系统服务管理脚本并测试&#xff08;建议两种方法实现&#xff09; 一、第一种方法 1、把 httpd-2.4.63.tar.gz 这个安装包上传到你的试验机上 2、 安装编译工具 (俺之前已经装好了&#xff09; 3、解压httpd包 4、解压后的httpd包的文…

(七)趣学设计模式 之 适配器模式!

目录 一、 啥是适配器模式&#xff1f;二、 为什么要用适配器模式&#xff1f;三、 适配器模式的实现方式1. 类适配器模式&#xff08;继承插座 &#x1f468;‍&#x1f469;‍&#x1f467;‍&#x1f466;&#xff09;2. 对象适配器模式&#xff08;插座转换器 &#x1f50c…

【NLP】注意力机制

目录 一、认识注意力机制 1.1 常见注意力计算规则 1.2 注意力机制的作用 1.3 注意力机制代码实现 二、注意力机制原理 2.1 attention计算过程 2.2 attention的计算逻辑 2.3 有无attention模型对比 2.3.1 无attention机制的模型 2.3.2 有attention机制的模型 三、Se…

Spring Boot 整合 Druid 并开启监控

文章目录 1. 引言2. 添加依赖3. 配置数据源4. 开启监控功能5. 自定义 Druid 配置&#xff08;可选&#xff09;6. 访问监控页面7. 注意事项8. 总结 Druid 是一个由阿里巴巴开源的高性能数据库连接池&#xff0c;它不仅提供了高效的连接管理功能&#xff0c;还自带了强大的监控…