Захват проверки не понимает, что `clear` указывает ссылку на локальную переменную

Следующий код считывает записи с пространственным разделителем из 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 ed, ссылка на line не может быть сохранена, правильно?

Как я могу рассказать об этом чеку?

Ответы

Ответ 1

Единственный способ сделать это - использовать transmute, чтобы изменить Vec<&'a str> на Vec<&'b str>. transmute небезопасно, и Rust не будет вызывать ошибку, если вы забыли вызов clear здесь. Возможно, вам захочется расширить блок unsafe до вызова clear, чтобы он очистился (не каламбур), где код возвращается к "безопасной земле".

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();
    }
}

Ответ 2

В этом случае Rust не знает, что вы пытаетесь сделать. К сожалению, .clear() не влияет на то, как проверяется .extend().

cache - это "вектор строк, которые живут до тех пор, пока основная функция", но в extend() вызывает добавление "строк, которые живут только до тех пор, пока одна итерация цикла", так что тип несоответствие. Вызов .clear() не меняет типы.

Обычно такие ограничения использования времени выражаются путем создания долгоживущего непрозрачного объекта, который обеспечивает доступ к его памяти за счет заимствования временного объекта с правильным временем жизни, например RefCell.borrow() предоставляет временный объект Ref. Реализация этого будет немного задействована и потребует небезопасных методов утилизации внутренней памяти Vec.

В этом случае альтернативное решение может состоять в том, чтобы избежать любых распределений вообще (.join() также выделяет) и поток печати благодаря Peekable оболочке итератора:

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, чтобы сказать, что знаете, что делаете, и переопределить проверку на всю жизнь.

Ответ 3

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)
    }
}

)