3个稳定版本
1.1.0 | 2023年9月17日 |
---|---|
1.0.1 | 2022年4月21日 |
1.0.0 | 2019年5月22日 |
#23 in 无标准库
9,273 每月下载量
在 16 个crates(直接使用8个) 中使用
17KB
102 行
Generativity
Generativity指的是创建一个唯一的生命周期:一个Rust借用检查器不会与其他任何生命周期统一的生命周期。这可以用来标记类型,这样你知道是你而不是另一个复制品创建了它们。这对于无检查的索引和类似技巧是必需的。
实现生成性的第一步是一个不变生命周期。然后消费者不能将此生命周期与任何其他生命周期统一。
传统上,这通过闭包来实现。如果你让用户在for<'a>
回调中编写代码,那么这个回调中的本地类型检查被迫假设它可以传递任何生命周期(for<'a>
约束),并且它不可能使用另一个生命周期来替代(不变性)。
这个crate通过宏和基于Drop
的作用域保护器实现了一种不同的方法。当你调用generativity::make_guard!
来创建一个唯一的生命周期保护器时,宏首先创建一个包含不变生命周期的Id
。然后,它在一个Drop
类型中将一个&'id Id<'id>
引用放入其中。宏的不同调用具有不同的结束时间与它的标签生命周期,因此生命周期不能统一。这些类型除了通过make_guard!
外没有其他安全的方法来构造它们,因此生命周期保证了唯一性。
这实际上与将函数的其余部分包裹在一个立即调用的闭包中执行相同的事情。我们做一些预处理工作(创建标签并将保护器传递给调用者),运行用户代码(此处的代码,之前的闭包),然后运行一些清理代码(在drop实现中)。这种技术,即隐藏的宏卫生impl Drop
,可以被大多数通常使用闭包参数的API使用,从而使它们能够避免引入闭包边界来控制“效果”的流程,如async.await
和?
。但这也带来了一些细微的缺点:能够.await
意味着局部变量可能会被遗忘而不是被丢弃,因此drop粘合剂不能依赖于其运行来保证正确性。这对这个crate来说是可行的,因为实际的drop实现是一个空操作,仅用于生命周期分析的影响,但这意味着更有趣的案例,如作用域线程,仍然需要使用闭包回调来确保其正确性关键的清理代码能够执行(例如,等待并连接作用域线程)。
需要注意的是,只有当生命周期实际上是不变的时候,才信任它们携带生命周期品牌。如&'a T
这样的可变生命周期仍然可以被缩小以适应品牌生命周期;&'static T
可以在期望&'a T
的地方使用,对于任何 'a
。
它是如何工作的?
// SAFETY: The lifetime given to `$name` is unique among trusted brands.
// We know this because of how we carefully control drop timing here.
// The branded lifetime's end is bound to be no later than when the
// `branded_place` is invalidated at the end of scope, but also must be
// no sooner than `lifetime_brand` is dropped, also at the end of scope.
// Some other variant lifetime could be constrained to be equal to the
// brand lifetime, but no other lifetime branded by `make_guard!` can,
// as its brand lifetime has a distinct drop time from this one. QED
let branded_place = unsafe { $crate::Id::new() };
#[allow(unused)]
let lifetime_brand = unsafe { $crate::LifetimeBrand::new(&branded_place) };
// implicit when exiting scope: drop(lifetime_brand), as LifetimeBrand: Drop
let $name = unsafe { $crate::Guard::new(branded_place) };
这个crate的先前版本在宏中产生了更多的代码,并内联定义了LifetimeBrand
类型。这改进了当时编译器版本的编译错误,但当前编译器通过在generativity
crate中定义LifetimeBrand
可以产生同样好或更好的错误。
LifetimeBrand
类型是非公共API,不应直接使用。它不受稳定性保证的覆盖;只有对make_guard!
和其他记录的API的使用被认为是稳定的。
巨大的生成性免责声明
上面最后一点非常重要。我们不能保证生命周期是完全唯一的。我们只能保证它在受信任的载体中是唯一的。这同样适用于传统方法
fn scope<F>(f: F)
where F: for<'id> FnOnce(Guard<'id>)
{
make_guard!(guard);
f(guard);
}
fn unify<'a>(_: &'a (), _: &Guard<'a>) {
// here, you have two `'a` which are equivalent to `guard`'s `'id`
}
fn main() {
let place = ();
make_guard!(guard);
unify(&place, &guard);
scope(|guard| {
unify(&place, &guard);
})
}
其他可变生命周期仍然可以缩小以与'id
统一。这意味着你不能仅仅信任任何一个漂浮的旧'id
生命周期。它必须由一个受信任的载体携带,并且不能从一个不受信任的生命周期中创建。
实现免责声明
这依赖于无效代码(一个#[inline(always)]
无操作删除),以影响借用检查。理论上,一个足够先进的借用检查器在进行了某些内联操作后查看CFG,将能够看出删除 lifetime_brand
不需要捕获的生命周期保持活动状态,并破坏我们创建的唯一性保证。这对于高阶闭包公式来说会更困难,但如果有足够的内联,在理论上仍然可能。幸运的是,根据不稳定“借用检查眼罩”的方向,这是例如 Box<&'a T>
尽管在作用域末尾之前 &'a T
借用已被无效化,但仍可以删除,以及推断一个泛型是否被 Drop::drop
“使用”的进一步稳定性影响,似乎任何此类削弱借用检查器眼中捕获生命周期的显式 impl Drop
"使用"都将选择加入。这个软件包不会选择加入此类功能,因此将保持正确性。
最低支持的Rust版本
该软件包目前需要Rust 1.56。我没有打算提高这个软件包的编译器版本要求。然而,这仅保证在给定的次要版本号内。
许可协议
根据您的选择,受以下任一协议许可:
- Apache License,版本2.0,(LICENSE-APACHE 或 https://apache.ac.cn/licenses/LICENSE-2.0)
- MIT许可证(LICENSE-MIT 或 http://opensource.org/licenses/MIT)
任您选择。
贡献
除非您明确声明,否则,您根据Apache-2.0许可证定义的任何有意提交以包含在您的工作中的贡献,将根据上述条款双许可,没有任何额外条款或条件。