Подтвердить что ты не робот

Прочитайте файл и получите массив строк

Я хочу прочитать файл и вернуть вектор String s. Следующая функция работает, но есть ли более сжатый или идиоматический способ?

use std::fs::File;
use std::io::Read;

fn lines_from_file(filename: &str) -> Vec<String> {
    let mut file = match File::open(filename) {
        Ok(file) => file,
        Err(_) => panic!("no such file"),
    };
    let mut file_contents = String::new();
    file.read_to_string(&mut file_contents)
        .ok()
        .expect("failed to read!");
    let lines: Vec<String> = file_contents.split("\n")
        .map(|s: &str| s.to_string())
        .collect();
    lines
}

Некоторые вещи, которые кажутся мне неоптимальными:

  • Две отдельные проверки ошибок для чтения файла.
  • Чтение всего файла на String, который будет выброшен. Это было бы особенно расточительно, если бы я хотел только первые N строк.
  • Создание &str для каждой строки, которая будет выбрана, вместо того, чтобы как-то перейти прямо из файла в String в строке.

Как это можно улучшить?

4b9b3361

Ответ 1

Как сказал BurntSushi, вы можете просто использовать итератор lines(). Но, чтобы ответить на ваш вопрос как есть:

  • Вероятно, вы должны прочитать " Обработка ошибок в ржавчине"; нужно ли их развернуть unwrap() ? s, при этом результат функции становится Result<Vec<String>, E> для некоторого разумного E Здесь мы повторно используем псевдоним типа io::Result.

  • Используйте итератор lines(). Другое, что вы можете сделать, это прочитать весь файл в String и вернуть его; есть итератор lines() для строк.

  • Об этом вы ничего не можете сделать: file_contents владеет его содержимым, и вы не можете разделить их на несколько принадлежащих ему String. Единственное, что вы можете сделать, это заимствовать содержимое каждой строки, а затем преобразовать ее в новую String. Тем не менее, то, как вы это выразили, подразумевает, что вы считаете, что создание &str дорого; это не так. Это буквально просто вычисляет пару смещений и возвращает их. A &str slice фактически эквивалентен (*const u8, usize).

Здесь измененная версия, которая делает в основном одно и то же:

use std::fs::File;
use std::io::{self, BufRead};
use std::path::Path;

fn lines_from_file<P>(filename: P) -> io::Result<io::Lines<io::BufReader<File>>>
where
    P: AsRef<Path>,
{
    let file = File::open(filename)?;
    Ok(io::BufReader::new(file).lines())
}

Еще одно изменение, которое я сделал: filename теперь является общим P: AsRef<Path>, потому что тот, что File::open хочет, поэтому он будет принимать больше типов без преобразования.

Ответ 2

DK. ответ совершенно прав и имеет большое объяснение. Однако вы заявили:

Прочитайте файл и получите массив строк

Массивы ржавчины имеют фиксированную длину, известную во время компиляции, поэтому я предполагаю, что вы действительно имеете в виду "вектор". Я бы написал так:

use std::{
    fs::File,
    io::{prelude::*, BufReader},
    path::Path,
};

fn lines_from_file<P>(filename: P) -> Vec<String>
where
    P: AsRef<Path>,
{
    let file = File::open(filename).expect("no such file");
    let buf = BufReader::new(file);
    buf.lines()
        .map(|l| l.expect("Could not parse line"))
        .collect()
}

// ---

fn main() {
    let lines = lines_from_file("/etc/hosts");
    for line in lines {
        println!("{:?}", line);
    }
}
  1. Как и в другом ответе, стоит использовать общий тип для имени файла.
  2. Result::expect сокращает панику на Err.
  3. BufRead::lines обрабатывает несколько типов строк новой строки, а не только "\n".
  4. BufRead::lines также дает вам отдельно выделенные String s, а не один большой глобус.
  5. Нет причин собирать временную переменную, чтобы вернуть ее. Особенно нет причин повторять тип (Vec<String>).

Если вы хотите вернуть Result при ошибке, вы можете выполнить сквош реализации до одной строки, если хотите:

use std::{
    fs::File,
    io::{self, BufRead, BufReader},
    path::Path,
};

fn lines_from_file<P>(filename: P) -> io::Result<Vec<String>>
where
    P: AsRef<Path>,
{
    BufReader::new(File::open(filename)?).lines().collect()
}