1 个不稳定版本
使用旧的 Rust 2015
0.1.0 | 2016年12月15日 |
---|
#785 在 数据结构
18KB
179 行
attr
attr
是一个库,通过使用已知的数据结构类型信息,通过类型化的路径对象提供对数据结构的内部访问。
这允许在不获取数据结构本身的情况下表达对数据结构的查询。
attr
通过提供描述数据路径的方法,而不暴露数据的确切结构,将访问与数据解耦。
为此,它描述了适用于给定类型的可遍历路径,并返回特定的值。 attr
使创建这些路径变得简单,并得到类型信息的支持。
该库是“按需付费”,简单指针解引用路径的成本与手写代码相同。
例如,提供了一个对 serde_json 库的接口。
动机示例
让我们考虑一个泛型验证器:它不是对它验证的类型泛型,而是让它对它检索数据验证的方式泛型化会更方便。
这有几个优点
- 验证器实现不需要知道它验证的类型
- 它避免了孤儿规则:访问操作始终是自定义的,并且可以是局部的
- 如果验证的类型结构发生变化,验证器实现不需要更改
struct PrefixValidator<P> {
pattern: String,
path: P
}
impl<P> PrefixValidator<P> {
fn validate<'a, 'b: 'a, T: 'b>(&'a self, t: T) -> std::result::Result<(), String>
where P: Traverse<'a, 'b, T, &'b str>
{
match self.path.traverse(t) {
Ok(s) => {
if s.starts_with(&self.pattern) {
Ok(())
} else {
Err(format!("Does not start with {}", self.pattern))
}
}
Err(reason) => Err(reason)
}
}
}
fn main() {
let user = User { data: Data { email: "[email protected]".into() }};
assert!(validate(&user).is_ok());
}
fn validate(u: &User) -> std::result::Result<(), String> {
let path = retrieve(EmailAttribute).from(DataAttribute);
let validator = PrefixValidator { pattern: "flo".into(), path: path };
validator.validate(u)
}
底层定义
请参阅 examples/validation.rs
以获取完整示例。
属性
库通过为每个访问策略定义一个实现 Attr<Type>
特性的访问器结构来工作。在这种情况下,每个数据结构已知的字段一个。
extern crate attr;
use attr::*;
struct Data {
email: String
}
struct User {
data: Data,
}
struct DataAttribute;
impl<'a> Attr<&'a User> for DataAttribute {
type Output = &'a Data;
fn name(&self) -> &'static str { "data" }
fn get(&self, u: &'a User) -> &'a Data { &u.data }
}
struct EmailAttribute;
impl<'a> Attr<&'a Data> for EmailAttribute {
type Output = &'a str;
fn name(&self) -> &'static str { "email" }
fn get(&self, d: &'a Data) -> &'a str { &d.email }
}
属性是检索结构的一部分的方式。请注意,虽然这通常是一个引用,但它不必是。
所有属性都需要一个名称,用于调试和诊断目的。
每个属性都与一个特定类型绑定,例如,此属性仅从 User
类型检索数据。
属性通过将其移动到单独的类型来外部化数据访问。这允许我们组合它们。除非需要,属性类型的大小为零,并且没有运行时成本。
泛型于可变性
属性可以泛型于可变性。只需提供两个实现,rustc
将推断它需要哪个。
struct DataAttribute;
impl<'a> Attr<&'a User> for DataAttribute {
type Output = &'a Data;
fn name(&self) -> &'static str { "data" }
fn get(&self, u: &'a User) -> &'a Data { &u.data }
}
impl<'a> Attr<&'a mut User> for DataAttribute {
type Output = &'a mut Data;
fn name(&self) -> &'static str { "data" }
fn get(&self, u: &'a User) -> &'a mut Data { &u.data }
}
路径
属性可以组合成访问路径,以安全的方式表达复杂的访问策略。路径最初是通过retrieve
函数构建的,然后通过额外的操作进行链式调用。路径构建是由内向外进行的,这使得推断变得容易。
let path = retrieve(EmailAttribute).from(DataAttribute)
这构建了一个路径,在调用时将从User
中使用DataAttribute
检索data
字段,然后使用EmailAttribute
从结果中的Data
检索email
字段,并返回结果。
路径是类型安全的,因此这将在编译时失败
let path = retrieve(DataAttribute).from(EmailAttribute)
通过调用路径的traverse
方法来访问,传递要工作的对象
let email = path.traverse(&user);
路径包含它们所持有的所有属性的总大小。这意味着通过路径进行访问来替换标准指针访问不会产生运行时成本。
路径遍历始终返回一个结果,因为如果数据结构是动态的(例如HashMap),则可能会失败。
其他访问策略
目前,这个库还提供了IndexableAttr
,用于允许索引访问的属性(例如向量)和IterableAttr
,用于可以迭代的属性(例如向量)。这些路径的构建可能不同,例如,迭代属性中的路径需要这样构建
let path = retrieve(NameAttribute).mapped(VectorAttribute).from(FooAttribute);
这将返回一个迭代器,遍历所有在向量中包装的结构的名称,这些结构位于名为foo
的字段之后。有关完整示例,请参阅tests/mapping.rs
。
此外,它还包含一些额外的属性类型,称为Insecure*
,用于表示检索可能失败(例如访问映射)的属性。它们返回结果而不是普通值。
缺失
这个库没有实现任何宏来简化样板代码或实现任何约定来使属性分组有意义(例如,将它们包装在模块中是有意义的)。这将在其他库中发生。
进一步阅读
要查看在serde_json上工作的属性的草稿实现,请参阅测试套件。
目前开放的事项
- 如果可能,统一属性和路径之间的检索接口
- 适当的错误类型,提供有关失败发生位置的好信息
- 循环路径和条件路径以访问深层数据结构
- 不安全的映射的缺失实现
致谢
- 用于构建路径的不规则列表是从Typed Linked Lists中改编的,并基于Tomaka设计的模式。
- 整个实现都受到了Cocoa中存在的“kv编码”想法的启发。
咬伤舌头
几乎将那个库命名为lazr-pointer,因为它打算在laze.rs项目中使用。
许可
MIT