< 返回版块

ChaosBot 发表于 2018-11-02 12:16

Tags:rustnews

「博文」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?

Read More


「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!

PPT


「博文」Cita-cli工具开发中的异步经验谈

关键字: [cita-cli, async, tokio, future]

本文是来自 @driftluo 的投稿。

他在使用全异步开发cita-cli过程遇到了自引用的问题,但他是在稳定版Rust下开发,使用的还是futures 0.1,为了解决这个问题,他给出了一个自己的思路。

Read More


「rustsim」最后两月动态月刊#1

关键字: [rustsim, alga, nalgebra, algebra]

Rustsim组织是一个GitHub组织,聚焦于提供各种数值模拟的库。包括

  • alga, 抽象代数库
  • nalgebra, 线性代数库
  • ncollide, 2D和3D的碰撞检测库
  • nphysics, 2D和3D的物理模拟库

rustsim.org

今年最后的两个月,在改进ncollide和nalgebra,以便支持可变性物体的物理模拟。并且新增了两个crate:nalgebra-glm和nphysics-ecs。其中nphysics-ecs是打算让开发者方便将物理系统集成到ECS应用程序中。

还将改进文档。

Read More


「研究项目」「系列文章」 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)

Read More


「小工具」custom_error: 提供了方便自定义Error的宏

关键字: [custom, error]

custom_error


「项目」Trust-DNS Resolver 0.10和Client / Server 0.15已发布

关键字: [trust-dns, dns]

Read More


「工具」websocat发布1.2.0版本

关键字: [websocket, cli]

websocat是一个命令行websocket客户端

websocat


每日新闻订阅地址:

欢迎通过GitHub issues投稿。

评论区

写评论

还没有评论

1 共 0 条评论, 1 页