如何将附加参数传递给特征的显示或调试实现?

How to pass additional parameters to trait's Display or debug implementation?

我有一个用 Rust 编写的解释器,它解析传递的脚本并将其表示为结构和枚举的嵌套树。所有这些结构和枚举都实现了 Debug 特征,因此我能够很好地打印它们。唯一的问题是一个特定的结构,如下所示。

struct Meta {
    start_index: usize,
    end_index: usize,
}

上面的结构几乎包含在树中的所有节点中。索引代表脚本字符串中的开始和结束索引。

MetaDebug 打印期间,我想打印由这些边界而不是那些数字表示的字符串切片。但是,Meta 和树中的任何节点都没有引用实际传递的脚本字符串。因此,即使我实现了 Debug 特性,因为我无权访问传递的字符串,它也无济于事。

我不想向 Meta 添加字符串字段,因为此要求仅用于调试和测试目的。

在适用的情况下为 Meta 实施正确的调试信息。例如,如果 struct X 知道关于 Meta 和所需字符串切片的所有信息,那么它应该在那里。

Meta自己实施Debug吗?这取决于该数据对于没有这些字符串切片的任何人是否有价值。如果不是,那就没有意义了

这里有一个可行的想法:创建一个额外的 struct,其中包括借来的 Meta 以及您的来源 String(也是借来的)。它可能看起来像这样:

struct MetaWithSource<'a> {
  meta: &'a Meta,
  source: &'a str
}

现在,为 MetaWithSource 实施 Debug/Display:

use std::fmt;

impl<'a> fmt::Debug for MetaWithSource<'a> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        // We now have access to the source via `self.source`
        todo!()
    }
}

最后,我们可以在 Meta 上添加一个方法来给我们一个 MetaWithSource:

impl Meta {
    pub fn with_source<'a>(&'a self, source: &'a str) -> MetaWithSource<'a> {
        MetaWithSource {
            meta: self,
            source,
        }
    }
}

这给我们带来了什么?我们现在可以像这样使用 {:?} 打印 Metas:

let m: Meta = ...
println!("{:?}", m.with_source(src));

// Since `m` is only borrowed by `with_source`, we can still use it here

缺点是我们需要为每个需要显示部分源字符串的结构Foo创建一个额外的结构FooWithSource(即使他们不直接使用源字符串!)。这可能是也可能不是巨大的痛苦。

不幸的是,除非源字符串以某种方式包含在引用它的对象中(如 Meta),否则我看不到解决最后一个问题的任何方法。这里的一个解决方法(我相信您已经考虑过)是简单地将源字符串或切片连同其他信息包含在 Meta:

struct Meta {
    start_index: usize,
    end_index: usize,
    text: String,
}

struct Meta<'a> {
    start_index: usize,
    end_index: usize,
    text: &'a str
}

这两个都不是完美的解决方案:第一个分配一个新的 String,即使您可能永远不需要所有权的全部权力;第二个“感染”了源字符串的生命周期,因此将“感染”包含它的任何其他结构。

尽管它分配了,但我预计第一个使用起来会更愉快。如果您担心分配问题(特别是如果您存储大量“具体”语法,如“(”、空格等),您可以使用“interner”并存储一个 Rc<String>,同一文本的多个实例将简单地共享指针(并且不需要新的 String 分配)。这是可能看起来像的快速草图:

use std::collections::HashMap;
use std::rc::Rc;

struct Meta {
    start_index: usize,
    end_index: usize,
    text: Rc<String>,
}

struct Interner<'a> {
    seen: HashMap<&'a str, Rc<String>>,
}


impl<'a> Interner<'a> {
    pub fn intern(&mut self, text: &'a str) -> Rc<String> {
        // Copies a pointer to a `String` referring to the text if we've already seen it:
        self.seen.get(text).map(Rc::clone).unwrap_or_else(|| {
            // Only allocates a new `String` if we haven't seen it yet:
            let new = Rc::new(String::from(text));
            self.seen.insert(text, Rc::clone(&new));
            new
        })
    }
}

然后,假设您在某种词法分析器中构造 Metas,您需要向词法分析器添加一个 Interner<'a>。无论您在何处创建新的 Meta,只需调用 lexer.interner.intern(&lexer.src[start_index..end_index]) 即可获得 Rc<String>.