文档原文:https://docs.rs/ouroboros/latest/ouroboros/attr.self_referencing.html
属性宏ouroboros::self_referencing
#[self_referencing]
此宏用于将常规结构转换为自引用结构。举个例子:
use ouroboros::self_referencing;
#[self_referencing]
struct MyStruct {
int_data: i32,
float_data: f32,
#[borrows(int_data)]
// `this这个生命周期是由#[self_referending]宏创建的
// 并且应该用在所有由#[borrows]宏标记的引用上
int_reference: &'this i32,
#[borrows(mut float_data)]
float_reference: &'this mut f32,
}
fn main() {
// 构建器由#[self_referending]宏创建
// 并用于创建结构体
let mut my_value = MyStructBuilder {
int_data: 42,
float_data: 3.14,
// 注意构造器中的字段名称
// 是struct +'_builder'结构中的字段名
// 即:{field_name}_builder
// 为字段赋值的闭包将被传递
// 对#[borrows]宏中定义的字段的引用
int_reference_builder: |int_data: &i32| int_data,
float_reference_builder: |float_data: &mut f32| float_data,
}.build();
// 不能直接访问原始结构体中的字段
// 构造器创建的访问器调用的方法为 borrow_{field_name}()
// 打印 42
println!("{:?}", my_value.borrow_int_data());
// 打印 3.14
println!("{:?}", my_value.borrow_float_reference());
// 设置float_data值为84.0
my_value.with_mut(|fields| {
**fields.float_reference = (**fields.int_reference as f32) * 2.0;
});
// 我们可以持有这个引用...
let int_ref = *my_value.borrow_int_reference();
println!("{:?}", *int_ref);
// 只要结构体还活着
drop(my_value);
// 下面的使用是会报错的
// println!("{:?}", *int_ref);
}
通过RustRover观察创建出来的结构体包装出了以下方法:
返回值类型为:
为了解释这个crate的特点和局限性,一些定义是必要的:
定义
- 不可变借用字段:至少被一个其他字段
不可变借用
的字段。 - 可变借用字段:正好由另一个字段
可变借用
的字段。 - self-referencing field:至少借用一个其他字段的字段。
- head field:不借用任何其他字段的字段,即不自引用的字段。 这不包括具有空借用(
#[borrows()]
)的字段 - tail field:未被任何其他字段借用的字段。
使用
要创建自引用结构体,必须编写结构体定义并将#[self_referending]
置于顶部。对于每个借用其他字段的字段,必须将#[borrows()]
放在顶部,并在括号内放置它借用的字段列表。可以加上Mut前缀,以指示需要可变借用。例如,#[borrows(a, b, mut c)]
表示前两个字段需要不可变地借入,第三个字段需要可变地借入。你也可以使用不带任何参数的#[borrows()]
来表示一个字段,该字段最终将从结构中借入,但在第一次创建时不会借入任何东西。例如,你可以在一个字段上使用:error: Option<&'this str>
。
您必须遵守这些限制
- 字段必须在第一次借用之前声明。
- 正常的借用规则适用,例如,一个字段不能被借用两次。
- 使用
'this
生存期的字段必须有一个对应的#[borrows()]
注释。
违反它们将导致错误消息直接指出违反的规则。
该crate的灵活性
上面的示例使用纯引用作为结构的自引用部分,但您可以使用依赖于结构中对象生存期的任何内容。例如,您可以 做这样的事情:
use ouroboros::self_referencing;
pub struct ComplexData<'a, 'b> {
aref: &'a i32,
bref: &'b mut i32,
number: i32,
}
impl<'a, 'b> ComplexData<'a, 'b> {
fn new(aref: &'a i32, bref: &'b mut i32, number: i32) -> Self {
Self { aref, bref, number }
}
/// Copies the value aref points to into what bref points to.
fn transfer(&mut self) {
*self.bref = *self.aref;
}
/// Prints the value bref points to.
fn print_bref(&self) {
println!("{}", *self.bref);
}
}
fn main() {
#[self_referencing]
struct DataStorage {
immutable: i32,
mutable: i32,
#[borrows(immutable, mut mutable)]
#[not_covariant]
complex_data: ComplexData<'this, 'this>,
}
let mut data_storage = DataStorageBuilder {
immutable: 10,
mutable: 20,
complex_data_builder: |i: &i32, m: &mut i32| ComplexData::new(i, m, 12345),
}.build();
data_storage.with_complex_data_mut(|data| {
// Copies the value in immutable into mutable.
data.transfer();
// Prints 10
data.print_bref();
});
}
协变性
在Rust语言中,许多类型具有一种称为“协变性”的属性。实际上,这意味着像Box<&'this i32>这样的协变类型可以用作Box<&'a i32>,只要’a比’this小。由于生命周期更短,因此它不会违反原始类型指定的生命周期。这与Fn(&'this i32)不同,它不是协变的。你不能给这个函数一个生命周期短于’this的引用,因为函数需要至少与’this一样长的东西。不幸的是,从宏内部无法轻易确定一个类型是否具有协变性。因此,您可能会收到一个编译器错误,让您知道宏不确定一个特定字段是否使用了协变类型。添加#[covariant]或#[not_covariant]可以解决此问题。
这些注解控制是否为该字段生成borrow_*方法。错误地使用这些标签将导致编译错误。它们不可能被不安全地使用。
生成的项目列表
MyStruct::new(fields...) -> MyStruct
基本构造函数。它按照您在中声明的顺序接受每个字段的值。对于head字段,你只需要传入它应该有的值,它将被移动到输出中。对于自引用字段,您必须提供一个函数或闭包,它根据它借用的值来创建值。使用前面的示例#[借入(a,b,mut c)]的字段将需要一个类型为FnOnce(a: &, b: &, c: &mut _) -> _的函数。具有空借用注释(#[借入()])的字段应将其值直接传递给。使用前面的Option<&'this str>示例的字段将要求输入None。不要传递函数。不收200元。
MyStruct::new_async(fields...) -> MyStruct
MyStruct::new_async_send(fields...) -> MyStruct
MyStructBuilder
MyStructAsyncBuilder
MyStructAsyncSendBuilder
MyStruct::try_new<E>(fields...) -> Result<MyStruct, E>
MyStruct::try_new_async<E>(fields...) -> Result<MyStruct, E>
MyStruct::try_new_async_send<E>(fields...) -> Result<MyStruct, E>
MyStruct::try_new_or_recover_async<E>(fields...) -> Result<MyStruct, (E, Heads)>
MyStruct::try_new_or_recover_async_send<E>(fields...) -> Result<MyStruct, (E, Heads)>
MyStruct::with_FIELD<R>(&self, user: FnOnce(field: &FieldType) -> R) -> R
MyStruct::borrow_FIELD(&self) -> &FieldType
MyStruct::with_FIELD_mut<R>(&mut self, user: FnOnce(field: &mut FieldType) -> R) -> R
MyStruct::with<R>(&self, user: FnOnce(fields: AllFields) -> R) -> R
MyStruct::with_mut<R>(&self, user: FnOnce(fields: AllFields) -> R) -> R
MyStruct::into_heads(self) -> Heads