You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

自定义CSVParser结构体调用parse_csv方法时提示方法未找到的问题排查求助

自定义CSVParser结构体调用parse_csv方法时提示方法未找到的问题排查求助

各位大佬好,我最近在折腾一个自定义的CSV解析器,用来处理公司内部那种格式奇怪的CSV——不能改输入格式,里面有多行值,而且所有字段都用双引号包着。目前我已经用Logos实现了一个能用的tokenizer,能把输入文本解析成[Option<&'s str>; COLUMNS](COLUMNS是const泛型),但在写测试用例,把解析后的数组转换成具体业务类型的时候,遇到了一个特别诡异的编译错误:

error[E0599]: no method named `parse_csv` found for struct `CSVParser<'_, 6, BadBin<'_>, fn(...) -> ... {parse_item}>` in the current scope
--> src\csv_parser.rs:424:45
|
118 | pub struct CSVParser<'f, const COLUMNS: usize, E: Error, F: CSVItemParser<'f, COLUMNS, E>> {
| ------------------------------------------------------------------------------------------ method `parse_csv` not found for this struct
...
424 | expect!("").assert_debug_eq(&parser.parse_csv(SAMPLE_INPUT));
|             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ method not found in `CSVParser<'_, 6, BadBin<'_>, fn(...) -> ... {parse_item}>`
|
= note: the method was found for
- `csv_parser::CSVParser<'f, COLUMNS, E, F>`

编译器说方法parse_csv不存在于我的parser实例类型里,但又说这个方法确实存在于CSVParser<'f, COLUMNS, E, F>这个结构体上,我完全搞不懂这矛盾的提示是什么意思😵‍💫

下面是我代码里的核心部分(完整代码是个gist,这里只贴关键逻辑,注释可能调整了行号):

use std::error::Error;
use core::marker::PhantomData;

pub struct CSVParser<'f, const COLUMNS: usize, E: Error, F: CSVItemParser<'f, COLUMNS, E>> {
    field_labels: [&'f str; COLUMNS],
    item_parser: F,
    _error_marker: PhantomData<E>,
}

/// 用来把[Option<&'s str>; COLUMNS]转换成业务类型的Trait
pub trait CSVItemParser<'f, const COLUMNS: usize, E: Error> {
    type Output<'s> where 'f: 's, Self: 's, E: 's;
    fn parse_item<'s>(
        &self,
        row: CSVRow<'f, 's, COLUMNS, E>,
    ) -> Result<Self::Output<'s>, ParseError<'f, 's, COLUMNS, E>>
    where
        Self: Sized + 's,
        E: 's;
}

// 给闭包/函数实现这个Trait,方便直接传解析逻辑
impl<'f, const COLUMNS: usize, E, F, T> CSVItemParser<'f, COLUMNS, E> for F
where
    E: Error,
    F: for<'s> Fn(CSVRow<'f, 's, COLUMNS, E>) -> Result<T, ParseError<'f, 's, COLUMNS, E>>,
{
    type Output<'s> = T where 'f: 's, Self: 's, E: 's;
    fn parse_item<'s>(
        &self,
        row: CSVRow<'f, 's, COLUMNS, E>,
    ) -> Result<Self::Output<'s>, ParseError<'f, 's, COLUMNS, E>>
    where
        Self: Sized + 's,
        E: 's,
    {
        self(row)
    }
}

/// 封装行数据和字段标签的辅助结构体,有一些方便取值的方法
pub struct CSVRow<'f: 's, 's, const COLUMNS: usize, E: Error> {
    fields: &'f [&'f str; COLUMNS],
    values: [Option<&'s str>; COLUMNS],
    _error_marker: PhantomData<E>,
}

// CSVParser的实现块,里面定义了parse_csv方法
impl<'f, const COLUMNS: usize, E: Error, F: CSVItemParser<'f, COLUMNS, E>> CSVParser<'f, COLUMNS, E, F> {
    pub fn new(fields: [&'f str; COLUMNS], parse_item: F) -> Self {
        Self {
            field_labels: fields,
            item_parser: parse_item,
            _error_marker: PhantomData,
        }
    }

    // 就是这个编译器说找不到的方法
    pub fn parse_csv<'s>(
        &'f self,
        source: &'s str,
    ) -> Result<Vec<F::Output<'s>>, ParseError<'f, 's, COLUMNS, E>> {
        let mut lex = Token::lexer(source);
        let mut items = Vec::new();
        let mut tok = match lex.next() {
            Some(Ok(Token::Header)) => match lex.next() {
                Some(Ok(Token::EOL)) => None,
                Some(Ok(tok)) => Some(tok),
                Some(Err(_)) => Err(ParseError::TokenError(lex.slice()))?,
                None => return Ok(items),
            },
            Some(Ok(tok)) => Some(tok),
            Some(Err(_)) => Err(ParseError::TokenError(lex.slice()))?,
            None => return Ok(items),
        };

        loop {
            let fields = self.parse_line(&mut lex, tok.take())?;
            items.push(self.item_parser.parse_item(fields)?);
            tok = match lex.next() {
                Some(Ok(t)) => Some(t),
                Some(Err(_)) => Err(ParseError::TokenError(lex.slice()))?,
                None => return Ok(items),
            }
        }
    }

    // 这里省略了parse_line的具体实现,它负责把token流转换成CSVRow
    fn parse_line<'s>(&self, lex: &mut logos::Lexer<'s, Token<'s>>, tok: Option<Token<'s>>) -> Result<CSVRow<'f, 's, COLUMNS, E>, ParseError<'f, 's, COLUMNS, E>> {
        // 具体解析逻辑...
    }
}

// 测试用的错误类型和解析函数
#[derive(Debug)]
struct BadBin<'a>(&'a str);
impl<'a> Error for BadBin<'a> {}
impl<'a> std::fmt::Display for BadBin<'a> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "BadBin: {}", self.0)
    }
}

fn parse_item<'f, 's>(row: CSVRow<'f, 's, 6, BadBin<'f>>) -> Result<(), ParseError<'f, 's, 6, BadBin<'f>>> {
    // 业务解析逻辑...
    Ok(())
}

// 测试代码
const SAMPLE_INPUT: &str = "\"col1\",\"col2\",\"col3\",\"col4\",\"col5\",\"col6\"\n\"val1\",\"val2\",\"val3\",\"val4\",\"val5\",\"val6\"";

#[test]
fn test_csv_parse() {
    let parser = CSVParser::new(["col1","col2","col3","col4","col5","col6"], parse_item);
    expect_test::expect!("").assert_debug_eq(&parser.parse_csv(SAMPLE_INPUT));
}

我的疑惑

我反复对比了结构体的泛型参数和impl块的参数,看起来完全一致啊?编译器提示的存在方法的类型CSVParser<'f, COLUMNS, E, F>和我的实例类型CSVParser<'_, 6, BadBin<'_>, fn(...) -> ... {parse_item}>,泛型参数都是匹配的,只是生命周期用了占位符'_而已。

我怀疑是不是生命周期约束太严格了?比如parse_csv方法里的&'f self,是不是应该改成&self?或者是CSVItemParser trait里的关联类型Output<'s>的生命周期约束有问题?

有没有大佬能帮我看看问题出在哪?卡了快一下午了,实在没头绪😭

内容来源于stack exchange

火山引擎 最新活动