Rust-接口设计建议之灵活(flexible)灵活(flexible)代码的契约(Contract)你写的代码包含契约契约:要求:代码使用的限制承诺:代码使用的保证设计接口时(经验法则):避免施加不必要的限制,只做能够兑现的承诺增加限制或取消承诺:重大的语
fn frobnicate1(s: String) -> String
fn frobnicate2(s: &str) -> Cow<'_, str>
fn frobnicate3(s: impl AsRef<str>) -> impl AsRef<str>
例子一
use std::borrow::Cow;
fn frobnicate3<T: AsRef<str>>(s: T) -> T {
s
}
fn main() {
let string = String::from("example");
let borrowed: &str = "hello";
let cow: Cow<str> = Cow::Borrowed("world");
let result1: &str = frobnicate3::<&str>(string.as_ref());
let result2: &str = frobnicate3::<&str>(borrowed);
let result3 = frobnicate3(cow);
println!("Result1: {:?}", result1);
println!("Result2: {:?}", result2);
println!("Result3: {:?}", result3);
}
例子二
// 你有一个函数,它接受一个实现了 AsRef<str> trait 的参数
fn print_as_str<T: AsRef<str>>(s: T) {
println!("{}", s.as_ref());
}
// 这个函数是泛型的,它对 T 进行了泛型化,
// 这意味着它会对你使用它的每一种实现了 AsRef<str> 的类型进行单态化。
// 例如,如果你用一个 String 和一个 &str 来调用它,
// 你就会在你的二进制文件中有两份函数的拷贝:
fn main() {
let s = String::from("hello");
let r = "world";
print_as_str(s); // 调用 print_as_str::<String>
print_as_str(r); // 调用 print_as_str::<&str>
}
例子三
// 为了避免这种重复,你可以把函数改成接受一个 &dyn AsRef<str>:
fn print_as_str(s: &dyn AsRef<str>) {
println!("{}", s.as_ref());
}
// 这个函数不再是泛型的,它接受一个 trait 对象,
// 它可以是任何实现了 AsRef<str> 的类型
// 这意味着它会在运行时使用动态分发来调用 as_ref 方法,
// 并且你只会在你的二进制文件中有一份函数的拷贝:
fn main() {
let s = String::from("hello");
let r = "world";
print_as_str(&s); // 传递一个类型为 &dyn AsRef<str> 的 trait 对象
print_as_str(&r); // 传递一个类型为 &dyn AsRef<str> 的 trait 对象
}
例子四
// 假设我们有一个名为 process 的泛型函数,它接受一个类型参数 T 并对其执行某些操作:
fn process<T>(value: T) {
// 处理 value 的代码
println!("处理 T");
}
// 上述函数使用静态分发,这意味着在编译时将为每个具体类型 T 生成相应的实现。
// 现在,假设调用者想要提供动态分发的方式,允许在运行时选择实现。
// 它们可以通过传递 Trait 对象作为参数,
// 使用 dyn 关键字来实现。以下是一个例子:
trait Processable {
fn process(&self);
}
struct TypeA;
impl Processable for TypeA {
fn process(&self) {
println!("处理 TypeA");
}
}
struct TypeB;
impl Processable for TypeB {
fn process(&self) {
println!("处理 TypeB");
}
}
fn process_trait_object(value: &dyn Processable) {
value.process();
}
// 如果调用者想要使用动态分发并在运行时选择实现,
// 它们可以调用 process_trait_object 函数,并传递 Trait 对象作为参数。
// 调用者可以根据需求选择要提供的具体实现:
fn main() {
let a = TypeA;
let b = TypeB;
process_trait_object(&a);
process_trait_object(&b);
process(&a);
process(&b);
process(&a as &dyn Processable);
process(&b as &dyn Processable);
}
T: AsRef<str>
或 impl AsRef<str>
&dyn Hash + Eq
这样的组合约束。例子五
fn foo(v: &Vec<usize>) {
// 处理 v 的代码
// ...
}
// 现在,我们决定将函数改为使用 Trait 限定 AsRef<[usize]>,
// 即 impl AsRef<[usize]>:
// fn foo(v: impl AsRef<[usize]>) {
// // 处理 v 的代码
// // ...
// }
fn main() {
let iter = vec![1, 2, 3].into_iter();
foo(&iter.collect());
}
// 在原始版本中,编译器可以推断出 iter.collect() 应该收集为一个 Vec<usize> 类型,
// 因为我们将其传递给了接受 &Vec<usize> 的 foo 函数。
// 然而,在更改为使用特质限定后,编译器只知道 foo 函数
// 接受一个实现了 AsRef<[usize]> 特质的类型。
// 这里有多个类型满足这个条件,例如 Vec<usize> 和 &[usize]。
// 因此,编译器无法确定应该将 iter.collect() 的结果解释为哪个具体类型。
// 这样的更改将导致编译器无法推断类型,并且调用者的代码将无法通过编译。
// 为了解决这个问题,调用者可能需要显示指定期望的类型,例如:
// let iter = vec![1, 2, 3].into_iter();
// foo(&iter.collect::<Vec<usize>>());
可复用:泛型函数能应用在广泛的类型上,同时明确给出了这些类型的必须满足的关系。
静态分派和编译器优化: 每个泛型函数都被专门用于实现了 trait bounds 的具体的类型 (即 单态化 monomorphized ),这意味着:
内联式布局:如果结构体和枚举体类型具有某个泛型参数 T
, T
的值将在结构体和枚举体里以内联方式排列,不产生任何间接调用。
可推断:由于泛型函数的类型参数通常是推断出来的, 泛型函数可以减少复杂的代码,比如显式转换、通常必须的一些方法调用。
精确的类型:因为泛型给实现了某个 trait 的具体类型一个名称, 从而有可能清楚这个类型需要或创建的地方在哪。比如这个函数:
fn binary<T: Trait>(x: T, y: T) -> T
会保证消耗和创建具有相同类型 T
的值;不可能传入实现了 Trait
的但不同名称的两个类型。
T
是类型参数,那么它代表一个单独的实际类型。 对于像 Vec<T>
这样具体的单独的元素类型也是一样, 而且 Vec
实际上为了内联这些元素,进行了专门的处理。 有时候,不同的类型会更有用,参考 trait objects 。The Rust RFC Book:<https://rust-lang.github.io/rfcs/introduction.html>
对象安全:描述一个 Trait 可否安全的包装成 Trait Object
对象安全的 Trait 是满足以下条件的 Trait(RFC 255):
&Self
(即 &self)&mut Self
(即 &mut self
)Box<Self>
Rc<Self>
Arc<Self>
Pin<P>
,其中 P 是上述类型之一where Self: Sized
约束(Self 的接收器类型(即 self)暗含了这一点)where Self: Sized
约束(Self 的接收器类型(即 self)暗含了这一点)例子六
// 假设我们有一个 Animal 特征,它有两个方法:name 和 speak。
// name 方法返回一个&str,表示动物的名字;
// speak 方法打印出动物发出的声音。
// 我们可以为 Dog 和 Cat 类型实现这个特征:
trait Animal {
fn name(&self) -> &str;
fn speak(&self);
}
struct Dog {
name: String,
}
impl Animal for Dog {
fn name(&self) -> &str {
&self.name
}
fn speak(&self) {
println!("Woof!");
}
}
struct Cat {
name: String,
}
impl Animal for Cat {
fn name(&self) -> &str {
&self.name
}
fn speak(&self) {
println!("Meow!");
}
}
// 这个 Animal 特征是 object-safe 的,因为它没有返回 Self 类型或使用泛型参数。
// 所以我们可以用它来创建一个 trait object:
fn main() {
let dog = Dog {
name: "Fido".to_string(),
};
let cat = Cat {
name: "Whiskers".to_string(),
};
let animals: Vec<&dyn Animal> = vec![&dog, &cat];
for animal in animals {
println!("This is {}", animal.name());
animal.speak();
}
}
// 这样我们就可以用一个统一的类型 Vec<&dyn Animal> 来存储不同类型的动物,
// 并且通过 trait object 来调用它们的方法。
例子七
// 但是如果我们给 Animal 特征添加一个新的方法 clone,它返回一个 Self 类型:
trait Animal {
fn name(&self) -> &str;
fn speak(&self);
fn clone(&self) -> Self;
}
// 那么这个特征就不再是 object-safe 的了,
// 因为 clone 方法违反了规则:返回类型不能是 Self。
// 这样我们就不能用它来创建 trait object 了,
// 因为编译器无法知道 Self 具体指代哪个类型
struct Dog {
name: String,
}
impl Animal for Dog {
fn name(&self) -> &str {
&self.name
}
fn speak(&self) {
println!("Woof!");
}
fn clone(&self) -> Self
where
Self: Sized,
{
todo!()
}
}
struct Cat {
name: String,
}
impl Animal for Cat {
fn name(&self) -> &str {
&self.name
}
fn speak(&self) {
println!("Meow!");
}
fn clone(&self) -> Self
where
Self: Sized,
{
todo!()
}
}
fn main() {
let dog = Dog {
name: "Fido".to_string(),
};
let cat = Cat {
name: "Whiskers".to_string(),
};
let animals: Vec<&dyn Animal> = vec![&dog, &cat]; // 报错 the trait `Animal` cannot be made into an object consider moving `clone` to another trait
for animal in animals {
println!("This is {}", animal.name());
animal.speak();
}
}
例子八
// 如果我们想让 Animal 特征保持 object-safe,
// 我们就不能给它添加返回 Self 类型的方法。
// 或者,我们可以给 clone 方法添加一个 where Self: Sized 的特征界定,
// 这样他就只能在具体类型上调用,而不是在 trait object 上:
trait Animal {
fn name(&self) -> &str;
fn speak(&self);
fn clone(&self) -> Self
where
Self: Sized;
}
struct Dog {
name: String,
}
impl Animal for Dog {
fn name(&self) -> &str {
&self.name
}
fn speak(&self) {
println!("Woof!");
}
fn clone(&self) -> Self
where
Self: Sized,
{
todo!()
}
}
struct Cat {
name: String,
}
impl Animal for Cat {
fn name(&self) -> &str {
&self.name
}
fn speak(&self) {
println!("Meow!");
}
fn clone(&self) -> Self
where
Self: Sized,
{
todo!()
}
}
// 这样我们就可以继续用 Animal 特征来创建 trait object 了,
// 但是我们不能用 trait object 来调用 clone 方法
fn main() {
let dog = Dog {
name: "Fido".to_string(),
};
let cat = Cat {
name: "Whiskers".to_string(),
};
cat.clone(); // 只能在具体的类型上调用
let animals: Vec<&dyn Animal> = vec![&dog, &cat];
for animal in animals {
println!("This is {}", animal.name());
animal.speak();
animal.clone(); // 报错 the `clone` method cannot be invoked on a trait object
}
}
例子九
use std::collections::HashSet;
use std::hash::Hash;
// 将泛型参数放在 Trait 本身上
trait Container<T> {
fn contains(&self, item: &T) -> bool;
}
// 我们可以为不同的容器类型实现 Container Trait,每个实现都具有自己特定的元素类型。
// 例,我们可以为 Vec<T> 和 HashSet<T> 实现 Container Trait:
impl<T> Container<T> for Vec<T>
where
T: PartialEq,
{
fn contains(&self, item: &T) -> bool {
self.iter().any(|x| x == item)
}
}
impl<T> Container<T> for HashSet<T>
where
T: Hash + Eq,
{
fn contains(&self, item: &T) -> bool {
self.contains(item)
}
}
fn main() {
// 创建一个 Vec<T> 和 HashSet<T> 的实例
let vec_container: Box<dyn Container<i32>> = Box::new(vec![1, 2, 3]);
let hashset_container: Box<dyn Container<i32>> = Box::new(vec![4, 5, 6].into_iter().collect::<HashSet<_>>());
// 调用 contains 方法
println!("Vec contains 2: {}", vec_container.contains(&2));
println!("HashSet contains 6: {}", hashset_container.contains(&6));
}
例子十
use std::fmt::Debug;
// 假设我们有一个 Trait Foo,它有一个泛型方法 bar,它接受一个泛型参数 T:
// trait Foo {
// fn bar<T>(&self, x: T);
//}
// 这个 Trait 是不是 object-safe 的呢?答案是:取决于 T 的类型。 注意:它不是对象安全的
// 如果 T 是一个具体类型,比如 i32或 String,那么它就不是 object-safe 的,
// 因为它需要在运行时知道 T 的具体类型才能调用 bar 方法。
// 但如果 T 也是一个 trait object,比如 &dyn Debug 或 &dyn Display,
// 那么这个 Trait 就是 object-safe 的,因为它可以用动态分发的方式来调用 T 的方法。
// 所以我们可以这样写:
trait Foo {
fn bar(&self, x: &dyn Debug);
}
// 定义一个结构体 A,它实现了 Foo 特征
struct A {
name: String,
}
impl Foo for A {
fn bar(&self, x: &dyn Debug) {
println!("A {} says {:?}", self.name, x);
}
}
// 定义一个结构体 B,它也实现了 Foo 特征
struct B {
id: i32,
}
impl Foo for B {
fn bar(&self, x: &dyn Debug) {
println!("B {} says {:?}", self.id, x);
}
}
// 这样我们就可以用 Foo 特征来创建 trait object 了,比如:
fn main() {
// 创建两个不同类型的值,它们都实现了 Foo 特征
let a = A {
name: "Alice".to_string(),
};
let b = B { id: 42};
// 创建一个 Vec,它存储了 Foo 的 trait object
let foos: Vec<&dyn Foo> = vec![&a, &b];
// 遍历 Vec,并用 trait object 调用 bar 方法
for foo in foos {
foo.bar(&"Hello"); // "Hello" 实现了 Debug 特征
}
}
例子十一
use std::borrow::Cow;
// 假设我们有一个函数 process_data,它接收一个字符串参数,
// 并根据一些条件对其进行处理。有时,我们需要修改输入字符串,
// 并拥有对修改后的字符串的所有权。
// 然而,大多数情况下,我们只是对输入字符串进行读取操作,而不需要修改它。
fn process_data(data: Cow<str>) {
if data.contains("invalid") {
// 如果输入字符串包含 “invalid”,我们需要修改它
let owned_data: String = data.into_owned();
// 进行一些修改操作
println!("Processed data: {}", owned_data);
} else {
// 如果输入字符串不包含 “invalid”,我们只需要读取它
println!("Data: {}", data);
}
}
// 在这个例子中,我们使用了 Cow<str> 类型作为参数类型。
// 当调用函数时,我们可以传递一个普通的字符串引用(&str)
// 或一个拥有所有权的字符串(String)作为参数。
fn main() {
let input1 = "This is valid data.";
process_data(Cow::Borrowed(input1));
let input2 = "This is invalid data.";
process_data(Cow::Owned(input2.to_owned()));
}
例子十二
use std::os::fd::AsRawFd;
// 一个表示文件句柄的类型
struct File {
// 文件名
name: String,
// 文件描述符
fd: i32,
}
// File 类型的方法实现
impl File {
// 一个构造函数,打开一个文件并返回一个 File 实例
fn open(name: &str) -> Result<File, std::io::Error> {
// 使用 std::fs::OpenOptions 打开文件,具有读写权限
let file = std::fs::OpenOptions::new().read(true).write(true).open(name)?;
// 使用 std::os::unix::io::AsRawFd 获取文件描述符
let fd = file.as_raw_fd();
// 返回一个 File 实例,包含 name 和 fd 字段
Ok(File {
name: name.to_string(),
fd,
})
}
// 一个显式的析构器,关闭文件并返回任何错误
fn close(self) -> Result<(), std::io::Error> {
// 使用 std::os::unix::io::FromRawFd 将 fd 转换回 std::fs::File
let file: std::fs::File = unsafe { std::os::unix::io::FromRawFd::from_raw_fd(self.id) };
// 使用 std::fs::File::sync_all 将任何挂起的写入刷新到磁盘
file.sync_all()?;
// 使用 std::fs::File::set_len 将文件截断为零字节
file.set_len(0)?;
// 再次使用 std::fs::File::sync_all 刷新截断
file.sync_all()?;
// 丢弃 file 实例,它会自动关闭
drop(file);
// 返回 Ok(())
Ok(())
}
}
// 一个测试 File 类型的主函数
fn main() {
// 创建一个名为 "test.txt" 的文件,包含一些内容
std::fs::write("test.txt", "Hello, world!").unwrap();
// 打开文件并获取一个 File 实例
let 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 = std::fs::metadata("test.txt").unwrap();
println!("File size: {} bytes", metadata.len());
}
注意:显式的析构函数需要在文档中突出显示
例子十三
use std::os::fd::AsRawFd;
// 一个表示文件句柄的类型
struct File {
// 文件名
name: String,
// 文件描述符
fd: i32,
}
// File 类型的方法实现
impl File {
// 一个构造函数,打开一个文件并返回一个 File 实例
fn open(name: &str) -> Result<File, std::io::Error> {
// 使用 std::fs::OpenOptions 打开文件,具有读写权限
let file = std::fs::OpenOptions::new().read(true).write(true).open(name)?;
// 使用 std::os::unix::io::AsRawFd 获取文件描述符
let fd = file.as_raw_fd();
// 返回一个 File 实例,包含 name 和 fd 字段
Ok(File {
name: name.to_string(),
fd,
})
}
// 一个显式的析构器,关闭文件并返回任何错误
fn close(self) -> Result<(), std::io::Error> {
// 移出 name 字段并打印它
let name = self.name; // 报错 不能从 `self.name` 中移出值,因为它位于 `&mut` 引用后面
println!("Closing file {}", name);
// 使用 std::os::unix::io::FromRawFd 将 fd 转换回 std::fs::File
let file: std::fs::File = unsafe { std::os::unix::io::FromRawFd::from_raw_fd(self.id) };
// 使用 std::fs::File::sync_all 将任何挂起的写入刷新到磁盘
file.sync_all()?;
// 使用 std::fs::File::set_len 将文件截断为零字节
file.set_len(0)?;
// 再次使用 std::fs::File::sync_all 刷新截断
file.sync_all()?;
// 丢弃 file 实例,它会自动关闭
drop(file);
// 返回 Ok(())
Ok(())
}
}
// Drop trait 的实现,用于在值离开作用域时运行一些代码
impl Drop for File {
// drop 方法,接受一个可变引用到 self 作为参数
fn drop(&mut self) {
// 调用 close 方法并忽略它的结果
let _ = self.close(); // 报错 不能从 `*self` 中移出值,因为它位于 `&mut` 引用后面
// 打印一条消息,表明文件被丢弃了
println!("Dropping file {}", self.name);
}
}
// 一个测试 File 类型的主函数
fn main() {
// 创建一个名为 "test.txt" 的文件,包含一些内容
std::fs::write("test.txt", "Hello, world!").unwrap();
// 打开文件并获取一个 File 实例
let 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 = std::fs::metadata("test.txt").unwrap();
println!("File size: {} bytes", metadata.len());
}
例子十四
use std::os::fd::AsRawFd;
// 一个表示文件句柄的类型
struct File {
// 一个包装在 Option 中的内部类型
inner: Option<InnerFile>,
}
// 一个内部类型,持有文件名和文件描述符
struct InnerFile {
// 文件名
name: String,
// 文件描述符
fd: i32,
}
// File 类型的方法实现
impl File {
// 一个构造函数,打开一个文件并返回一个 File 实例
fn open(name: &str) -> Result<File, std::io::Error> {
// 使用 std::fs::OpenOptions 打开文件,具有读写权限
let file = std::fs::OpenOptions::new().read(true).write(true).open(name)?;
// 使用 std::os::unix::io::AsRawFd 获取文件描述符
let fd = file.as_raw_fd();
// 返回一个 File 实例,包含一个 Some(InnerFile) 的 inner 字段
Ok(File {
inner: Some(InnerFile {
name: name.to_string(),
fd,
}),
})
}
// 一个显式的析构器,关闭文件并返回任何错误
fn close(mut self) -> Result<(), std::io::Error> {
// 使用 Option::take 取出 inner 字段的值,并检查是否是 Some(InnerFile)
if let Some(inner) = self.inner.take() {
// 移出 name 和 fd 字段并打印它们
let name = inner.name;
let fd = inner.fd;
println!("Closing file {} with fd {}", name, fd);
// 使用 std::os::unix::io::FromRawFd 将 fd 转换回 std::fs::File
let file: std::fs::File = unsafe { std::os::unix::io::FromRawFd::from_raw_fd(self.id) };
// 使用 std::fs::File::sync_all 将任何挂起的写入刷新到磁盘
file.sync_all()?;
// 使用 std::fs::File::set_len 将文件截断为零字节
file.set_len(0)?;
// 再次使用 std::fs::File::sync_all 刷新截断
file.sync_all()?;
// 丢弃 file 实例,它会自动关闭
drop(file);
// 返回 Ok(())
Ok(())
} else {
// 如果 inner 字段是 None,说明文件已经被关闭或丢弃,返回一个错误
Err(std::io::Error::new(
std::io::ErrorKind::Other,
"File already closed or dropped",
))
}
}
}
// Drop trait 的实现,用于在值离开作用域时运行一些代码
impl Drop for File {
// drop 方法,接受一个可变引用到 self 作为参数
fn drop(&mut self) {
// 使用 Option::take 取出 inner 字段的值,并检查是否是 Some(InnerFile)
if let Some(inner) = self.inner.take() {
// 移出 name 和 fd 字段并打印它们
let name = inner.name;
let fd = inner.id;
println!("Dropping file {} with fd {}", name, fd);
// 使用 std::os::unix::io::FromRawFd 将 fd 转换回 std::fs::File
let file: std::fs::File = unsafe { std::os::unix::io::FromRawFd::from_raw_fd(fd) };
// 丢弃 file 实例,它会自动关闭
drop(file);
} else {
// 如果 inner 字段是 None,说明文件已经被关闭或丢弃,不做任何操作
}
}
}
// 一个测试 File 类型的主函数
fn main() {
// 创建一个名为 "test.txt" 的文件,包含一些内容
std::fs::write("test.txt", "Hello, world!").unwrap();
// 打开文件并获取一个 File 实例
let 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 = std::fs::metadata("test.txt").unwrap();
println!("File size: {} bytes", metadata.len());
}
例子十五
use std::os::fd::AsRawFd;
// 一个表示文件句柄的类型
struct File {
// 文件名,包装在一个 Option 中
name: Option<String>,
// 文件描述符,包装在一个 Option 中
fd: Option<i32>,
}
// File 类型的方法实现
impl File {
// 一个构造函数,打开一个文件并返回一个 File 实例
fn open(name: &str) -> Result<File, std::io::Error> {
// 使用 std::fs::OpenOptions 打开文件,具有读写权限
let file = std::fs::OpenOptions::new().read(true).write(true).open(name)?;
// 使用 std::os::unix::io::AsRawFd 获取文件描述符
let fd = file.as_raw_fd();
// 返回一个 File 实例,包含一个 Some(name) 和一个 Some(fd) 的字段
Ok(File {
name: Some(name.to_string()),
fd: Some(fd),
})
}
// 一个显式的析构器,关闭文件并返回任何错误
fn close(mut self) -> Result<(), std::io::Error> {
// 使用 std::mem::take 取出 name 字段的值,并检查是否是 Some(name)
if let Some(name) = std::mem::take(&mut self.name) {
// 使用 std::mem::take 取出 fd 字段的值,并检查是否是 Some(fd)
if let Some(fd) = std::mem::take(&mut self.fd) {
// 打印文件名和文件描述符
println!("Closing file {} with fd {}", name, fd);
// 使用 std::os::unix::io::FromRawFd 将 fd 转换回 std::fs::File
let file: std::fs::File = unsafe { std::os::unix::io::FromRawFd::from_raw_fd(fd) };
// 使用 std::fs::File::sync_all 将任何挂起的写入刷新到磁盘
file.sync_all()?;
// 使用 std::fs::File::set_len 将文件截断为零字节
file.set_len(0)?;
// 再次使用 std::fs::File::sync_all 刷新截断
file.sync_all()?;
// 丢弃 file 实例,它会自动关闭
drop(file);
// 返回 Ok(())
Ok(())
} else {
// 如果 fd 字段是 None,说明文件已经被关闭或丢弃,返回一个错误
Err(std::io::Error::new(
std::io::ErrorKind::Other,
"File descriptor already taken or dropped",
))
}
} else {
// 如果 name 字段是 None,说明文件已经被关闭或丢弃,返回一个错误
Err(std::io::Error::new(
std::io::ErrorKind::Other,
"File name already taken or dropped",
))
}
}
}
// Drop trait 的实现,用于在值离开作用域时运行一些代码
impl Drop for File {
// drop 方法,接受一个可变引用到 self 作为参数
fn drop(&mut self) {
// 使用 std::mem::take 取出 name 字段的值,并检查是否是 Some(name)
if let Some(name) = std::mem::take(&mut self.name) {
// 使用 std::mem::take 取出 fd 字段的值,并检查是否是 Some(fd)
if let Some(fd) = std::mem::take(&mut self.fd) {
// 打印文件名和文件描述符
println!("Dropping file {} with fd {}", name, fd);
// 使用 std::os::unix::io::FromRawFd 将 fd 转换回 std::fs::File
let file: std::fs::File = unsafe { std::os::unix::io::FromRawFd::from_raw_fd(fd) };
// 丢弃 file 实例,它会自动关闭
drop(file);
} else {
// 如果 fd 字段是 None,说明文件已经被关闭或丢弃,不做任何操作
}
} else {
// 如果 name 字段是 None,说明文件已经被关闭或丢弃,不做任何操作
}
}
}
// 一个测试 File 类型的主函数
fn main() {
// 创建一个名为 "test.txt" 的文件,包含一些内容
std::fs::write("test.txt", "Hello, world!").unwrap();
// 打开文件并获取一个 File 实例
let 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 = std::fs::metadata("test.txt").unwrap();
println!("File size: {} bytes", metadata.len());
}
例子十六
// 引入 std 库中的一些模块
use std::{mem::ManuallyDrop, os::fd::AsRawFd};
// 定义一个表示文件句柄的结构体
struct File {
// 文件名,包装在一个 ManuallyDrop 中
name: ManuallyDrop<String>,
// 文件描述符,包装在一个 ManuallyDrop 中
fd: ManuallyDrop<i32>,
}
// 为 File 结构体实现一些方法
impl File {
// 一个构造函数,打开一个文件并返回一个 File 实例
fn open(name: &str) -> Result<File, std::io::Error> {
// 使用 std::fs::OpenOptions 打开文件,具有读写权限
let file = std::fs::OpenOptions::new().read(true).write(true).open(name)?;
// 使用 std::os::unix::io::AsRawFd 获取文件描述符
let fd = file.as_raw_fd();
// 返回一个 File 实例,包含一个 ManuallyDrop(name) 和一个 ManuallyDrop(fd) 的字段
Ok(File {
name: ManuallyDrop::new(name.to_string()),
fd: ManuallyDrop::new(fd),
})
}
// 一个显式的析构器,关闭文件并返回任何错误
fn close(mut self) -> Result<(), std::io::Error> {
// 使用 std::mem::replace 将 name 字段替换为一个空字符串,并获取原来的值
if let name = std::mem::replace(&mut self.name, ManuallyDrop::new(String::new()));
// 使用 std::mem::replace 将 fd 字段替换为一个无效的值,并获取原来的值
if let fd = std::mem::replace(&mut self.fd, ManuallyDrop::new(-1));
// 打印文件名和文件描述符
println!("Closing file {:?} with fd {:?}", name, fd);
// 使用 std::os::unix::io::FromRawFd 将 fd 转换回 std::fs::File
let file: std::fs::File = unsafe { std::os::unix::io::FromRawFd::from_raw_fd(*fd) };
// 使用 std::fs::File::sync_all 将任何挂起的写入刷新到磁盘
file.sync_all()?;
// 使用 std::fs::File::set_len 将文件截断为零字节
file.set_len(0)?;
// 再次使用 std::fs::File::sync_all 刷新截断
file.sync_all()?;
// 丢弃 file 实例,它会自动关闭
drop(file);
// 返回 Ok(())
Ok(())
}
}
// 为 File 结构体实现 Drop trait,用于在值离开作用域时运行一些代码
impl Drop for File {
// drop 方法,接受一个可变引用到 self 作为参数
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.id) };
// 打印文件名和文件描述符
println!("Dropping file {:?} with fd {:?}", name, fd);
// 如果 fd 字段不是无效的值,说明文件还没有被关闭或丢弃,需要执行一些操作
if fd != -1 {
// 使用 std::os::unix::io::FromRawFd 将 fd 转换回 std::fs::File
let file: std::fs::File = unsafe { std::os::unix::io::FromRawFd::from_raw_fd(fd) };
// 丢弃 file 实例,它会自动关闭
drop(file);
}
}
}
// 一个测试 File 类型的主函数
fn main() {
// 创建一个名为 "test.txt" 的文件,包含一些内容
std::fs::write("test.txt", "Hello, world!").unwrap();
// 打开文件并获取一个 File 实例
let 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 = std::fs::metadata("test.txt").unwrap();
println!("File size: {} bytes", metadata.len());
}
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!