喜欢的话别忘了点赞、收藏加关注哦(加关注即可阅读全文),对接下来的教程有兴趣的可以关注专栏。谢谢喵!(=^・ω・^=)
说句题外话,这篇文章一共5721个字,是我截至目前写的最长的一篇文章,看我这么努力,还不点赞、收藏加关注?
2.8.1. 显式析构函数的问题
添加显式析构函数时会遇到问题:
- 当某一类型实现了
Drop
,在析构函数中无法将该类型的任何字段移出。因为在显式析构函数运行后,drop()
仍会被调用,它接受&mut self
,要求self
的所有部分都没被移动。 Drop
接收的是&mut self
而不是self
,因此Drop
无法实现简单地调用显式析构函数并忽略其结果(因为Drop
不拥有self
)
以上一篇文章的例子为基础,如果我们加上既实现了Drop
trait,又写了close
方法:
rust
use std::os::fd::AsRawFd;
use std::fs::{File as StdFile, OpenOptions, metadata};
use std::io::Error;
/// 一个表示文件句柄的类型
struct File {
/// 文件名
name: String,
/// 文件描述符
fd: i32,
}
impl File {
/// 一个构造函数,打开一个文件并返回一个 File 实例
fn open(name: &str) -> Result<File, Error> {
// 使用 OpenOptions 打开文件,具备读写权限
let file: StdFile = OpenOptions::new()
.read(true)
.write(true)
.open(name)?;
// 获取文件描述符
let fd: i32 = file.as_raw_fd();
// 返回一个 File 实例
Ok(File {
name: name.to_string(),
fd,
})
}
/// 一个显式的析构函数,关闭文件并返回任何错误
fn close(self) -> Result<(), Error> {
// 使用 FromRawFd 将 fd 转换回 File
let file: std::fs::File = unsafe {
std::os::unix::io::FromRawFd::from_raw_fd(self.fd)
};
// 刷新文件数据到磁盘
file.sync_all()?;
// 将文件截断为 0 字节
file.set_len(0)?;
// 再次刷新文件
file.sync_all()?;
// 丢弃文件实例,它会自动关闭文件
drop(file);
// 返回成功
Ok(())
}
}
//实现drop trait
impl Drop for File {
fn drop(&mut self) {
let _ = self.close(); //调用close方法来丢弃
println!("File dropped");
}
}
fn main() {
// 创建一个名为 "test.txt" 的文件,并写入一些内容
std::fs::write("test.txt", "Hello, world!").unwrap();
// 打开文件并获取 File 实例
let file: File = File::open("test.txt").unwrap();
// 打印文件名和 fd
println!("File name: {}, fd: {}", file.name, file.fd);
// 关闭文件并处理任何错误
match file.close() {
Ok(()) => println!("File closed successfully"),
Err(e) => println!("Error closing file: {}", e),
}
// 检查关闭后的文件大小
let metadata = metadata("test.txt").unwrap();
println!("File size: {} bytes", metadata.len());
}
输出:
error[E0507]: cannot move out of `*self` which is behind a mutable reference
--> src/main.rs:59:17
|
59 | let _ = self.close(); //调用close方法来丢弃
| ^^^^ ------- `*self` moved due to this method call
| |
| move occurs because `*self` has type `File`, which does not implement the `Copy` trait
|
note: `File::close` takes ownership of the receiver `self`, which moves `*self`
--> src/main.rs:33:14
|
33 | fn close(self) -> Result<(), Error> {
| ^^^^
note: if `File` implemented `Clone`, you could clone the value
--> src/main.rs:6:1
|
6 | struct File {
| ^^^^^^^^^^^ consider implementing `Clone` for this type
...
59 | let _ = self.close(); //调用close方法来丢弃
| ---- you could clone this value
报错信息显示无法从*self
中移出值,因为它位于&mut self
后面。
2.8.2. 解决方案
首先需要说明的是没有完美的解决方案,我们只能尽力弥补。
解决方案1:把结构体包装Option<T>
里然后再套一层结构体
我们可以将顶层方案作为包装了Option<T>
的新类型,这样Option<T>
内部持有一个类型,这个类型包含所有的字段。
这个时候我们就需要两个析构函数,外边一个里面一个。在这两个析构函数中使用Option::take
函数来获取数据所有权从而移除值。
由于内部类型没有实现Drop
,所以你可以获取所有字段的所有权。
缺点:想在顶层类型上提供的所有方法,现在都必须添加通过Option<T>
这层壳来获取内部类型上字段的代码。
我们在上文的代码例基础上进行修改:
步骤1:改掉File
的定义,套一层壳
首先我们得把两个字段移到另一个结构体里,套在Option<T>
中作为File
结构体的字段
rust
/// 一个表示文件句柄的类型
struct InnerFile {
/// 文件名
name: String,
/// 文件描述符
fd: i32,
}
/// 给InnerFile套了一个壳
struct File {
/// 把InnerFile包装在Option<T>中
inner: Option<InnerFile>,
}
步骤2:修改File
上的方法
File
上有两个方法,我们都需要添加通过Option<T>
这层壳来获取内部类型上字段的代码。
首先是open
方法:
rust
/// 一个构造函数,打开一个文件并返回一个 File 实例
fn open(name: &str) -> Result<File, Error> {
// 使用 OpenOptions 打开文件,具备读写权限
let file: StdFile = OpenOptions::new()
.read(true)
.write(true)
.open(name)?;
// 获取文件描述符
let fd: i32 = file.as_raw_fd();
// 返回一个 File 实例
Ok(File {
inner: Some( InnerFile {
name: name.to_string(),
fd,
})
})
}
- 由于这个代码只有返回值设计了
File
结构体,所以也只有返回值需要改
接下来是close
方法:
rust
/// 一个显式的析构函数,关闭文件并返回任何错误
fn close(mut self) -> Result<(), Error> { // 记得self要变为mut self,否则take不了
// 使用模式匹配提取出字段的值
if let Some(inner) = self.inner.take() {
let name = inner.name;
let fd = inner.fd;
println!("Closing file: {} with fd: {}", name, fd);
// 使用 FromRawFd 将 fd 转换回 File
let file: std::fs::File = unsafe {
std::os::unix::io::FromRawFd::from_raw_fd(fd)
};
// 刷新文件数据到磁盘
file.sync_all()?;
// 将文件截断为 0 字节
file.set_len(0)?;
// 再次刷新文件
file.sync_all()?;
// 丢弃文件实例,它会自动关闭文件
drop(file);
// 返回成功
Ok(())
} else {
// 如果inner字段是None,说明文件已经被关闭或丢弃,返回错误
Err(Error::new(
std::io::ErrorKind::Other,
"File is already closed",
))
}
}
- 参数传进去之后先得通过模式匹配来获取字段的值
- 如果如果
inner
字段是None
,也就是模式匹配不成功的情况下,我们需要自己写一个错误返回
步骤3:修改Drop
trait的实现
Drop::drop
方法需要修改:
rust
fn drop(&mut self) {
// 使用模式匹配获取字段值
if let Some(inner) = self.inner.take() {
let name = inner.name;
let fd = inner.fd;
println!("Dropping file: {} (fd: {})", name, fd);
// 使用 FromRawFd 将 fd 转换回 File
let file: std::fs::File = unsafe {
std::os::unix::io::FromRawFd::from_raw_fd(fd)
};
// 丢弃file实例
drop(file);
} else {
// 如果inner字段是None的话,说明文件已经被丢弃或关闭,不做任何操作
}
}
- 参数传进去之后先得通过模式匹配来获取字段的值
- 如果
inner
字段是None
的话,说明文件已经被丢弃或关闭,不做任何操作
步骤4:微修主函数
主函数中需要提取字段值的地方需要修改:
rust
fn main() {
// ...前文无修改,已省略
// 打印文件名和 fd (这里需要修改)
println!("File name: {}, fd: {}",
file.inner.as_ref().unwrap().name,
file.inner.as_ref().unwrap().fd
);
// ...后文无修改,已省略
}
- 原始类型是
Option<InnerFile>
,调用.as_ref()
后变成Option<&InnerFile>
- 变成了
Option<&InnerFile>
再使用unwrap
提取出来的值就是引用而不是所有的值 file.inner
是一个Option<InnerFile>
类型。而直接访问Option
的值需要转移所有权或匹配处理(例如通过take()
或unwrap()
),这会销毁Option
的内部值,所以得要as_ref
整体代码
rust
use std::os::fd::AsRawFd;
use std::fs::{File as StdFile, OpenOptions, metadata};
use std::io::Error;
/// 一个表示文件句柄的类型
struct InnerFile {
/// 文件名
name: String,
/// 文件描述符
fd: i32,
}
/// 给InnerFile套了一个壳
struct File {
/// 把InnerFile包装在Option<T>中
inner: Option<InnerFile>,
}
impl File {
/// 一个构造函数,打开一个文件并返回一个 File 实例
fn open(name: &str) -> Result<File, Error> {
// 使用 OpenOptions 打开文件,具备读写权限
let file: StdFile = OpenOptions::new()
.read(true)
.write(true)
.open(name)?;
// 获取文件描述符
let fd: i32 = file.as_raw_fd();
// 返回一个 File 实例
Ok(File {
inner: Some( InnerFile {
name: name.to_string(),
fd,
})
})
}
/// 一个显式的析构函数,关闭文件并返回任何错误
fn close(mut self) -> Result<(), Error> { // 记得self要变为mut self,否则take不了
// 使用模式匹配提取出字段的值
if let Some(inner) = self.inner.take() {
let name = inner.name;
let fd = inner.fd;
println!("Closing file: {} with fd: {}", name, fd);
// 使用 FromRawFd 将 fd 转换回 File
let file: std::fs::File = unsafe {
std::os::unix::io::FromRawFd::from_raw_fd(fd)
};
// 刷新文件数据到磁盘
file.sync_all()?;
// 将文件截断为 0 字节
file.set_len(0)?;
// 再次刷新文件
file.sync_all()?;
// 丢弃文件实例,它会自动关闭文件
drop(file);
// 返回成功
Ok(())
} else {
// 如果inner字段是None,说明文件已经被关闭或丢弃,返回错误
Err(Error::new(
std::io::ErrorKind::Other,
"File is already closed",
))
}
}
}
// 实现drop trait,用于在值离开作用域时运行的一些代码
impl Drop for File {
fn drop(&mut self) {
// 使用模式匹配获取字段值
if let Some(inner) = self.inner.take() {
let name = inner.name;
let fd = inner.fd;
println!("Dropping file: {} (fd: {})", name, fd);
// 使用 FromRawFd 将 fd 转换回 File
let file: std::fs::File = unsafe {
std::os::unix::io::FromRawFd::from_raw_fd(fd)
};
// 丢弃file实例
drop(file);
} else {
// 如果inner字段是None的话,说明文件已经被丢弃或关闭,不做任何操作
}
}
}
fn main() {
// 创建一个名为 "test.txt" 的文件,并写入一些内容
std::fs::write("test.txt", "Hello, world!").unwrap();
// 打开文件并获取 File 实例
let file: File = File::open("test.txt").unwrap();
// 打印文件名和 fd (这里需要修改)
println!("File name: {}, fd: {}",
file.inner.as_ref().unwrap().name,
file.inner.as_ref().unwrap().fd
);
// 关闭文件并处理任何错误
match file.close() {
Ok(()) => println!("File closed successfully"),
Err(e) => println!("Error closing file: {}", e),
}
// 检查关闭后的文件大小
let metadata = metadata("test.txt").unwrap();
println!("File size: {} bytes", metadata.len());
}
解决方案2:把字段包装在Option<T>
里
我们也可以保持结构体不变,把每个字段的值都套在Option<T>
里,需要获取所有权使用时用Option::take
就可以,需要引用时用.as_ref()
加.unwrap()
就可以。
如果类型具有合理的空值,那么效果很好。
缺点:如果你必须将几乎每个字段都包装在Option
中,然后对这些字段的每次访问都进行匹配的unwrap
就会使代码变得很繁琐。
我们在上文的代码例基础上进行修改:
步骤1:改掉File
的定义
为每一个字段添加一层Option<T>
:
rust
/// 一个表示文件句柄的类型
struct File {
/// 文件名
name: Option<String>,
/// 文件描述符
fd: Option<i32>,
}
步骤2:修改File
上的方法
File
上有两个方法,我们都需要添加通过Option<T>
这层壳来获取内部类型上字段的代码。
首先是open
方法:
rust
/// 一个构造函数,打开一个文件并返回一个 File 实例
fn open(name: &str) -> Result<File, Error> {
// 使用 OpenOptions 打开文件,具备读写权限
let file: StdFile = OpenOptions::new()
.read(true)
.write(true)
.open(name)?;
// 获取文件描述符
let fd: i32 = file.as_raw_fd();
// 返回一个 File 实例
Ok(File {
name: Some(name.to_string()),
fd: Some(fd),
})
}
open
方法的参数没有涉及到File
结构体,所以接收参数部分不用修改open
方法的返回值涉及到了File
,得为每个字段添上Some
变体
其次是close
方法:
rust
/// 一个显式的析构函数,关闭文件并返回任何错误
fn close(mut self) -> Result<(), Error> {
// 模式匹配,并使用std::mem::take取出name字段的值
if let Some(name) = std::mem::take(&mut self.name) {
//模式匹配,并使用std::mem::take取出fd字段的值
if let Some(fd) = std::mem::take(&mut self.fd) {
// 打印
println!("Closing file: {} with fd: {}", name, fd);
// 使用 FromRawFd 将 fd 转换回 File
let file: std::fs::File = unsafe {
std::os::unix::io::FromRawFd::from_raw_fd(fd)
};
// 刷新文件数据到磁盘
file.sync_all()?;
// 将文件截断为 0 字节
file.set_len(0)?;
// 再次刷新文件
file.sync_all()?;
// 丢弃文件实例,它会自动关闭文件
drop(file);
// 返回成功
Ok(())
} else {
// 如果fd字段是None,说明文件已经被关闭或丢弃,返回一个错误
Err(Error::new(
std::io::ErrorKind::Other,
"File descriptor already dropped or taken",
))
}
} else {
// 如果name字段是None,说明文件已经被关闭或丢弃,返回一个错误
Err(Error::new(
std::io::ErrorKind::Other,
"File name already dropped or taken",
))
}
}
- 参数要先经过模式匹配,并使用
std::mem::take
取出里面的值 - 如果任意字段是
None
,说明文件已经被关闭或丢弃,返回一个错误
步骤3:修改Drop
trait的实现
rust
fn drop(&mut self) {
// 使用模式匹配获取字段值
if let Some(name) = self.name.take() {
if let Some(fd) = self.fd.take() {
println!("Dropping file: {} (fd: {})", name, fd);
// 使用 FromRawFd 将 fd 转换回
Filelet file: std::fs::File = unsafe {
std::os::unix::io::FromRawFd::from_raw_fd(fd)
};
// 丢弃file实例
drop(file);
} else {
// 如果fd字段是None,说明文件已经被关闭或丢弃,不做任何操作
}
} else {
// 如果name字段是None,说明文件已经被关闭或丢弃,不做任何操作
}
}
- 参数要先经过模式匹配,并使用
std::mem::take
取出里面的值 - 如果任意字段是
None
,说明文件已经被关闭或丢弃,不做任何操作
步骤4:微修主函数
rust
fn main() {
// ...前文无修改,已省略
// 打印文件名和 fd (这里需要修改)
println!("File name: {}, fd: {}",
file.name.as_ref().unwrap(),
file.fd.as_ref().unwrap()
);
// ...后文无修改,已省略
}
- 原始类型被
Option<T>
包裹,调用.as_ref()
后获得里面值的引用 - 变成了引用之后再使用
unwrap
提取出来的值就是引用而不是所有的值 - 而直接访问
Option
的值需要转移所有权或匹配处理(例如通过take()
或unwrap()
),这会销毁Option
的内部值,所以得要as_ref
整体代码
rust
use std::os::fd::AsRawFd;
use std::fs::{File as StdFile, OpenOptions, metadata};
use std::io::Error;
/// 一个表示文件句柄的类型
struct File {
/// 文件名
name: Option<String>,
/// 文件描述符
fd: Option<i32>,
}
impl File {
/// 一个构造函数,打开一个文件并返回一个 File 实例
fn open(name: &str) -> Result<File, Error> {
// 使用 OpenOptions 打开文件,具备读写权限
let file: StdFile = OpenOptions::new()
.read(true)
.write(true)
.open(name)?;
// 获取文件描述符
let fd: i32 = file.as_raw_fd();
// 返回一个 File 实例
Ok(File {
name: Some(name.to_string()),
fd: Some(fd),
})
}
/// 一个显式的析构函数,关闭文件并返回任何错误
fn close(mut self) -> Result<(), Error> {
// 模式匹配,并使用使用std::mem::take取出name字段的值
if let Some(name) = std::mem::take(&mut self.name) {
//模式匹配,并使用使用std::mem::take取出fd字段的值
if let Some(fd) = std::mem::take(&mut self.fd) {
// 打印
println!("Closing file: {} with fd: {}", name, fd);
// 使用 FromRawFd 将 fd 转换回 File
let file: std::fs::File = unsafe {
std::os::unix::io::FromRawFd::from_raw_fd(fd)
};
// 刷新文件数据到磁盘
file.sync_all()?;
// 将文件截断为 0 字节
file.set_len(0)?;
// 再次刷新文件
file.sync_all()?;
// 丢弃文件实例,它会自动关闭文件
drop(file);
// 返回成功
Ok(())
} else {
// 如果fd字段是None,说明文件已经被关闭或丢弃,返回一个错误
Err(Error::new(
std::io::ErrorKind::Other,
"File descriptor already dropped or taken",
))
}
} else {
// 如果name字段是None,说明文件已经被关闭或丢弃,返回一个错误
Err(Error::new(
std::io::ErrorKind::Other,
"File name already dropped or taken",
))
}
}
}
// 实现drop trait,用于在值离开作用域时运行的一些代码
impl Drop for File {
fn drop(&mut self) {
// 使用模式匹配获取字段值
if let Some(name) = self.name.take() {
if let Some(fd) = self.fd.take() {
println!("Dropping file: {} (fd: {})", name, fd);
// 使用 FromRawFd 将 fd 转换回
Filelet file: std::fs::File = unsafe {
std::os::unix::io::FromRawFd::from_raw_fd(fd)
};
// 丢弃file实例
drop(file);
} else {
// 如果fd字段是None,说明文件已经被关闭或丢弃,不做任何操作
}
} else {
// 如果name字段是None,说明文件已经被关闭或丢弃,不做任何操作
}
}
}
fn main() {
// 创建一个名为 "test.txt" 的文件,并写入一些内容
std::fs::write("test.txt", "Hello, world!").unwrap();
// 打开文件并获取 File 实例
let file: File = File::open("test.txt").unwrap();
// 打印文件名和 fd (这里需要修改)
println!("File name: {}, fd: {}",
file.name.as_ref().unwrap(),
file.fd.as_ref().unwrap()
);
// 关闭文件并处理任何错误
match file.close() {
Ok(()) => println!("File closed successfully"),
Err(e) => println!("Error closing file: {}", e),
}
// 检查关闭后的文件大小
let metadata = metadata("test.txt").unwrap();
println!("File size: {} bytes", metadata.len());
}
方法3:将数据持有在ManuallyDrop
类型内
将数据持有在ManuallyDrop
类型内,它会解引用内部类型,不必再使用unwrap
。
在drop
中进行销毁时,可使用ManuallyDrop::take
来获取所有权。
缺点:ManuallyDrop::take
是不安全的,需要放在unsafe
块中。
我们在上文的代码例基础上进行修改:
步骤1:改掉File
的定义
为每一个字段添加一层Option<T>
:
rust
/// 一个表示文件句柄的类型
struct File {
/// 文件名
name: ManuallyDrop<String>,
/// 文件描述符
fd: ManuallyDrop<i32>,
}
步骤2:修改File
上的方法
File
上有两个方法,我们都需要添加通过Option<T>
这层壳来获取内部类型上字段的代码。
首先是open
方法:
rust
/// 一个构造函数,打开一个文件并返回一个 File 实例
fn open(name: &str) -> Result<File, Error> {
// 使用 OpenOptions 打开文件,具备读写权限
let file: StdFile = OpenOptions::new()
.read(true)
.write(true)
.open(name)?;
// 获取文件描述符
let fd: i32 = file.as_raw_fd();
// 返回一个 File 实例
Ok(File {
name: ManuallyDrop::new(name.to_string()),
fd: ManuallyDrop::new(fd),
})
}
open
方法的参数没有涉及到File
结构体,所以接收参数部分不用修改open
方法的返回值涉及到了File
,每个字段都得用ManuallyDrop::new
来传值
其次是close
方法:
rust
/// 一个显式的析构函数,关闭文件并返回任何错误
fn close(mut self) -> Result<(), Error> {
// 使用std::mem::replace将name字段替换为一个空字符串,把原来的值给name
let name =
std::mem::replace(&mut self.name, ManuallyDrop::new("".to_string()));
// 使用std::mem::replace将fd字段替换为一个无效的值(-1),把原来的值给fd
let fd =
std::mem::replace(&mut self.fd, ManuallyDrop::new(0));
// 打印
println!("Closing file: {:?} with fd: {:?}", name, fd);
// 使用 FromRawFd 将 fd 转换回 File
let file: std::fs::File = unsafe {
std::os::unix::io::FromRawFd::from_raw_fd(*fd) //这里fd要先解引用
};
// 刷新文件数据到磁盘
file.sync_all()?;
// 将文件截断为 0 字节
file.set_len(0)?;
// 再次刷新文件
file.sync_all()?;
// 丢弃文件实例,它会自动关闭文件
drop(file);
// 返回成功
Ok(())
}
- 使用
std::mem::replace
将name
字段替换为一个空字符串,把原来的值给name
- 使用
std::mem::replace
将fd
字段替换为一个无效的值(-1),把原来的值给fd
std::os::unix::io::FromRawFd::from_raw_fd(*fd)
中参数要先解引用,也就是写*fd
步骤3:修改Drop
trait的实现
rust
fn drop(&mut self) {
// 使用ManuallyDrop::take取出name字段的值,并检查是否是空字符串
let name = unsafe { ManuallyDrop::take(&mut self.name) };
// 使用ManuallyDrop::take取出fd字段的值,并检查是否是无效的值
let fd = unsafe { ManuallyDrop::take(&mut self.fd) };
//打印
println!("Dropping file: {:?} (fd: {:?})", name, fd);
// 如果fd字段不是无效的值,说明文件还没有被关闭或丢弃,需要执行丢弃操作
if fd != -1 || !name.is_empty() {
let file = unsafe { std::fs::File::from_raw_fd(fd) };
// 丢弃
drop(file);
}
}
- 使用
ManuallyDrop::take
取出name
和fd
字段的值,并检查是否是空字符串或无效的值 - 如果
fd
字段不是无效的值(不是-1),或是name
字段不是空字符串,就说明文件还没有被关闭或丢弃,需要执行丢弃操作 - 其实这里不用两个判断条件(
fd != -1 || !name.is_empty()
),一个就够了,因为name
和fd
字段的值的变化是一起的,一个无效就代表着整个结构体都还未被清理。
步骤4:微修主函数
rust
fn main() {
// ...前文无修改,已省略
// 打印文件名和 fd (这里需要修改)
println!("File name: {}, fd: {}", *file.name, *file.fd);
// ...后文无修改,已省略
}
- 使用解引用来打印值
整体代码
rust
use std::os::fd::{AsRawFd, FromRawFd};
use std::fs::{File as StdFile, OpenOptions, metadata};
use std::io::Error;
use std::mem::ManuallyDrop;
/// 一个表示文件句柄的类型
struct File {
/// 文件名
name: ManuallyDrop<String>,
/// 文件描述符
fd: ManuallyDrop<i32>,
}
impl File {
/// 一个构造函数,打开一个文件并返回一个 File 实例
fn open(name: &str) -> Result<File, Error> {
// 使用 OpenOptions 打开文件,具备读写权限
let file: StdFile = OpenOptions::new()
.read(true)
.write(true)
.open(name)?;
// 获取文件描述符
let fd: i32 = file.as_raw_fd();
// 返回一个 File 实例
Ok(File {
name: ManuallyDrop::new(name.to_string()),
fd: ManuallyDrop::new(fd),
})
}
/// 一个显式的析构函数,关闭文件并返回任何错误
fn close(mut self) -> Result<(), Error> {
// 使用std::mem::replace将name字段替换为一个空字符串,把原来的值给name
let name =
std::mem::replace(&mut self.name, ManuallyDrop::new("".to_string()));
// 使用std::mem::replace将fd字段替换为一个无效的值(-1),把原来的值给fd
let fd =
std::mem::replace(&mut self.fd, ManuallyDrop::new(-1));
// 打印
println!("Closing file: {:?} with fd: {:?}", name, fd);
// 使用 FromRawFd 将 fd 转换回 File
let file: std::fs::File = unsafe {
std::os::unix::io::FromRawFd::from_raw_fd(*fd) //这里fd要先解引用
};
// 刷新文件数据到磁盘
file.sync_all()?;
// 将文件截断为 0 字节
file.set_len(0)?;
// 再次刷新文件
file.sync_all()?;
// 丢弃文件实例,它会自动关闭文件
drop(file);
// 返回成功
Ok(())
}
}
// 实现drop trait,用于在值离开作用域时运行的一些代码
impl Drop for File {
fn drop(&mut self) {
// 使用ManuallyDrop::take取出name字段的值,并检查是否是空字符串
let name = unsafe { ManuallyDrop::take(&mut self.name) };
// 使用ManuallyDrop::take取出fd字段的值,并检查是否是无效的值
let fd = unsafe { ManuallyDrop::take(&mut self.fd) };
//打印
println!("Dropping file: {:?} (fd: {:?})", name, fd);
// 如果fd字段不是无效的值,说明文件还没有被关闭或丢弃,需要执行丢弃操作
if fd != -1 || !name.is_empty() {
let file = unsafe { std::fs::File::from_raw_fd(fd) };
// 丢弃
drop(file);
}
}
}
fn main() {
// 创建一个名为 "test.txt" 的文件,并写入一些内容
std::fs::write("test.txt", "Hello, world!").unwrap();
// 打开文件并获取 File 实例
let file: File = File::open("test.txt").unwrap();
// 打印文件名和 fd (这里需要修改)
println!("File name: {}, fd: {}", *file.name, *file.fd);
// 关闭文件并处理任何错误
match file.close() {
Ok(()) => println!("File closed successfully"),
Err(e) => println!("Error closing file: {}", e),
}
// 检查关闭后的文件大小
let metadata = metadata("test.txt").unwrap();
println!("File size: {} bytes", metadata.len());
}
三种方案的选择
这三种方案的选择要根据实际情况,通常第二个方案。但是如果真的字段太多要写的unwrap
太多的话就需要考虑其他的方案。
如果代码足够简单,可以轻松检查代码的安全性,那么第三种ManuallyDrop
方案也是非常好的。