「博文」NLL之后:函数间冲突
关键字: [nll, niko]
niko在思考Rust所有权未来进展,首先打算列出了当前所有权和借用系统的各种限制。
本文着重于阐述第一个限制:函数间冲突。
问题:
经常会有一个包含很多字段的大结构体,并非所有字段都被所有方法使用到。
use std::sync::mpsc::Sender;
struct MyStruct {
widgets: Vec<MyWidget>,
counter: usize,
listener: Sender<()>,
}
struct MyWidget { .. }
假如有一个increment方法,用来在每次发生事件的时候计数,并且给listener发送消息。
impl MyStruct {
fn signal_event(&mut self) {
self.counter += 1;
self.listener.send(()).unwrap();
}
}
当我们同时使用MyStruct的某些其他字段时,尝试调用此方法就会出现问题。
impl MyStruct {
fn check_widgets(&mut self) {
for widget in &self.widgets {
if widget.check() {
self.signal_event();
}
}
}
}
假设我们现在要检查widget,这个过程可能会产生计数事件。可惜,这段代码会产生编译错误:
error[E0502]: cannot borrow `*self` as mutable because it is also borrowed as immutable
--> src/main.rs:26:17
|
24 | for widget in &self.widgets {
| -------------
| |
| immutable borrow occurs here
| immutable borrow used here, in later iteration of loop
25 | if widget.check() {
26 | self.signal_event();
| ^^^^^^^^^^^^^^^^^^^ mutable borrow occurs here
错误消息试图在告诉你:
- 循环中正在借用self.widgets
- 然后,访问self.signal_event,而signal_event有可能改变当前正在迭代的widget。
虽然开发者知道这里不会修改到widget的widget字段,但是编译器不知道。
如果解决?
impl MyStruct {
fn check_widgets(&mut self) {
for widget in &self.widgets {
if widget.check() {
// Inline `self.signal_event()`:
self.counter += 1;
self.listener.send(()).unwrap();
}
}
}
}
就是把signal_event方法再搬过来。但是现在已经违反了DRY(Don't repeat yourself)原则。
如何改进?
struct EventSignal {
counter: usize,
listener: Sender<()>,
}
impl EventSignal {
fn signal_event(&mut self) {
self.counter += 1;
self.listener.send(()).unwrap();
}
}
提取一个独立的结构体EventSignal,并为其实现signal_event方法。然后修改MyStruct:
struct MyStruct {
widgets: Vec<MyWidget>,
signal: EventSignal,
}
impl MyStruct {
fn check_widgets(&mut self) {
for widget in &mut self.widgets {
if widget.update() {
self.signal.signal_event(); // <-- Changed
}
}
}
}
将大的结构体分解为小结构体,有时候可能会使代码更加清晰。但也有问题。比如会使代码变得冗长,以及这种方法有时候也不那么凑效。比如MyStruct中有可能出现一个方法,既使用count又使用widget。
另外一种解决方案是使用自由函数。
fn signal_event(counter:&mut usize,listener:&Sender <()>){
* counter + = 1;
listener.send(()).unwrap();
}
比如将signal_event函数变为独立函数,而不是方法,就可以避免使用&mut self。但这种方法有点极端。
也可以定义一种视图结构来解决问题,但同样是比较极端的方案:
struct CheckWidgetsView<'me> {
widgets: &'me Vec<MyWidget>,
counter: &'me mut usize,
listener: &'me mut Sender<()>,
}
impl<'me> CheckWidgetsView<'me> {
fn signal_event(&mut self) {
*self.counter += 1;
self.listener.send(()).unwrap();
}
fn check_widgets(&mut self) {
for widget in &self.widgets {
if widget.check() {
self.signal_event();
}
}
}
}
这里的魔法是,CheckWidgetsView已经把调用所需要的借用包含了。所以在check_widgets的时候,不会相互影响。
实际上,经常出现此类问题的地方是闭包。因为闭包是要捕获变量。
fn check_widgets(&mut self) {
// Make a closure that uses `self.counter`
// and `self.listener`; but it will actually
// capture all of `self`.
let signal_event = || {
self.counter += 1;
self.listener.send(()).unwrap();
};
for widget in &self.widgets {
if widget.check() {
signal_event();
}
}
}
上面的闭包signal_event虽然指定了具体的字段,但依旧会捕获整个self。现在Rust已经接受了RFC 2229来解决这个问题。
需要扩展语言本身来解决此问题,但当前还在讨论中。可以关注这个讨论贴:After NLL – what’s next for borrowing and lifetimes?
「Slides」禅与说服贵公司使用Rust的艺术
关键字: [convinced, use rust]
Rust核心团队成员Ashley Williams(@ag_bubs),同时也是crates.io、Rust社区的团队领导,WebAssembly工作组的成员分享了这篇主题。
「视频地址:」How I Convinced the World's Largest Package Manager to Use Rust, and So Can You!
「博文」Cita-cli工具开发中的异步经验谈
关键字: [cita-cli, async, tokio, future]
本文是来自 @driftluo 的投稿。
他在使用全异步开发cita-cli过程遇到了自引用的问题,但他是在稳定版Rust下开发,使用的还是futures 0.1,为了解决这个问题,他给出了一个自己的思路。
「rustsim」最后两月动态月刊#1
关键字: [rustsim, alga, nalgebra, algebra]
Rustsim组织是一个GitHub组织,聚焦于提供各种数值模拟的库。包括
- alga, 抽象代数库
- nalgebra, 线性代数库
- ncollide, 2D和3D的碰撞检测库
- nphysics, 2D和3D的物理模拟库
今年最后的两个月,在改进ncollide和nalgebra,以便支持可变性物体的物理模拟。并且新增了两个crate:nalgebra-glm和nphysics-ecs。其中nphysics-ecs是打算让开发者方便将物理系统集成到ECS应用程序中。
还将改进文档。
「研究项目」「系列文章」 Shifgrethor IV: 跟踪
关键字: [Shifgrethor, GC]
本文阐述了Shifgrethor如何跟踪对象,识别该对象是否存活。
定义Trace trait
跟踪算法原理:从根(root)开始,访问它引用的每个GC对象,并标记该对象为活跃对象,这样垃圾收集器就不会回收它。
该算法基于访问者模式。这是Rust社区普遍使用的模式,比如serde就是访问者模式。(插一句,《Rust编程之道》里也专门介绍了该模式)
pub unsafe trait Trace {
fn mark(&self);
// ...
}
所以,GC如果想跟踪某些对象,这些对象就必须实现Trace。实现Trace问题不大,但是证明GC对象是否被正确的追踪才是问题。
Proving rootedness
将这种保证跟踪是否正确的验证称为Proving rootedness。Shifgrethor的解决方案是引入新的类型GcStore。GcStore是一种没有实现Deref的gc指针。
#[derive(GC)]
struct Foo<'root> {
#[gc] bar: GcStore<'root, Bar>,
}
上面代码会生成一个名为bar的方法,其函数签名为 Gc<'root, Foo<'_>> -> Gc<'root, Bar>
,这是为了获得GC中某些内容的引用,必须始终证明是在该引用的生命周期内对其生根(rooted)
「小工具」custom_error: 提供了方便自定义Error的宏
关键字: [custom, error]
「项目」Trust-DNS Resolver 0.10和Client / Server 0.15已发布
关键字: [trust-dns, dns]
「工具」websocat发布1.2.0版本
关键字: [websocket, cli]
websocat是一个命令行websocket客户端
每日新闻订阅地址:
欢迎通过GitHub issues投稿。
评论区
写评论还没有评论