使用哈希集规范化Rust中的对象
作为一项教育活动,我正在考虑移植到Rust 它的基本操作模式是将大量CVS主文件解析为中间形式,然后分析中间形式,目的是将其转换为git快速导出流 解析时要做的一件事是将中间形式的公共部分转换为规范表示。一个激励性的例子是提交作者。CVS存储库可能有数十万个单独的文件提交,但可能不到一千个作者。因此,在解析时使用一个interning表,当您从文件解析作者时输入作者,它将给您一个指向规范版本的指针,如果它以前没有看到过,则创建一个新版本。(我也听说过这叫做雾化或实习)。然后,该指针存储在中间对象上 在Rust中,我第一次尝试做类似的事情,尝试使用使用哈希集规范化Rust中的对象,rust,hashset,canonicalization,Rust,Hashset,Canonicalization,作为一项教育活动,我正在考虑移植到Rust 它的基本操作模式是将大量CVS主文件解析为中间形式,然后分析中间形式,目的是将其转换为git快速导出流 解析时要做的一件事是将中间形式的公共部分转换为规范表示。一个激励性的例子是提交作者。CVS存储库可能有数十万个单独的文件提交,但可能不到一千个作者。因此,在解析时使用一个interning表,当您从文件解析作者时输入作者,它将给您一个指向规范版本的指针,如果它以前没有看到过,则创建一个新版本。(我也听说过这叫做雾化或实习)。然后,该指针存储在中间对象
HashSet
作为interning表。注意:这使用的是CVS版本号而不是作者,这只是一个数字序列,如1.2.3.4,表示为Vec
use std::collections::HashSet;
use std::hash::Hash;
#[derive(PartialEq, Eq, Debug, Hash, Clone)]
struct CvsNumber(Vec<u16>);
fn intern<T:Eq + Hash + Clone>(set: &mut HashSet<T>, item: T) -> &T {
let dupe = item.clone();
if !set.contains(&item) {
set.insert(item);
}
set.get(&dupe).unwrap()
}
fn main() {
let mut set: HashSet<CvsNumber> = HashSet::new();
let c1 = CvsNumber(vec![1, 2]);
let c2 = intern(&mut set, c1);
let c3 = CvsNumber(vec![1, 2]);
let c4 = intern(&mut set, c3);
}
使用std::collections::HashSet;
使用std::hash::hash;
#[派生(PartialEq、Eq、调试、哈希、克隆)]
结构CvsNumber(Vec);
fn实习生(集合:&mut哈希集合,项目:T)->&T{
让dupe=item.clone();
if!set.contains(&item){
套。插入(项目);
}
set.get(&dupe).unwrap()
}
fn main(){
让mut set:HashSet=HashSet::new();
设c1=CvsNumber(vec![1,2]);
设c2=内部(和多个集合,c1);
设c3=CvsNumber(vec![1,2]);
设c4=内部(和多个集合,c3);
}
此操作失败,出现错误[E0499]:一次不能将“set”作为可变项借用多次
。这很公平,HashSet
不保证在获得引用后添加更多项时对其键的引用有效。C版本谨慎地保证了这一点。为了得到这个保证,我认为哈希集
应该在框
上方。然而,我无法向借书人解释它的使用寿命
这里我要介绍的所有权模型是interning表拥有数据的规范版本,并分发引用。只要interning表存在,引用就应该有效。我们应该能够在不使旧引用无效的情况下向interning表添加新内容。我认为我的问题的根源在于,我不知道如何以与Rust所有权模型一致的方式编写此合同的接口
我在有限的防锈知识中看到的解决方案有:
HashSet
,然后在第二个过程中冻结它并使用引用。这意味着额外的临时存储(有时是大量存储)有人有更好的想法吗?你的分析是正确的。最终的问题是,当修改
HashSet
时,编译器无法保证突变不会影响现有的分配。事实上,在一般情况下,它们可能会影响它们,除非您添加另一层间接寻址,正如您已经确定的那样
这是不安全
非常有用的地方的一个典型例子。作为程序员,您可以断言代码只能以特定的方式使用,并且这种特定的方式将允许变量通过任何突变保持稳定。可以使用类型系统和模块可见性来帮助强制执行这些条件
请注意,String
已经引入了堆分配。只要分配后不更改字符串
,就不需要额外的框
像这样的事情似乎是一个好的开始:
use std::{cell::RefCell, collections::HashSet, mem};
struct EasyInterner(RefCell<HashSet<String>>);
impl EasyInterner {
fn new() -> Self {
EasyInterner(RefCell::new(HashSet::new()))
}
fn intern<'a>(&'a self, s: &str) -> &'a str {
let mut set = self.0.borrow_mut();
if !set.contains(s) {
set.insert(s.into());
}
let interned = set.get(s).expect("Impossible missing string");
// TODO: Document the pre- and post-conditions that the code must
// uphold to make this unsafe code valid instead of copying this
// from Stack Overflow without reading it
unsafe { mem::transmute(interned.as_str()) }
}
}
fn main() {
let i = EasyInterner::new();
let a = i.intern("hello");
let b = i.intern("world");
let c = i.intern("hello");
// Still strings
assert_eq!(a, "hello");
assert_eq!(a, c);
assert_eq!(b, "world");
// But with the same address
assert_eq!(a.as_ptr(), c.as_ptr());
assert!(a.as_ptr() != b.as_ptr());
// This shouldn't compile; a cannot outlive the interner
// let x = {
// let i = EasyInterner::new();
// let a = i.intern("hello");
// a
// };
let the_pointer;
let i = {
let i = EasyInterner::new();
{
// Introduce a scope to contstrain the borrow of `i` for `s`
let s = i.intern("inner");
the_pointer = s.as_ptr();
}
i // moving i to a new location
// All outstanding borrows are invalidated
};
// but the data is still allocated
let s = i.intern("inner");
assert_eq!(the_pointer, s.as_ptr());
}
使用std:{cell::RefCell,collections::HashSet,mem};
结构EasyInterner(RefCell);
简单易懂{
fn new()->Self{
EasyInterner(RefCell::new(HashSet::new())
}
fn实习生和a街{
设mut set=self.0.borrow_mut();
if!set.contains(个){
set.插入(s.到());
}
让interned=set.get.expect(“不可能缺少字符串”);
//TODO:记录代码必须满足的前置和后置条件
//坚持使此不安全代码有效,而不是复制此代码
//从堆栈溢出而不读取它
不安全{mem::transmute(interned.as_str())}
}
}
fn main(){
设i=EasyInterner::new();
让a=i.intern(“你好”);
让b=i.实习生(“世界”);
让c=i.intern(“你好”);
//静止弦
断言(a,“你好”);
断言(a,c);
断言(b,“世界”);
//但地址是一样的
断言(a.as_ptr(),c.as_ptr());
断言!(a.as_ptr()!=b.as_ptr());
//这不应该编译;一个不能比实习生活得更长
//设x={
//设i=EasyInterner::new();
//让a=i.intern(“你好”);
//a
// };
让_指针;
让我={
设i=EasyInterner::new();
{
//引入一个作用域来扩展'i'for'的借用`
设s=i.intern(“内部”);
_指针=s.as_ptr();
}
i//将i移动到新位置
//所有未偿还的借款均无效
};
//但数据仍在分配中
设s=i.intern(“内部”);
断言(指针,s.as_ptr());
}
但是,使用板条箱可能更为方便,如:
- ,它拥有伺服项目的集体智慧李>
虽然现在它不会引起问题,但如果将来有人决定更改HashSet
的使用以包括一些删减(例如,只保留一百个作者),那么不安全的
将严厉地打击你
在没有str的情况下
use std::collections::HashSet;
use std::hash::Hash;
use std::rc::Rc;
#[derive(PartialEq, Eq, Debug, Hash, Clone)]
struct CvsNumber(Rc<Vec<u16>>);
fn intern<T:Eq + Hash + Clone>(set: &mut HashSet<T>, item: T) -> T {
if !set.contains(&item) {
let dupe = item.clone();
set.insert(dupe);
item
} else {
set.get(&item).unwrap().clone()
}
}
fn main() {
let mut set: HashSet<CvsNumber> = HashSet::new();
let c1 = CvsNumber(Rc::new(vec![1, 2]));
let c2 = intern(&mut set, c1);
let c3 = CvsNumber(Rc::new(vec![1, 2]));
let c4 = intern(&mut set, c3);
}