如何用#[from]将nom解析错误转换为thiserror自定义错误变体?
你遇到的编译错误核心是生命周期不匹配:
你的MyError里定义的ParseError变体持有nom::Err<nom::error::Error<&'static str>>,但parse_something的输入是普通的&str(非'static生命周期)。nom解析失败时,错误会持有输入字符串的引用,这个引用的生命周期和输入参数一致,远短于'static。当你用#[from]自动转换错误时,这个短生命周期的引用会被强行塞进需要'static引用的错误类型里,触发编译器的“借用数据逃逸”检查。
根据你的需求,有两种常用处理方式:
方式一:给错误类型添加生命周期(简单但带生命周期约束)
调整MyError的定义,让它的生命周期和输入字符串匹配,这样#[from]就能正常工作:
use thiserror::Error; // 注意原代码里的`error`是笔误,应该是`Error` use nom::number::complete::i32; pub fn parse_something(input: &str) -> Result<i32, MyError<'_>> { let (_, number) = i32(input)?; Ok(number) } #[derive(Error, Debug, PartialEq)] pub enum MyError<'a> { #[error("Parsing error in the input")] ParseError(#[from] nom::Err<nom::error::Error<&'a str>>), }
这种方式不需要手动转换错误,但MyError会带有生命周期参数,如果你需要将错误存储到结构体中或者跨线程传递,会有额外的约束。
方式二:使用拥有所有权的错误类型(无生命周期约束)
如果希望错误类型不依赖生命周期,可以把nom错误中的引用转换成拥有所有权的String,此时需要手动处理错误转换(可以封装成工具函数简化代码):
use thiserror::Error; use nom::{Err, error::Error as NomError, number::complete::i32, Needed}; pub fn parse_something(input: &str) -> Result<i32, MyError> { i32(input) .map(|(_, num)| num) .map_err(|err| match err { Err::Error(e) => MyError::ParseError(NomError::new(e.input.to_string(), e.code)), Err::Failure(e) => MyError::ParseError(NomError::new(e.input.to_string(), e.code)), Err::Incomplete(needed) => MyError::Incomplete(needed), }) } #[derive(Error, Debug, PartialEq)] pub enum MyError { #[error("Parsing error in the input: {0}")] ParseError(NomError<String>), #[error("Incomplete input, need more data: {0}")] Incomplete(Needed), }
这种方式的错误类型是“独立”的,没有生命周期限制,适合大多数场景。
#[from]在这里失效 #[from]宏的作用是自动为你的错误类型实现From trait,从而支持?运算符的自动转换。但它要求源错误类型能够安全地转换成目标错误类型——而nom::Err<Error<&'a str>>到nom::Err<Error<&'static str>>的转换是编译器不允许的,因为前者的引用生命周期完全依赖输入参数,无法保证它能活到'static的长度,强行转换会导致悬垂引用。
内容的提问来源于stack exchange,提问作者cadolphs

