如何将附加参数传递给特征的显示或调试实现?
How to pass additional parameters to trait's Display or debug implementation?
我有一个用 Rust 编写的解释器,它解析传递的脚本并将其表示为结构和枚举的嵌套树。所有这些结构和枚举都实现了 Debug
特征,因此我能够很好地打印它们。唯一的问题是一个特定的结构,如下所示。
struct Meta {
start_index: usize,
end_index: usize,
}
上面的结构几乎包含在树中的所有节点中。索引代表脚本字符串中的开始和结束索引。
在 Meta
的 Debug
打印期间,我想打印由这些边界而不是那些数字表示的字符串切片。但是,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,
}
}
}
这给我们带来了什么?我们现在可以像这样使用 {:?}
打印 Meta
s:
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
})
}
}
然后,假设您在某种词法分析器中构造 Meta
s,您需要向词法分析器添加一个 Interner<'a>
。无论您在何处创建新的 Meta
,只需调用 lexer.interner.intern(&lexer.src[start_index..end_index])
即可获得 Rc<String>
.
我有一个用 Rust 编写的解释器,它解析传递的脚本并将其表示为结构和枚举的嵌套树。所有这些结构和枚举都实现了 Debug
特征,因此我能够很好地打印它们。唯一的问题是一个特定的结构,如下所示。
struct Meta {
start_index: usize,
end_index: usize,
}
上面的结构几乎包含在树中的所有节点中。索引代表脚本字符串中的开始和结束索引。
在 Meta
的 Debug
打印期间,我想打印由这些边界而不是那些数字表示的字符串切片。但是,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,
}
}
}
这给我们带来了什么?我们现在可以像这样使用 {:?}
打印 Meta
s:
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
})
}
}
然后,假设您在某种词法分析器中构造 Meta
s,您需要向词法分析器添加一个 Interner<'a>
。无论您在何处创建新的 Meta
,只需调用 lexer.interner.intern(&lexer.src[start_index..end_index])
即可获得 Rc<String>
.