如何在Rust的for循环中读取文件行?实现C同款电影文件读取
在Rust中实现读取电影文件的功能
我已用C语言实现读取Movies.txt文件的功能,文件内每条电影条目包含name和rating两个属性。现需用Rust实现相同功能,以下是我的C语言代码、Rust尝试代码以及文件内容:
C语言实现代码
#include <cstdio> #include <cstdlib> void read_file() { FILE* movies = fopen("Movies.txt", "r"); if (movies == nullptr) { perror("Error opening Movies.txt"); exit(EXIT_FAILURE); } // contains only 4 entries as of now for (int i = 0; i < 4; ++i) { char name[1024]{}; if (fscanf(movies, "%*[^=]=%s", name) == EOF) { perror("Error in reading Movies.txt"); printf("in \"%s: %s\" at line %d\n", __FILE__, __func__, __LINE__); exit(EXIT_FAILURE); } float rating{}; if (fscanf(movies, "%*[^=]=%f", &rating) == EOF) { perror("Error in reading Movies.txt"); printf("in \"%s: %s\" at line %d\n", __FILE__, __func__, __LINE__); exit(EXIT_FAILURE); } printf("Movie Name: %s\tRating: %f\n", name, rating); } fclose(movies); } int main() { read_file(); }
我的Rust尝试代码
use std::fs::File; use regex::Regex; use std::io::{BufRead, BufReader}; fn read_file() { let file = File::open("Movies.txt"); let buff_reader; match file { Ok(mut file) => buff_reader = BufReader::new(file), _err => return, } for (_, line) in buff_reader.lines().enumerate() { for i in [0..4] { let line = line.unwrap(); let reg_ex = Regex::new(r"^[^=]+\s*=\s*(?<name>\w+)$").unwrap(); let Some(caps) = reg_ex.captures(&line) else { println!("no match!"); return; }; // want to read next line... but how? // let line = line.unwrap(); // let reg_ex = Regex::new(r"^[^=]+\s*=\s*(?<rating>\w+)$").unwrap(); // let Some(caps) = reg_ex.captures(&line) else { // println!("no match!"); // return; // }; // println!("Movie Name: {}\tRating: {}", name, rating); } } } fn main() { read_file(); }
Movies.txt文件内容
Movie name: Harry Potter1 rating: 8.5 Movie name: Harry Potter2 rating: 9.5 Movie name: Harry Potter3 rating: 8 Movie name: Harry Potter4 rating: 8
Rust正确实现方案
不需要嵌套循环,直接通过迭代器每次读取两行(对应一个电影的名字和评分)即可。以下是更简洁且鲁棒的实现:
use std::fs::File; use std::io::{BufRead, BufReader, Error}; fn read_file() -> Result<(), Error> { // 打开文件,失败则返回错误 let file = File::open("Movies.txt")?; let reader = BufReader::new(file); // 将行转换为迭代器 let mut lines = reader.lines(); // 每次读取两行:名字行 + 评分行 while let (Some(name_line_res), Some(rating_line_res)) = (lines.next(), lines.next()) { // 处理行读取错误 let name_line = name_line_res?; let rating_line = rating_line_res?; // 提取电影名:去除前缀"Movie name: " let name = name_line.strip_prefix("Movie name: ").ok_or_else(|| { Error::new(std::io::ErrorKind::InvalidData, "格式错误:无法解析电影名称行") })?; // 提取评分:去除前缀"rating: "并转换为浮点数 let rating_str = rating_line.strip_prefix("rating: ").ok_or_else(|| { Error::new(std::io::ErrorKind::InvalidData, "格式错误:无法解析评分行") })?; let rating: f32 = rating_str.parse()?; // 输出结果 println!("Movie Name: {}\tRating: {}", name, rating); } Ok(()) } fn main() { // 统一处理所有错误 if let Err(e) = read_file() { eprintln!("读取文件出错:{}", e); } }
关键说明
- 迭代器读取两行:通过
lines.next()连续调用两次,每次获取一个电影的名字行和评分行,无需硬编码循环次数(比如C语言中的4次),自动处理到文件结束。 - 简单解析方式:利用
strip_prefix直接去除固定前缀,比正则表达式更高效且易维护。 - 完善的错误处理:使用
?传播错误,在main函数中统一捕获并输出错误信息,避免直接unwrap导致程序崩溃。 - 类型安全:将评分字符串转换为
f32类型,确保类型正确性。
如果一定要使用正则表达式,也可以修改解析部分,比如:
// 需要在Cargo.toml中添加依赖:regex = "1.10", lazy_static = "1.4" use regex::Regex; use lazy_static::lazy_static; // 预编译正则表达式,避免重复编译开销 lazy_static! { static ref NAME_REGEX: Regex = Regex::new(r"^Movie name: (?P<name>.+)$").unwrap(); static ref RATING_REGEX: Regex = Regex::new(r"^rating: (?P<rating>\d+(\.\d+)?)$").unwrap(); } // 在read_file函数内替换解析部分 let name = NAME_REGEX.captures(&name_line) .and_then(|caps| caps.name("name")) .map(|m| m.as_str()) .ok_or_else(|| Error::new(std::io::ErrorKind::InvalidData, "格式错误:无法解析电影名称行"))?; let rating = RATING_REGEX.captures(&rating_line) .and_then(|caps| caps.name("rating")) .map(|m| m.as_str().parse::<f32>()) .ok_or_else(|| Error::new(std::io::ErrorKind::InvalidData, "格式错误:无法解析评分行"))??;
内容的提问来源于stack exchange,提问作者Harry
相关产品推荐
相关产品推荐

