В качестве учебного упражнения я рассматриваю перенос cvs-fast -экспорт в Rust.
Его основной режим работы заключается в преобразовании ряда мастер-файлов CVS в промежуточную форму, а затем в анализе промежуточной формы с целью преобразования ее в поток быстрого экспорта git.
Одна из вещей, которые делаются при синтаксическом анализе, — это преобразование общих частей промежуточной формы в каноническое представление. Мотивирующим примером являются авторы коммитов. Репозиторий CVS может иметь сотни тысяч коммитов отдельных файлов, но, возможно, менее тысячи авторов. Таким образом, промежуточная таблица используется при анализе, где вы вводите автора, когда вы анализируете его из файла, и она даст вам указатель на каноническую версию, создав новую, если она не видела ее раньше. (Я также слышал, что это называется атомизацией или интернированием). Затем этот указатель сохраняется в промежуточном объекте.
Моя первая попытка сделать что-то подобное в Rust заключалась в использовании HashSet
в качестве промежуточной таблицы. Обратите внимание, что здесь используются номера версий 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);
}
Это не удается с error[E0499]: cannot borrow 'set' as mutable more than once at a time
. Это достаточно справедливо, HashSet
не гарантирует, что ссылки на его ключи будут действительными, если вы добавите больше элементов после того, как получили ссылку. Версия C гарантирует это. Чтобы получить эту гарантию, я думаю, что HashSet
должно быть больше Box<T>
. Однако я не могу объяснить срок службы для этого контролеру заимствований.
Модель владения, к которой я стремлюсь, заключается в том, что промежуточная таблица владеет каноническими версиями данных и выдает ссылки. Ссылки должны быть действительными, пока существует промежуточная таблица. Мы должны иметь возможность добавлять новые вещи в таблицу интернирования, не делая недействительными старые ссылки. Я думаю, что корень моей проблемы в том, что я не понимаю, как написать интерфейс для этого контракта в соответствии с моделью владения Rust.
Решения, которые я вижу с моим ограниченным знанием Rust:
- Сделайте два прохода, постройте
HashSet
на первом проходе, затем заморозьте его и используйте ссылки на втором проходе. Это означает дополнительное временное хранилище (иногда существенное). - Небезопасно
У кого-нибудь есть идея получше?