解决Rust中‘borrowed value does not live long enough’错误的更优方案
解决Rust中"borrowed value does not live long enough"的更优方案
我在参与Advent of Code活动时编写Rust代码,遇到了borrowed value does not live long enough编译错误。目前用硬编码匹配颜色的临时方案解决,但这种方式在颜色数量增多时会非常繁琐,想寻求更优的解决办法。
原始代码
use std::collections::HashMap; use std::fs::File; use std::io::{self, BufRead}; use std::path::Path; fn main() { let mut coloured_cubes = HashMap::from([ ("red", 0), ("green", 0), ("blue", 0), ]); let mut game_nr = 0; let mut sum_of_powers = 0; let file = std::env::args().nth(1). expect("Please specify a filename"); if let Ok(lines) = read_lines(file) { for line in lines { if let Ok(mut cal_line) = line { game_nr += 1; coloured_cubes.insert("red", 0); coloured_cubes.insert("green", 0); coloured_cubes.insert("blue", 0); let prefix = format!("Game {game_nr}: "); if ! cal_line.starts_with(&prefix) { panic!("Line {game_nr} had a wrong prefix"); } cal_line.replace_range(..prefix.len(), ""); for game_part in cal_line.split("; ") { for cube in game_part.split(", ") { let cube_info: Vec<&str> = cube.split(' ').collect(); if cube_info.len() != 2 { panic!("{prefix} wrong format '{cube}'"); } let count = cube_info[0].parse::<i32>().unwrap(); let colour = cube_info[1]; if count < 1 { panic!("{} count should at least be 1 {:?}", prefix, cube_info); } if ! coloured_cubes.contains_key(colour) { panic!("{prefix} illegal colour {colour}"); } if count > coloured_cubes[colour] { let temp_colour = match colour { "red" => "red", "green" => "green", "blue" => "blue", _ => panic!("Got a wrong colour: {colour}") }; coloured_cubes.insert(temp_colour, count); } } } sum_of_powers += coloured_cubes["red"] * coloured_cubes["green"] * coloured_cubes["blue"]; } else { panic!("Problem with line"); } } } else { panic!("Did not get any lines!"); } println!("ResultSum of powers: {sum_of_powers}"); } // Functions // The output is wrapped in a Result to allow matching on errors // Returns an Iterator to the Reader of the lines of the file. fn read_lines<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()) }
当前临时方案的问题
用temp_colour变量通过match硬编码匹配颜色字符串,虽然能绕过生命周期错误,但颜色数量增多时,需要不断扩展match分支,维护成本极高,也不符合Rust的类型安全理念。
错误根源
coloured_cubes初始使用的是字符串字面量(&'static str类型,生命周期为程序整个运行期),而从cal_line.split生成的colour是指向栈上字符串的引用,生命周期仅到当前循环迭代结束(cal_line被drop时)。直接将该引用插入HashMap,会导致HashMap持有一个已失效的引用,违反Rust的内存安全规则。
更优解决方案
有两种更优雅的解决方式,均能彻底解决生命周期问题,同时提升代码的可维护性和安全性:
方案1:将HashMap的键改为拥有所有权的String
把HashMap的键类型从&str改为String,这样可以存储拥有所有权的字符串,避免引用生命周期不匹配的问题。
修改点:
- 初始化HashMap时,将字符串字面量转为String
- 插入时直接把
colour转为String
修改后的核心代码片段:
// 初始化HashMap,使用String作为键 let mut coloured_cubes = HashMap::from([ ("red".to_string(), 0), ("green".to_string(), 0), ("blue".to_string(), 0), ]); // 处理颜色插入时,直接转换为String if count > coloured_cubes[colour] { coloured_cubes.insert(colour.to_string(), count); }
方案2:使用枚举类型表示颜色(推荐)
定义一个枚举类型来表示所有合法颜色,这样不仅能解决生命周期问题,还能实现类型安全,提前在编译期检查非法颜色,减少运行时panic。
修改后的完整代码:
use std::collections::HashMap; use std::fs::File; use std::io::{self, BufRead}; use std::path::Path; use std::convert::TryFrom; #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] enum Colour { Red, Green, Blue, } impl TryFrom<&str> for Colour { type Error = &'static str; fn try_from(s: &str) -> Result<Self, Self::Error> { match s { "red" => Ok(Colour::Red), "green" => Ok(Colour::Green), "blue" => Ok(Colour::Blue), _ => Err("invalid colour"), } } } fn main() { let mut coloured_cubes = HashMap::from([ (Colour::Red, 0), (Colour::Green, 0), (Colour::Blue, 0), ]); let mut game_nr = 0; let mut sum_of_powers = 0; let file = std::env::args().nth(1) .expect("Please specify a filename"); if let Ok(lines) = read_lines(file) { for line in lines { if let Ok(mut cal_line) = line { game_nr += 1; // 重置颜色计数 coloured_cubes.insert(Colour::Red, 0); coloured_cubes.insert(Colour::Green, 0); coloured_cubes.insert(Colour::Blue, 0); let prefix = format!("Game {game_nr}: "); if !cal_line.starts_with(&prefix) { panic!("Line {game_nr} had a wrong prefix"); } cal_line.replace_range(..prefix.len(), ""); for game_part in cal_line.split("; ") { for cube in game_part.split(", ") { let cube_info: Vec<&str> = cube.split(' ').collect(); if cube_info.len() != 2 { panic!("{prefix} wrong format '{cube}'"); } let count = cube_info[0].parse::<i32>().unwrap(); let colour = Colour::try_from(cube_info[1]) .unwrap_or_else(|e| panic!("{prefix} illegal colour: {e}")); if count < 1 { panic!("{} count should at least be 1 {:?}", prefix, cube_info); } if count > coloured_cubes[&colour] { coloured_cubes.insert(colour, count); } } } sum_of_powers += coloured_cubes[&Colour::Red] * coloured_cubes[&Colour::Green] * coloured_cubes[&Colour::Blue]; } else { panic!("Problem with line"); } } } else { panic!("Did not get any lines!"); } println!("ResultSum of powers: {sum_of_powers}"); } fn read_lines<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()) }
这种方案的优势:
- 彻底消除生命周期问题,枚举是值类型,不存在引用失效风险
- 编译期检查非法颜色,无需运行时
contains_key判断 - 代码可读性和可维护性更高,新增颜色只需扩展枚举和转换逻辑
内容的提问来源于stack exchange,提问作者Cecil Westerhof
相关产品推荐
相关产品推荐

