调用在循环中使用“&mut self.0”的函数 (E0499)

Call a function that uses `&mut self.0` in a loop (E0499)

提问人:Jacob Birkett 提问时间:10/26/2023 最后编辑:Jacob Birkett 更新时间:10/29/2023 访问量:80

问:

我正在寻找一种解决这种特定情况下缺乏 polonius 问题的方法。据我所知,其他答案似乎不适用。

我有两个结构,一个.前者是解耦的,但后者与前者高度耦合。 应该由任何 构造,并且应该由相同的构造。SourceBytes<S>SourceCharsSourceBytes<S>S: Iterator<Item = u8>SourceCharsS: Iterator<Item = u8>

以下是每个定义的样子:

#[derive(Clone, Debug)]
pub struct SourceBytes<S>
where
    S: Iterator<Item = u8>,
{
    iter: S,
    buffer: Vec<S::Item>,
}

#[derive(Clone, Debug)]
pub struct SourceChars<S>(S)
where
    S: Iterator<Item = u8>;

其目的是抽象化,以便可以缓冲每个项目,并且不可变地读取,而无需从迭代器中获取/弹出项目。它看起来像这样:SourceBytes<S>SS::Item

impl<S> Iterator for SourceBytes<S>
where
    S: Iterator<Item = u8>,
{
    type Item = S::Item;

    fn next(&mut self) -> Option<Self::Item> {
        self.buffer.pop().or_else(|| self.iter.next())
    }
}

这工作正常,缓冲区的处理方式如下:

impl<S> SourceBytes<S>
where
    S: Iterator<Item = u8>,
{
    // pub fn new<I>(iter: I) -> Self
    // where
    //     I: IntoIterator<Item = S::Item, IntoIter = S>,
    // {
    //     Self {
    //         iter: iter.into_iter(),
    //         buffer: Vec::new(),
    //     }
    // }

    fn buffer(&mut self, count: usize) -> Option<&[u8]> {
        if self.buffer.len() < count {
            self.buffer
                .extend(self.iter.by_ref().take(count - self.buffer.len()));
        }
        self.buffer.get(0..count)
    }
}

因此,每次调用时,项目都将从中获取并推送到 。每次调用时,它都会首先从 中获取,然后从后一个字段的类型中获取。SourceBytes<S>::bufferSbuffer<SourceBytes as Iterator>::nextself.bufferself.iterS

现在,目的是提供一个接口来读取字节(这是 ),直到它找到有效的 UTF-8 ,然后返回它:SourceChars<S>Iteratorself.0Schar

impl<S> Iterator for SourceChars<S>
where
    S: Iterator<Item = u8>,
{
    type Item = char;

    fn next(&mut self) -> Option<Self::Item> {
        let mut buf = [0; 4];
        // A single character can be at most 4 bytes.
        for (i, byte) in self.0.by_ref().take(4).enumerate() {
            buf[i] = byte;
            if let Ok(slice) = std::str::from_utf8(&buf[..=i]) {
                return slice.chars().next();
            }
        }
        None
    }
}

这也很好用。

现在,我还希望提供一个 for,以便可以依赖 提供的缓冲区(在这种情况下,它是 )。implSourceChars<&mut SourceBytes<S>>SourceCharsself.0&mut SourceBytes<S>

impl<S> SourceChars<&mut SourceBytes<S>>
where
    S: Iterator<Item = u8>,
{
    fn buffer(&mut self, count: usize) -> Option<&str> {
        // let mut src = self.0.by_ref();
        for byte_count in 0.. {
            let Some(buf) = self.0.buffer(byte_count) else {
                return None;
            };
            if let Ok(slice) = std::str::from_utf8(buf) {
                if slice.chars().count() >= count {
                    return Some(slice);
                }
            }
        }
        unreachable!()
    }
}

这依赖于实际缓冲字节,而是充当包装器,将迭代器的解释从 bytes 更改为 s。SourceChars<&mut SourceBytes<S>>::bufferSourceBytes<S>::bufferSourceCharsSchar

问题在于不能多次可变地借用,并且在循环中,编译器似乎不会删除引用。self.0&mut self.0

我怎样才能在不遇到此编译器错误的情况下以依赖的方式实现它?SourceCharsSourceBytes::buffer

error[E0499]: cannot borrow `*self.0` as mutable more than once at a time
   --> src/parser/iter.rs:122:29
    |
119 |     fn buffer(&mut self, count: usize) -> Option<&str> {
    |               - let's call the lifetime of this reference `'1`
...
122 |             let Some(buf) = self.0.buffer(byte_count) else {
    |                             ^^^^^^ `*self.0` was mutably borrowed here in the previous iteration of the loop
...
127 |                     return Some(slice);
    |                            ----------- returning this value requires that `*self.0` is borrowed for `'1`
Rust 参考 生存期 可变

评论

0赞 cdhowie 10/26/2023
这是借用检查器中的一个已知限制(参见为什么 rust 认为借用在其他分支中处于活动状态)。
0赞 Jacob Birkett 10/26/2023
@cdhowie我试图自己找到解决方法,但你有什么建议吗?我想为我未来的自己提供这个 API,我知道我会经常使用它,我的整个设计都取决于此。因此,如果有一种不安全的方法可以做到这一点,我全神贯注,但我不想在没有指导的情况下尝试编写不安全的代码。每次我这样做,都会有人告诉我这是错误的。play.rust-lang.org/......polonius-the-crab
0赞 Chayim Friedman 10/26/2023
缓冲区不应该是,你应该从头开始弹出吗?VecDeque
0赞 Jacob Birkett 10/26/2023
@ChayimFriedman 刚刚发现这个错误:(感谢您及时给我解决方案!
0赞 Jacob Birkett 10/26/2023
@ChayimFriedman 与 不同。后者取 a,前者取 .这使我的 impl 复杂化。你知道很多吗?为什么每个签名都不同?VecDeque::getVec::getRangeusizeVecDequeget

答:

4赞 Chayim Friedman 10/26/2023 #1

解决方法与所有 Polonius 问题一样:重复计算。它的效率较低,但它有效。

impl<S> SourceChars<&mut SourceBytes<S>>
where
    S: Iterator<Item = u8>,
{
    fn buffer(&mut self, count: usize) -> Option<&str> {
        // let mut src = self.0.by_ref();
        for byte_count in 0.. {
            let Some(buf) = self.0.buffer(byte_count) else {
                return None;
            };
            if let Ok(slice) = std::str::from_utf8(buf) {
                if slice.chars().count() >= count {
                    return Some(std::str::from_utf8(self.0.buffer(byte_count).unwrap()).unwrap());
                }
            }
        }
        unreachable!()
    }
}

评论

0赞 Jacob Birkett 10/26/2023
虽然这不是我喜欢的解决方案,但它是一个有效的解决方案。谢谢。
2赞 Jmb 10/26/2023
请注意,第二次使用可能会提高效率,因为您已经知道缓冲区包含有效的 UTF8。from_utf8_unchecked
0赞 JMAA 10/26/2023
并不是说完全有可能通过一些优化过程来删除重复项。 cargo-show-asm 是一个很好的工具,可以准确检查生成的内容
0赞 Jacob Birkett 10/29/2023 #2

我之前尝试过的一个选项是 crate polonius-the-crab,但这最终导致了 API 的使用出现更多问题,此外还使特征边界难以正确。

由于这种不便,我最终使用了一个不安全的指针强制来缩短 的生存期,使其不再依赖于 .buf&mut SourceBytes

impl<S> Buffered for SourceChars<&mut S>
where
    for<'a> S: Iterator<Item = u8> + Buffered<ItemSlice<'a> = &'a [u8]> + 'a,
{
    type ItemSlice<'items> = &'items str where Self: 'items;

    // Allowed specifically here because the borrow checker is incorrect.
    #[allow(unsafe_code)]
    fn buffer(&mut self, count: usize) -> Option<Self::ItemSlice<'_>> {
        for byte_count in 0.. {
            let buf = self.0.buffer(byte_count)?;
            // SAFETY:
            //
            // This unsafe pointer coercion is here because of a limitation
            // in the borrow checker. In the future, when Polonius is merged as
            // the de-facto borrow checker, this unsafe code can be removed.
            //
            // The lifetime of the byte slice is shortened to the lifetime of
            // the return value, which lives as long as `self` does.
            //
            // This is referred to as the "polonius problem",
            // or more accurately, the "lack-of-polonius problem".
            //
            // <https://github.com/rust-lang/rust/issues/54663>
            let buf: *const [u8] = buf;
            let buf: &[u8] = unsafe { &*buf };

            if let Ok(slice) = std::str::from_utf8(buf) {
                if slice.chars().count() >= count {
                    return Some(slice);
                }
            }
        }
        unreachable!()
    }
}

此外,以下是显示 API 使用情况的测试。使用板条箱无法解决我在实施这些测试时遇到的一些生命周期问题。polonius-the-crab

#[cfg(test)]
mod tests {
    use super::{Buffered, SourceBytes, SourceChars};

    #[test]
    fn test_source_chars() {
        let source = "abcdefg";
        let chars = SourceChars::new(source.bytes());
        assert_eq!(source, chars.collect::<String>());
    }

    #[test]
    fn test_source_chars_buffer() {
        let source = "abcdefg";
        let mut bytes = SourceBytes::new(source.bytes());
        let mut chars = SourceChars::new(&mut bytes);
        // Ensure that the `buffer` function works.
        assert_eq!(&source[0..3], chars.buffer(3).unwrap());
        // Ensure that the characters are taken from the buffer,
        // and that `buffer` correctly preserves them.
        assert_eq!(&source[0..4], chars.by_ref().take(4).collect::<String>());
        // Ensure that the iterator has been advanced.
        assert_eq!(&source[4..7], chars.buffer(3).unwrap());
    }
}