当前位置: 首页 > article >正文

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

相关文章:

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

喜欢的话别忘了点赞、收藏加关注哦&#xff08;加关注即可阅读全文&#xff09;&#xff0c;对接下来的教程有兴趣的可以关注专栏。谢谢喵&#xff01;(&#xff65;ω&#xff65;) 说句题外话&#xff0c;这篇文章一共5721个字&#xff0c;是我截至目前写的最长的一篇文章&a…...

【复习】Redis

数据结构 Redis常见的数据结构 String&#xff1a;缓存对象Hash&#xff1a;缓存对象、购物车List&#xff1a;消息队列Set&#xff1a;点赞、共同关注ZSet&#xff1a;排序 Zset底层&#xff1f; Zset底层的数据结构是由压缩链表或跳表实现的 如果有序集合的元素 < 12…...

STM32使用NRF2401进行数据传送

NRF2401是一款由Nordic Semiconductor公司生产的单片射频收发芯片&#xff0c;以下是关于它的详细介绍&#xff1a; 一、主要特点 工作频段&#xff1a;NRF2401工作于2.4~2.5GHz的ISM&#xff08;工业、科学和医疗&#xff09;频段&#xff0c;该频段无需申请即可使用&#xf…...

Fetch API 与 XMLHttpRequest:深入剖析异步请求的利器

Hi&#xff0c;我是布兰妮甜 &#xff01;在现代 Web 开发中&#xff0c;异步通信是实现动态和交互式用户体验的基石。XMLHttpRequest (XHR) 作为老牌劲旅&#xff0c;曾一度统治着这一领域。然而&#xff0c;随着 Fetch API 的横空出世&#xff0c;开发者们迎来了一个更现代、…...

如何生成traceid以及可视化展示

根据你的需求&#xff0c;以下是一些可以生成唯一 traceId 并用于分布式链路追踪的工具和项目&#xff0c;这些项目支持生成唯一的 traceId&#xff0c;并将其用于日志记录和分布式追踪&#xff1a; 1. OpenTelemetry OpenTelemetry 是一个开源的观测框架&#xff0c;支持生成…...

【LeetCode541】反转字符串

题目描述 给定一个字符串 s 和一个整数 k&#xff0c;从字符串开头算起&#xff0c;每计数至 2k 个字符&#xff0c;就反转这 2k 字符中的前 k 个字符。 如果剩余字符少于 k 个&#xff0c;则将剩余字符全部反转。 如果剩余字符小于 2k 但大于或等于 k 个&#xff0c;则反转前…...

DeepSeek、微信、硅基流动、纳米搜索、秘塔搜索……十种不同方法实现DeepSeek使用自由

为了让大家实现 DeepSeek 使用自由&#xff0c;今天分享 10 个畅用 DeepSeek 的平台。 一、官方满血版&#xff1a;DeepSeek官网与APP 首推&#xff0c;肯定是 DeepSeek 的官网和 APP&#xff0c;可以使用满血版 R1 和 V3 模型&#xff0c;以及联网功能。 网址&#xff1a; htt…...

C++:pthread线程分离和线程属性

在 C 的多线程编程中&#xff0c;pthread 库提供了强大的功能来管理线程。其中&#xff0c;线程分离和线程属性是两个重要的概念&#xff0c;它们对于优化线程的行为和资源管理有着关键作用。 线程分离 1.1 什么是线程分离 在 pthread 库中&#xff0c;线程有两种状态&#…...

Orange 开源项目 - 集成阿里云大模型

1 阿里云的大模型服务平台百炼 阿里云的大模型服务平台百炼是一站式的大模型开发及应用构建平台。不论是开发者还是业务人员&#xff0c;都能深入参与大模型应用的设计和构建。您可以通过简单的界面操作&#xff0c;在5分钟内开发出一款大模型应用&#xff0c;或在几小时内训练…...

Docker 搭建 MySQL 数据库

Docker 搭建 MySQL 数据库 前言一、准备工作二、设置 MySQL 容器的目录结构三、配置 MySQL 容器四、自定义 MySQL 配置五、端口配置&#xff1a;Host 网络模式 vs Port 映射模式六、检查 MySQL 容器状态七、连接到 MySQL 容器八、备份与恢复总结 前言 在本篇文章中&#xff0c…...

使用 Docker 部署 Flask 应用

使用 Docker 部署 Flask 应用 一、引言 在现代软件开发中,应用的部署和环境管理是至关重要的环节。传统的部署方式常常会遇到 “在我机器上能运行,在你机器上不行” 的问题,而 Docker 的出现很好地解决了这个痛点。Docker 是一个用于开发、部署和运行应用程序的开放平台,…...

公开整理-最新中国城市统计NJExcel+PDF版本(1985-2024年)

数据简介&#xff1a;《中国城市统计NJ》从1985年开始&#xff0c;本NJ内容共分四个部分:第一部分是全国城市行政区划,列有不同区域、不同级别的城市分布情况;第二、三部分分别是地级以上城市统计资料和县级城市统计资料,具体包括人口、劳动力及土地资源、综合经济、工业、交通…...

python绘图之swarmplot分布散点图

swarmplot 是 Seaborn 提供的一种用于展示分类数据分布的散点图。它的主要作用是将数据点按照分类变量&#xff08;通常是离散变量&#xff09;进行分组&#xff0c;并在每个分类中以一种非重叠的方式展示数据点的位置。这种可视化方式可以帮助我们直观地理解数据在不同分类下的…...

KubeSphere平台安装

KubeSphere简介 KubeSphere 是一款功能强大的容器管理平台&#xff0c;以下是其简介&#xff1a; 1&#xff09;基本信息 开源项目&#xff1a;基于 Apache-2.0 授权协议开源&#xff0c;由 Google Go、Groovy、HTML/CSS 和 Shell 等多种编程语言开发。基础架构&#xff1a;…...

Claude 3.7 Sonnet 泄露,Anthropic 最先进 AI 模型即将在 AWS Bedrock 上首次亮相

(图片&#xff1a;AWS) Anthropic 旗下先进的 AI 模型 Claude 3.7 Sonnet 似乎即将发布。业界预计&#xff0c;亚马逊可能会在2025年2月26日的活动中公布相关消息。泄露的信息表明&#xff0c;该模型将托管于 AWS Bedrock 平台&#xff0c;该平台以提供尖端 AI 模型访问而闻名…...

ONNX转RKNN的环境搭建和部署流程

将ONNX模型转换为RKNN模型的过程记录 工具准备 rknn-toolkit:https://github.com/rockchip-linux/rknn-toolkit rknn-toolkit2:https://github.com/airockchip/rknn-toolkit2 rknn_model_zoo:https://github.com/airockchip/rknn_model_zoo ultralytics_yolov8:https://github…...

解决鼠标唤醒关屏状态下的笔记本

以下是通过计划任务和PowerShell实现鼠标唤醒控制的全网独家解决方案,基于Windows事件触发机制,结合设备管理API实现精准控制,最终实现仅需通过win+l锁定屏幕,再关闭屏幕,既不会出现唤醒笔记问的问题: 一、技术原理深度解析 1. 事件触发机制 Windows安全子系统在锁屏/…...

MongoDB 复制(副本集)

MongoDB 复制(副本集) 引言 MongoDB是一个高性能、可扩展、易于使用的文档存储系统。它以JSON-like的文档存储结构&#xff0c;支持灵活的数据模型。在分布式系统中&#xff0c;为了提高数据可用性和系统稳定性&#xff0c;常常需要实现数据的备份和冗余。MongoDB提供了副本集…...

聊聊 FocusSearch/focus_mcp_sql:Text2SQL 的新玩法

聊聊 FocusSearch/focus_mcp_sql&#xff1a;Text2SQL 的新玩法 最近在 GitHub 上逛的时候&#xff0c;发现了一个挺有意思的项目——FocusSearch/focus_mcp_sql。作为一个对 Text2SQL 有点小研究的前端码农&#xff0c;我忍不住想和大家聊聊这个工具。它不像那些常见的基于大…...

Linux红帽:RHCSA认证知识讲解(二)配置网络与登录本地远程Linux主机

Linux红帽&#xff1a;RHCSA认证知识讲解&#xff08;二&#xff09;配置网络与登录本地远程Linux主机 前言一、使用命令行&#xff08;nmcli 命令&#xff09;配置网络&#xff0c;配置主机名第一步第二步修改主机名称 二、使用图形化界面&#xff08;nmtui 命令&#xff09;配…...

C#开发——ConcurrentDictionary集合

ConcurrentDictionary<TKey, TValue> 是 C# 中一个专为多线程场景设计的线程安全字典集合&#xff0c;位于 System.Collections.Concurrent 命名空间中。它允许多个线程同时对字典进行读写操作&#xff0c;而无需额外的同步措施。 一、集合特征 此集合有如下特征…...

深入浅出ES6:现代JavaScript的基石

ES6&#xff08;ECMAScript 2015&#xff09;是JavaScript语言的一次重大更新&#xff0c;引入了许多新特性&#xff0c;使JavaScript更加强大、优雅和易于维护。这些特性已经成为现代JavaScript开发的基石&#xff0c;掌握它们对于任何JavaScript开发者都至关重要。本文将深入…...

小型字符级语言模型的改进方向和策略

小型字符级语言模型的改进方向和策略 一、回顾小型字符级语言模型的处理流程 前文我们已经从零开始构建了一个小型字符级语言模型,那么如何改进和完善我们的模型呢?有哪些改进的方向?我们先回顾一下模型的流程: 图1 小型字符级语言模型的处理流程 (1)核心模块交互过程:…...

一周学会Flask3 Python Web开发-Jinja2模板访问对象

锋哥原创的Flask3 Python Web开发 Flask3视频教程&#xff1a; 2025版 Flask3 Python web开发 视频教程(无废话版) 玩命更新中~_哔哩哔哩_bilibili 如果渲染模板传的是对象&#xff0c;如果如何来访问呢&#xff1f; 我们看下下面示例&#xff1a; 定义一个Student类 cla…...

vue 3D 翻页效果

<template><view class"swipe-container" touchstart"onTouchStart" touchmove"onTouchMove" touchend"onTouchEnd"><view class"page">初始页</view></view> </template><script&g…...

JSONL 是什么格式

JSONL&#xff08;JSON Lines&#xff09;格式是一种简洁的文件格式&#xff0c;它将每一行作为一个独立的JSON对象&#xff0c;每个对象之间通过换行符分隔。JSONL常用于处理大规模数据&#xff0c;特别是在日志文件、机器学习数据集或数据流应用中。 示例 假设我们有以下3条…...

npm : 无法加载文件 E:\ProgramFiles\Nodejs\npm.ps1,因为在此系统上禁止运行脚本。

这个错误是因为 Windows 系统的 PowerShell 执行策略 限制了脚本的运行。默认情况下&#xff0c;PowerShell 的执行策略是 Restricted&#xff0c;即禁止运行任何脚本。以下是解决该问题的步骤&#xff1a; 1. 检查当前执行策略 打开 PowerShell&#xff08;管理员权限&#x…...

pycharm 调试 debug 进入 remote_sources

解决办法1&#xff1a; pycharm函数跳转到remote_sources中的文件中_pycharm修改remotesource包存放地址-CSDN博客 file->settings->project structure将项目文件夹设为"Sources"&#xff08;此时文件夹会变为蓝色&#xff09;。 解决方法2 Debug:使用Pychar…...

测试面试题:以一个登录窗口为例,设计一下登录界面测试的思路和方法

在测试登录窗口时,可以从 表单测试、 逻辑判断和 业务流程三个方面设计测试思路和方法。以下是一个详细的测试方案: 1. 表单测试 表单测试主要关注输入框、按钮等UI元素的正确性和用户体验。 测试点: 输入框测试 用户名和密码输入框是否正常显示。输入框是否支持预期的字符类…...

[特殊字符] 蓝桥杯 Java B 组 之最小生成树(Prim、Kruskal) 并查集应用

Day 3&#xff1a;最小生成树&#xff08;Prim、Kruskal&#xff09; & 并查集应用 &#x1f4d6; 一、最小生成树&#xff08;MST&#xff09;简介 最小生成树&#xff08;Minimum Spanning Tree, MST&#xff09; 是一个 无向连通图 的 最小代价子图&#xff0c;它包含 …...