读取文件并获取字符串数组

Nat*_*ong 19 idiomatic rust

我想读取一个文件并找回Strings 的向量.以下功能有效,但有更简洁或惯用的方式吗?

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
}
Run Code Online (Sandbox Code Playgroud)

有些事情对我来说似乎不是最理想的:

  • 两个单独的错误检查用于读取文件.
  • 将整个文件读取到a String,将被丢弃.如果我只想要前N行,这将特别浪费.
  • 制作一条&str将被丢弃的每行,而不是以某种方式直接从文件到String每行.

如何改进?

DK.*_*DK. 14

作为BurntSushi说,你可以只使用lines()迭代器.但是,按原样解决您的问题:

  • 您应该阅读Rust中的错误处理 ; 那些unwrap()s应该变成?s,函数的结果变得Result<Vec<String>, E>合理E.在这里,我们重用io::Result类型别名.

  • 使用lines()迭代器.你可以做的另一件事是将整个文件读入a String 并返回 ; 还有一个lines()字符串的迭代器.

  • 这个你不能做任何事情:file_contents拥有它的内容,你不能将它们分成多个拥有String的.你唯一能做的就是借用每一行的内容,然后将其转换成新的String.也就是说,你这样做的方式意味着你相信创造一个&str是昂贵的; 事实并非如此.它实际上只是计算一对偏移量并返回它们.一个&str片实际上等同于(*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())
}
Run Code Online (Sandbox Code Playgroud)

我做的另一个改变是:filename现在是通用的P: AsRef<Path>,因为这是File::open想要的,所以它将接受更多类型而不需要转换.

  • @NathanLong使用`unwrap`或`panic!'意味着如果文件不可读,整个线程就会爆炸,调用者会在没有警告的情况下死掉.如果调用者不关心这个问题,他们可以在结果上调用`unwrap`并获得相同的爆炸行为.*或*他们实际上可以决定如何处理错误.无论哪种方式,它对读取文件没有影响:两者都会导致函数以某种方式停止执行. (4认同)
  • 您可以添加调用 `lines_from_file` 的示例吗? (2认同)

She*_*ter 14

DK.的答案非常正确并且有很好的解释.但是,你说:

读取文件并获取字符串数组

Rust数组有一个固定长度,在编译时已知,所以我假设你的意思是"vector".我会这样写:

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

fn lines_from_file(filename: impl AsRef<Path>) -> Vec<String> {
    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);
    }
}
Run Code Online (Sandbox Code Playgroud)
  1. 与另一个答案一样,使用实现AsRef文件名的泛型类型是值得的.
  2. Result::expect缩短恐慌情绪Err.
  3. BufRead::lines处理多种类型的换行,而不仅仅是"\n".
  4. BufRead::lines还为你提供了单独分配的Strings,而不是一个大的glob.
  5. 没有理由收集临时变量只是为了返回它.特别没有理由重复类型(Vec<String>).

如果你想要返回Result失败,你可以根据需要将实现压缩到一行:

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

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

// ---

fn main() {
    let lines = lines_from_file("/etc/hosts").expect("Could not load lines");
    for line in lines {
        println!("{:?}", line);
    }
}
Run Code Online (Sandbox Code Playgroud)