首页 文章

借用检查器没有意识到`clear`会丢弃对局部变量的引用

提问于
浏览
14

以下代码从stdin读取空格分隔的记录,并将逗号分隔的记录写入stdout . 即使使用优化的构建,它也相当慢(大约是使用的两倍,比如awk) .

use std::io::BufRead;

fn main() {
    let stdin = std::io::stdin();
    for line in stdin.lock().lines().map(|x| x.unwrap()) {
        let fields: Vec<_> = line.split(' ').collect();
        println!("{}", fields.join(","));
    }
}

一个明显的改进是使用 itertools 加入而不分配向量( collect 调用导致分配) . 但是,我尝试了一种不同的方法:

fn main() {
    let stdin = std::io::stdin();
    let mut cache = Vec::<&str>::new();
    for line in stdin.lock().lines().map(|x| x.unwrap()) {
        cache.extend(line.split(' '));
        println!("{}", cache.join(","));
        cache.clear();
    }
}

此版本尝试反复使用相同的向量 . 不幸的是,编译器抱怨:

error: `line` does not live long enough
 --> src/main.rs:7:22
  |
7 |         cache.extend(line.split(' '));
  |                      ^^^^
  |
note: reference must be valid for the block suffix following statement 1 at 5:39...
 --> src/main.rs:5:40
  |
5 |     let mut cache = Vec::<&str>::new();
  |                                        ^
note: ...but borrowed value is only valid for the for at 6:4
 --> src/main.rs:6:5
  |
6 |     for line in stdin.lock().lines().map(|x| x.unwrap()) {
  |     ^

error: aborting due to previous error

这当然有意义: line 变量仅在 for 循环的主体中存活,而 cache 在迭代中保持指针 . 但是这个错误对我来说仍然是虚假的:因为每次迭代后缓存都是 clear ,所以不能保留对 line 的引用,对吧?

我怎样才能告诉借阅检查员?

3 回答

  • 8

    唯一的方法是使用transmuteVec<&'a str> 更改为 Vec<&'b str> . transmute 是不安全的,如果忘记调用 clear ,Rust不会引发错误 . 您可能希望在调用 clear 之后将 unsafe 块扩展到清除(没有双关语),代码返回到"safe land" .

    use std::io::BufRead;
    use std::mem;
    
    fn main() {
        let stdin = std::io::stdin();
        let mut cache = Vec::<&str>::new();
        for line in stdin.lock().lines().map(|x| x.unwrap()) {
            let cache: &mut Vec<&str> = unsafe { mem::transmute(&mut cache) };
            cache.extend(line.split(' '));
            println!("{}", cache.join(","));
            cache.clear();
        }
    }
    
  • 8

    在这种情况下,Rust没有尝试做 . 不幸的是, .clear() 不会影响如何检查 .extend() .

    cache 是"vector of strings that live as long as the main function",但在 extend() 中调用're appending 1270129 , so that'是类型不匹配 . 对 .clear() 的调用不会更改类型 .

    通常这种限时使用是通过制作一个长寿命的不透明对象来表示的,该对象通过借用具有正确生命周期的临时对象来访问其内存,如 RefCell.borrow() 给出一个临时的 Ref 对象 . 这样做的实现有点涉及,并且需要不安全的方法来回收 Vec 的内部存储器 .

    在这种情况下,替代解决方案可以是完全避免任何分配( .join() 也分配)并通过 Peekable iterator包装器流式传输:

    for line in stdin.lock().lines().map(|x| x.unwrap()) {
        let mut fields = line.split(' ').peekable();
        while let Some(field) = fields.next() {
            print!("{}", field);
            if fields.peek().is_some() {
                print!(",");
            }
        }
        print!("\n");
    }
    

    顺便说一下:弗朗西斯回答 transmute 也很好 . 您可以使用 unsafe 表示您知道自己在做什么并覆盖终身检查 .

  • 5

    Itertools有.format()用于延迟格式化,它也会跳过分配字符串 .

    use std::io::BufRead;
    use itertools::Itertools;
    
    fn main() {
        let stdin = std::io::stdin();
        for line in stdin.lock().lines().map(|x| x.unwrap()) {
            println!("{}", line.split(' ').format(","));
        }
    }
    

    (一个题外话,这样的东西是一个“安全的抽象”,在另一个答案的最小解决方案中:

    fn repurpose<'a, T: ?Sized>(mut v: Vec<&T>) -> Vec<&'a T> {
        v.clear();
        unsafe {
            transmute(v)
        }
    }
    

相关问题