如何实现通用穿插可能的突变

How to implement generic intersperse with mutations possible

目标

创建 function/macro,它有一个像这样的 api:

fn writesperse(
    buf: &mut String,
    items: impl IntoIterator<Item=impl fmt::Display>,
    sep: impl fmt::Display,
) -> fmt::Result {
    // intersperse impl elided
}

此 api 的主要使用者是类似于以下的结构:

use std::fmt;

// Drives building of query
struct QueryBuilder<'a> {
    buf: String,
    data: &'a Data,
    state: State,
}

impl<'a> QueryBuilder<'a> {
    // example method showing how writesperse might be used
    fn f(&mut self) -> fmt::Result {
        writesperse(
            &mut self.buf,
            self.data.names().map(|n| self.state.resolve(n)),
            ", ",
        )
    }
}

// Represents mutable container for computed values
struct State;
impl State {
    fn resolve(&mut self, _name: &str) -> &StateRef {
        // mutate state if name has not been seen before (elided)
        &StateRef
    }
}

// Represents example computed value
struct StateRef;
impl fmt::Display for StateRef {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "STATEREF")
    }
}

// Immutable container with various collections of objects
struct Data;
impl Data {
    // example iterator of references to some owned data
    fn names(&self) -> impl Iterator<Item=&str> {
        ::std::iter::once("name")
    }

    // another iterator of a different references
    fn items(&self) -> impl Iterator<Item=&DataRef> {
        ::std::iter::once(&DataRef)
    }
}

// Represents some type Data might own
struct DataRef;

错误

error[E0495]: cannot infer an appropriate lifetime for autoref due to conflicting requirements
  --> src/lib.rs:13:50
   |
13 |             self.data.names().map(|n| self.state.resolve(n)),
   |                                                  ^^^^^^^
   |
note: first, the lifetime cannot outlive the lifetime '_ as defined on the body at 13:35...
  --> src/lib.rs:13:35
   |
13 |             self.data.names().map(|n| self.state.resolve(n)),
   |                                   ^^^^^^^^^^^^^^^^^^^^^^^^^
note: ...so that reference does not outlive borrowed content
  --> src/lib.rs:13:39
   |
13 |             self.data.names().map(|n| self.state.resolve(n)),
   |                                       ^^^^^^^^^^
note: but, the lifetime must be valid for the method call at 13:13...
  --> src/lib.rs:13:13
   |
13 |             self.data.names().map(|n| self.state.resolve(n)),
   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
note: ...so that a type/lifetime parameter is in scope here
  --> src/lib.rs:13:13
   |
13 |             self.data.names().map(|n| self.state.resolve(n)),
   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

我试过的

到目前为止,我唯一的成功是在需要的每个站点手动执行散布逻辑。

let mut iter = some_iter.into_iter();
if let Some(i) = iter.next() {
    // do any state mutation here so mutable reference is released
    let n = self.state.resolve(n);
    write!(&mut self.buf, "{}", n)?;
}

for i in iter {
    // do same thing above
}

如果我尝试使 State::resolve 不可变(这意味着我需要预先计算不可取的值),我会得到一个不同的错误。

error[E0502]: cannot borrow `self` as immutable because it is also borrowed as mutable
  --> src/lib.rs:13:35
   |
11 |         writesperse(
   |         ----------- mutable borrow later used by call
12 |             &mut self.buf,
   |             ------------- mutable borrow occurs here
13 |             self.data.names().map(|n| self.state.resolve(n)),
   |                                   ^^^ ---- second borrow occurs due to use of `self` in closure
   |                                   |
   |                                   immutable borrow occurs here

这个错误比较容易理解。但是,我不明白为什么不允许我尝试做的事情。为什么我不能同时分发对 QueryBuilder 的 buf 的可变引用和对 State and/or Data 中对象的引用的迭代器?

最终,我的首要任务是将散布逻辑抽象到某个需要 Iterator<Item=fmt::Display> 的函数或宏中。如果此迭代器可能会改变状态和 return 对其数据的引用,那将是一个额外的好处。不过,我认为这是不可能的,至少从我对流式迭代器板条箱的理解来看是这样。

感谢您的帮助!

writesperse 不是这里的问题,resolve 是。

因为它需要 &mut self 和 return 一个生命周期绑定到 self 的引用,所以不能再次调用 resolve 除非从第一个电话已挂断。您可以在这个简化的 f 中看到这一点(穿插编译器错误):

// error[E0499]: cannot borrow `self.state` as mutable more than once at a time
fn f(&mut self) {
    let a = self.state.resolve("alec");
    //      ---------- first mutable borrow occurs here
    let _b = self.state.resolve("brian");
    //       ^^^^^^^^^^ second mutable borrow occurs here
    println!("{}", a);
    //             - first borrow later used here
}

Iterator 的部分契约是它不产生内部引用。所以 |n| self.state.resolve(n) 根本不是可以传递给 Iterator::map.

的闭包

修复resolve

如果 resolve 使用 &self 而不是 &mut self,这会起作用,因为闭包不需要专门借用 self.state;它可以 return 引用原始生命周期而不用担心重叠。那么让我们试试看:

fn resolve(&self, _name: &str) -> &StateRef {
    // some kind of interior mutability thing here, probably have to return
    // `std::cell:Ref<StateRef>` or `MutexGuard<StateRef>` instead, but that
    // doesn't matter for this demonstration
    &StateRef
}

Oh dear.

error[E0502]: cannot borrow `self` as immutable because it is also borrowed as mutable
  --> src/lib.rs:23:35
   |
21 |         writesperse(
   |         ----------- mutable borrow later used by call
22 |             &mut self.buf,
   |             ------------- mutable borrow occurs here
23 |             self.data.names().map(|n| self.state.resolve(n)),
   |                                   ^^^ ---- second borrow occurs due to use of `self` in closure
   |                                   |
   |                                   immutable borrow occurs here

这是怎么回事?因为闭包在需要不可变引用的上下文中使用 self,所以闭包借用了 self,并且 &mut self.buf 也可变地借用了 self 的(部分),并且它们两者必须同时存在才能传递到 writesperse。所以这看起来像是一个死胡同,但它实际上非常接近:它只需要对 f 进行小的更改即可编译。

fn f(&mut self) -> fmt::Result {
    let state = &self.state;
    writesperse(
        &mut self.buf,
        self.data.names().map(|n| state.resolve(n)),
        ", ",
    )
}

只要它们都发生在单个函数体内,编译器就可以推断出相互排斥的部分借用。只要借用发生在同一个函数中,编译器就会让你同时借用 self.state 和可变地借用 self.buf。创建一个变量来借用 self.state 使得闭包只捕获 state,而不是 self.

其他选项

如果你能让 resolve 取得 &self,以上方法有效。这里有一些其他的想法:

  • 你可以从另一个方向解决这个问题:按值制作 resolve return StateRef 这样它就不会扩展 &mut 借用。
  • 您可以通过更改闭包来实现相同的目的,这样 就不会 return 引用; |n| state.resolve(n).to_string() 无需进一步更改即可工作(但它确实做了一堆不必要的分配和复制)。
  • 您可以使用 ArcRc 代替 & 引用并将所有生命周期管理推迟到运行时。
  • 您可以编写一个宏来为您完成重复的部分,这样 writesperse 就不是必需的了。

另见

  • 如果你需要使用内部可变性来使 resolve&self
  • Mutable borrow of self doesn't change to immutable 解释了为什么 stateresolve returns
  • 之后仍然被认为是 "mutably borrowed"