【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::replace
将name
字段替换为一个空字符串,把原来的值给name
- 使用
std::mem::replace
将fd
字段替换为一个无效的值(-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
取出name
和fd
字段的值,并检查是否是空字符串或无效的值 - 如果
fd
字段不是无效的值(不是-1),或是name
字段不是空字符串,就说明文件还没有被关闭或丢弃,需要执行丢弃操作 - 其实这里不用两个判断条件(
fd != -1 || !name.is_empty()
),一个就够了,因为name
和fd
字段的值的变化是一起的,一个无效就代表着整个结构体都还未被清理。
步骤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种解决方案
喜欢的话别忘了点赞、收藏加关注哦(加关注即可阅读全文),对接下来的教程有兴趣的可以关注专栏。谢谢喵!(・ω・) 说句题外话,这篇文章一共5721个字,是我截至目前写的最长的一篇文章&a…...

LabVIEW Browser.vi 库说明
browser.llb 库位于C:\Program Files (x86)\National Instruments\LabVIEW 2019\vi.lib\Platform目录,它是 LabVIEW 平台下用于与网络浏览器相关操作的重要库。该库为 LabVIEW 开发者提供了一系列工具,用于实现网页浏览控制、网页数据获取与交互等功能&a…...
promise的方法有哪些?【JavaScript】
Promise对象在JavaScript中是一种处理异步操作的方式,它提供了一组方法来管理和控制异步操作的结果。以下是一些常用的Promise方法: 以下是对 constructor(executor)、then(onFulfilled, onRejected)、catch(onRejected)、 finally(onFin…...
基于模仿学习(IL)的端到端自动驾驶发展路径
基于模仿学习(IL)的端到端自动驾驶发展路径 1. 核心论文解析 (1) UniAD:感知-规划一体化 核心思想:首次提出将感知任务(如目标检测、车道线识别、轨迹预测)与规划任务集成到统一的端到端框架中ÿ…...
第1篇:SOLR 简介与源码环境搭建
第1篇:SOLR 简介与源码环境搭建 1.1 SOLR 是什么? Apache SOLR 是一个基于 Apache Lucene 的高性能开源搜索平台。它不仅继承了 Lucene 强大的全文搜索能力,还通过封装和扩展,提供了企业级的功能,比如分布式搜索(SolrCloud)、RESTful API、动态 Schema 管理等。自 200…...
Docker 搭建 Redis 数据库
Docker 搭建 Redis 数据库 前言一、准备工作二、创建 Redis 容器的目录结构三、启动 Redis 容器1. 通过 redis.conf 配置文件设置密码2. 通过 Docker 命令中的 requirepass 参数设置密码 四、Host 网络模式与 Port 映射模式五、检查 Redis 容器状态六、访问 Redis 服务总结 前言…...
MySQL 连表查询:原理、语法与优化
目录 引言 什么是连表查询? 连表查询的类型 1. 内连接(INNER JOIN) 2. 左连接(LEFT JOIN) 3. 右连接(RIGHT JOIN) 4. 全连接(FULL JOIN) 5. 交叉连接(…...
实战技巧:如何快速提高网站收录的权威性?
快速提高网站收录的权威性是一个系统性的工作,涉及内容质量、网站结构、外部链接、用户体验等多个方面。以下是一些实战技巧,可以帮助你快速提升网站收录的权威性: 一、提升内容质量 原创性: 确保网站内容具备高质量与原创性&a…...

vue语法v-model例子单选题和多选题
<template><!-- 单选框 --><input type"radio" v-model"danxuan" value"a"><label for"a">a</label><input type"radio" v-model"danxuan" value"b"><label fo…...

计算机网络面试知识点总结
目录 1. 计算机网络的基本知识点2. OSI 七层模型3. TCP/IP 四层模型4. TCP 和 UDP4.1 TCP 协议4.2 TCP 流量控制4.3 TCP 拥塞控制4.4 TCP 三次握手4.5 TCP 四次挥手4.6 TCP 粘包问题4.7 TCP Socket交互流程4.8 UDP 协议以及和 TCP 协议的不同 5. HTTP协议5.1 HTTP 请求方法以及…...

JVM生产环境问题定位与解决实战(二):JConsole、VisualVM到MAT的高级应用
生产问题定位指南:几款必备的可视化工具 引言 在上一篇文章中,详细的介绍了JDK自带的一系列命令行工具,,如jps、jmap、jstat、jstack以及jcmd等,这些工具为排查和诊断Java虚拟机(JVM)问题提供…...

c++入门-------命名空间、缺省参数、函数重载
C系列 文章目录 C系列前言一、命名空间二、缺省参数2.1、缺省参数概念2.2、 缺省参数分类2.2.1、全缺省参数2.2.2、半缺省参数 2.3、缺省参数的特点 三、函数重载3.1、函数重载概念3.2、构成函数重载的条件3.2.1、参数类型不同3.2.2、参数个数不同3.2.3、参数类型顺序不同 前言…...

Lua语言入门(自用)
全局与非全局 在lua语言当中没有被local表示的是全局变量 反之则是本地变量(仅仅作用在某个文件,函数,或者代码块) 下面是实例代码和运行结果 --hello.luaA 10;--这样就是全局变量,然后这个编译器如果是大写就是默认的全局变量 local b 3;--这样就是局部变量--reference.…...
大数据开发平台的框架
根据你的需求,以下是从 GitHub 推荐的 10 个可以实现大数据开发平台的项目: 1. Apache Spark Apache Spark 是一个开源的分布式计算框架,适用于大规模数据处理和分析。它提供了强大的数据处理能力,支持实时数据处理、机器学习和…...

X86_64位下的GS寄存器
在 64 位模式下,段机制通常(但不是完全)被禁用,从而创建一个平坦的 64 位线性地址空间。处理器将 CS、DS、ES、SS 的段基地址视为零,从而创建一个与有效地址相等的线性地址。FS 和 GS 段是例外。这些段寄存器ÿ…...
#6【TIP2023】Dif-Fusion
📜 Dif-Fusion: Toward High Color Fidelity in Infrared and Visible Image Fusion With Diffusion Models 🍸 源码:https://github.com/GeoVectorMatrix/Dif-Fusion 🍺 前向扩散过程 前向过程看成是逐步加噪的Markov链,在每一个时间步 t t t ,...
科普:“git“与“github“
Git与GitHub的关系可以理解为:Git是一种软件工具,而GitHub则是一个在线平台,它们是“一家子”。二者的关联最直接体现在你通过Git在GitHub仓库中clone软件包到你的机器中来。 具体来说: 一、Git 定义:Git是一个开源的…...
unplugin-vue-components,unplugin-auto-import,借用ai去学习
好的,我现在需要帮助用户理解他们提供的这段Vite配置代码。用户之前已经询问过关于ElementPlusResolver的导入问题,现在他们展示了在vite.config.ts中使用该解析器的具体配置。我需要逐步分析这段代码,并解释每个部分的作用。 首先ÿ…...
蓝桥杯 2013 省 B 翻硬币
题目背景 小明正在玩一个“翻硬币”的游戏。 题目描述 桌上放着排成一排的若干硬币。我们用 * 表示正面,用 o 表示反面(是小写字母,不是零),比如可能情形是 **oo***oooo,如果同时翻转左边的两个硬币&…...
【硬件设计】DDR3、DDR4、DDR5、DDR6性能对比与硬件设计要点
目录 一、各代DDR技术核心性能指标对比 二、各代DDR技术特性详解 三、硬件设计通用原则与差异化需求 四、技术演进趋势总结 一、各代DDR技术核心性能指标对比 指标DDR3DDR4DDR5DDR6(预测)发布时间2007年2014年2020年预计2026年5传输速率800-1600 MT…...

盘古信息PCB行业解决方案:以全域场景重构,激活智造新未来
一、破局:PCB行业的时代之问 在数字经济蓬勃发展的浪潮中,PCB(印制电路板)作为 “电子产品之母”,其重要性愈发凸显。随着 5G、人工智能等新兴技术的加速渗透,PCB行业面临着前所未有的挑战与机遇。产品迭代…...
MySQL 隔离级别:脏读、幻读及不可重复读的原理与示例
一、MySQL 隔离级别 MySQL 提供了四种隔离级别,用于控制事务之间的并发访问以及数据的可见性,不同隔离级别对脏读、幻读、不可重复读这几种并发数据问题有着不同的处理方式,具体如下: 隔离级别脏读不可重复读幻读性能特点及锁机制读未提交(READ UNCOMMITTED)允许出现允许…...

转转集团旗下首家二手多品类循环仓店“超级转转”开业
6月9日,国内领先的循环经济企业转转集团旗下首家二手多品类循环仓店“超级转转”正式开业。 转转集团创始人兼CEO黄炜、转转循环时尚发起人朱珠、转转集团COO兼红布林CEO胡伟琨、王府井集团副总裁祝捷等出席了开业剪彩仪式。 据「TMT星球」了解,“超级…...
vue3 字体颜色设置的多种方式
在Vue 3中设置字体颜色可以通过多种方式实现,这取决于你是想在组件内部直接设置,还是在CSS/SCSS/LESS等样式文件中定义。以下是几种常见的方法: 1. 内联样式 你可以直接在模板中使用style绑定来设置字体颜色。 <template><div :s…...

DBAPI如何优雅的获取单条数据
API如何优雅的获取单条数据 案例一 对于查询类API,查询的是单条数据,比如根据主键ID查询用户信息,sql如下: select id, name, age from user where id #{id}API默认返回的数据格式是多条的,如下: {&qu…...
解决本地部署 SmolVLM2 大语言模型运行 flash-attn 报错
出现的问题 安装 flash-attn 会一直卡在 build 那一步或者运行报错 解决办法 是因为你安装的 flash-attn 版本没有对应上,所以报错,到 https://github.com/Dao-AILab/flash-attention/releases 下载对应版本,cu、torch、cp 的版本一定要对…...
Spring Boot+Neo4j知识图谱实战:3步搭建智能关系网络!
一、引言 在数据驱动的背景下,知识图谱凭借其高效的信息组织能力,正逐步成为各行业应用的关键技术。本文聚焦 Spring Boot与Neo4j图数据库的技术结合,探讨知识图谱开发的实现细节,帮助读者掌握该技术栈在实际项目中的落地方法。 …...

中医有效性探讨
文章目录 西医是如何发展到以生物化学为药理基础的现代医学?传统医学奠基期(远古 - 17 世纪)近代医学转型期(17 世纪 - 19 世纪末)现代医学成熟期(20世纪至今) 中医的源远流长和一脉相承远古至…...

手机平板能效生态设计指令EU 2023/1670标准解读
手机平板能效生态设计指令EU 2023/1670标准解读 以下是针对欧盟《手机和平板电脑生态设计法规》(EU) 2023/1670 的核心解读,综合法规核心要求、最新修正及企业合规要点: 一、法规背景与目标 生效与强制时间 发布于2023年8月31日(OJ公报&…...
Docker拉取MySQL后数据库连接失败的解决方案
在使用Docker部署MySQL时,拉取并启动容器后,有时可能会遇到数据库连接失败的问题。这种问题可能由多种原因导致,包括配置错误、网络设置问题、权限问题等。本文将分析可能的原因,并提供解决方案。 一、确认MySQL容器的运行状态 …...