Я рву на себе волосы (а их у меня осталось немного), пытаясь понять, как использовать Rust.
Вот что я пытаюсь сделать
let mut map = HashMap::new();
let input = File::open(filename).unwrap();
let reader = BufReader::new(input);
let re = Regex::new(r"(.+)\)(.+)").unwrap();
for line in reader.lines() {
if let Some(captures) = re.captures(&line.unwrap()) {
let key = captures.get(1).map_or("", |m| m.as_str());
let value = captures.get(2).map_or("", |m| m.as_str());
println!("{} {}", key, value);
map.entry(key).or_insert(Vec::new()).push(value);
}
}
println!("{:?}", map);
Я получаю эту ошибку компилятора:
error[E0716]: temporary value dropped while borrowed
--> src/main.rs:22:46
|
22 | if let Some(captures) = re.captures(&line.unwrap()) {
| ^^^^^^^^^^^^^ creates a temporary which is freed while still in use
...
30 | }
| - temporary value is freed at the end of this statement
31 | println!("{:?}", map);
| --- borrow later used here
|
= note: consider using a `let` binding to create a longer lived value
Это уже выглядит очень уродливо, и компилятор недоволен. Любые предложения о том, как сделать это более «ржавым» способом, который будет работать?
Также небольшая придирка: BufRead::lines выделяет новый String для каждой строки. Вероятно, это не то, что вам нужно, поэтому вместо этого вам следует рассмотреть возможность использования BufRead::read_line.
@PitaJ - to_string() заставил это работать. Но наверняка должен быть лучший способ, чем этот беспорядок....
Я знаю, как вы себя чувствуете... Такие, казалось бы, простые вещи в Rust могут быстро усложниться. Это определенно не краткий язык, и мне тоже сложно с этим многословием справиться, но он заставляет вас «проходить движения», чтобы в итоге получить доказуемо правильное приложение.
Чтобы разобрать это, давайте начнем с line
reader
читает файл построчно, при этом счастливый путь создает String
for line in
становится владельцем указанного String
в переменной line
line
выходит за рамки в конце каждого цикла// line is "owned by" this variable
for line in reader.lines() {
// so no matter what is in here
} // `line` gets delete from memory here
Далее все "to_string штучки". Большая часть скорости и эффективности памяти Rust основана на «абстракциях с нулевой стоимостью», то есть «избегании большого количества копий вещей». Этот вид операции может быть очень дорогим в других языках. В дополнение к фактической работе по проверке шаблона на строку, если он совпадает, всего у вас будет:
String
за всю линейкуString
для соответствующей части (захват [0])String
для каждой из групп захватаЭто втрое превышает количество «материала» в памяти, его нельзя выделить бесплатно для каждого цикла, и ЦП должен был выполнять работу по его копированию.
this is a test
Фрагмент для is a на самом деле будет выглядеть примерно так:
{
referenceToString: <some_memory_address>,
start: 5, // number of characters to the beginning
length: 4,
}
Все вещи в результате captures основаны на них. Помните, что line удаляется из памяти после цикла. Любой из этих фрагментов, относящихся к <some_memory_address>, больше не будет действительным, поэтому вы не можете поместить их в свой HashMap. Вам нужно вызвать to_string, чтобы скопировать фактический текст в новое значение. Это является причиной большинства подобных ошибок. В C++ вы могли бы сохранить такую конструкцию, но содержимое в этой области памяти было бы заменено чем-то другим. Ваша карта будет иметь got_knows_what как в ключах, так и в значениях, и они будут постоянно меняться по мере записи в память нового материала.
Далее обратите внимание, что line — это Result<String, Error>. Любое количество вещей может пойти не так в середине чтения файла. Таким образом, line — это либо следующая строка как String, либо Error, которую вам нужно обработать. Это как "перевернутая попытка/поймать", если хотите. Вы можете «пнуть банку» с завершающим ?, но тогда текущая функция должна будет вернуть Result<Whatever, Error>, что сделает это проблемой вызывающей стороны.
Использование unwrap — это всегда «сделка с дьяволом». Если вы действительно, абсолютно, на 100% уверены, что на самом деле не может быть ошибки, вы можете использовать его, но всегда может быть ошибка во время ввода-вывода, так что это никогда не безопасное предположение. unwrap обходит механизм обработки ошибок языка, поэтому, если вы unwrap ошибетесь, программа вылетит, фактически говоря: «Я пытался вас предупредить ...»
Наконец, как упомянул @Pitaj, reader.lines создает новый String для каждой строки. Есть некоторые накладные расходы, связанные с необходимостью снова и снова находить пустую память, поэтому вы можете быть более эффективными, особенно для больших файлов, с помощью reader.read_line.
Итак, во-первых, давайте сделаем ваш беспорядок намного хуже, со встроенными комментариями.
// notice we return a `Result`, because we're not going to deal with errors here
fn get_matches(filename: &str) -> io::Result<HashMap<String, Vec<String>>> {
let re = Regex::new(r"(.+)\)(.+)").unwrap();
let mut map = HashMap::new();
// `?` to "kick the can" on an error opening the file
let file = File::open(filename)?;
// the reader is `mut` because... well... because the compiler
// told me it should be. ;-)
let mut reader = BufReader::new(file);
// the memory buffer we're going to reuse for each loop
let mut buffer = String::new();
// `read_line` returns the size read, which is 0 when we're finished
// `?` to "kick the can" on an error reading the line
while reader.read_line(&mut buffer)? > 0 {
// if the regex matches
if let Some(captures) = re.captures(&buffer) {
// convenience closure to unpack
let capture = |index| {
// the capture group will always be a `Some` here, so you *could*
// use `unwrap` here. But it would not be if your regex used a
// `?` (optional) capture, so safer to "go through the motions"
if let Some(m) = captures.get(index) {
// the match is a `Match` instance, so convert
// to `&str` with `as_str`
let str_slice = m.as_str();
// we can't keep a `&str` after the loop, so
// create a new `String` with its contents
let string = str_slice.to_string();
// the result is an `Option`
return Some(string)
}
// if the match were `None`, this `capture` function
// wouldn't have a `String` to return
None
};
// if both captures exist, `k` and `v` are `String`
if let (Some(k), Some(v)) = (capture(1), capture(2)) {
// and now `map` owns both of these `String` values
map.entry(k).or_insert(Vec::new()).push(v);
}
}
// empty out the buffer, but keep the memory it allocated
buffer.clear();
}
Ok(map)
}
После всего этого кажется, что это не так уж и плохо. ;-)
fn get_matches(filename: &str) -> io::Result<HashMap<String, Vec<String>>> {
let re = Regex::new(r"(.+)\)(.+)").unwrap();
let mut map = HashMap::new();
let file = File::open(filename)?;
let mut reader = BufReader::new(file);
let mut buffer = String::new();
while reader.read_line(&mut buffer)? > 0 {
if let Some(captures) = re.captures(&buffer) {
let capture = |index| {
captures.get(index).map(|c| c.as_str().to_string())
};
if let (Some(k), Some(v)) = (capture(1), capture(2)) {
map.entry(k).or_insert(Vec::new()).push(v);
}
}
buffer.clear();
}
Ok(map)
}
Пробовали ли вы, как предполагает ошибка компиляции, добавлять let line = line.unwrap(); над строкой if let? Другой вариант for line in reader.lines().map(|line| line.unwrap()) {