#string #rust #match
#строка #Ржавчина #совпадение
Вопрос:
Я пишу проект, в котором структура System
может быть создана из файла данных. В файле данных некоторые строки содержат ключевые слова, которые указывают значения, которые должны быть прочитаны либо внутри строки, либо в последующих N следующих строках (отделенных от строки пустой строкой).
Я хотел бы иметь a vec!
, содержащий ключевые слова (статически известные во время компиляции), проверьте, содержит ли строка, возвращаемая итератором, ключевое слово, и выполните соответствующие операции.
Теперь мой код выглядит так:
impl System {
fn read_data<P>(filename: P) -> io::Result<io::Lines<io::BufReader<File>>> where P: AsRef<Path> {
let file = File::open(filename)?;
let f = BufReader::new(file);
Ok(f.lines())
}
...
pub fn new_from_data<P>(dataname: P) -> System where P: AsRef<Path> {
let keywd = vec!["atoms", "atom types".into(),
"Atoms".into()];
let mut sys = System::new();
if let Ok(mut lines) = System::read_data(dataname) {
while let Some(line) = lines.next() {
for k in keywd {
let split: Vec<amp;str> = line.unwrap().split(" ").collect();
if split.contains(k) {
match k {
"atoms" => sys.natoms = split[0].parse().unwrap(),
"atom types" => sys.ntypes = split[0].parse().unwrap(),
"Atoms" => {
lines.next();
// assumes fields are: atom-ID molecule-ID atom-type q x y z
for _ in 1..=sys.natoms {
let atline = lines.next().unwrap().unwrap();
let data: Vec<amp;str> = atline.split(" ").collect();
let atid: i32 = data[0].parse().unwrap();
let molid: i32 = data[1].parse().unwrap();
let atype: i32 = data[2].parse().unwrap();
let charge: f32 = data[3].parse().unwrap();
let x: f32 = data[4].parse().unwrap();
let y: f32 = data[5].parse().unwrap();
let z: f32 = data[6].parse().unwrap();
let at = Atom::new(atid, molid, atype, charge, x, y, z);
sys.atoms.push(at);
};
},
_ => (),
}
}
}
}
}
sys
}
}
Я очень не уверен в двух моментах:
- Я не знаю, обрабатывал ли я построчное чтение файла идиоматическим способом, поскольку я переделал несколько примеров из книги и Rust на примере. Но возврат итератора заставляет меня задуматься, когда и как разворачивать результаты. Например, при вызове итератора внутри цикла while мне нужно разворачивать дважды, как в
let atline = lines.next().unwrap().unwrap();
? Я думаю, что компилятор пока не жалуется из-за первой ошибки, с которой он сталкивается, которая - Я не могу понять, какой тип присваивается значению k, поскольку я получаю типичный:
error[E0308]: mismatched types
--> src/system/system.rs:65:39
|
65 | if split.contains(k) {
| ^ expected `amp;str`, found `str`
|
= note: expected reference `amp;amp;str`
found reference `amp;str`
error: aborting due to previous error
Как мы должны объявить подстроку и сравнить ее со строками, которые я ввел keywd
? Я пытался использовать k в contains, сказать ему посмотреть amp;keywd и т. Д., Но я просто чувствую, что трачу свое время на неправильное решение проблемы. Заранее спасибо, любая помощь действительно приветствуется.
Комментарии:
1. Быстрый ответ на «ожидаемый
amp;amp;str
, найденamp;str
» заключается в использованииamp;k
, но вы правы в том, что исправление, которое проходит проверку типа, но затем приходит средство проверки заимствования и обнаруживает больше ошибок
Ответ №1:
Давайте рассмотрим эти вопросы один за другим. Я пройдусь по тому, как они отображаются в коде.
Сначала вам нужно позаимствовать keywd
в for
цикле, т.е. amp;keywd
. Потому что в противном keywd
случае выполняется перемещение после первой итерации while
цикла, и, следовательно, почему компилятор жалуется на это.
for k in amp;keywd {
let split: Vec<amp;str> = line.unwrap().split(" ").collect();
Далее, когда вы вызываете .unwrap()
on line
, возникает та же проблема. Это приводит к перемещению внутреннего Ok
значения из Result
. Вместо этого вы можете сделать line.as_ref().unwrap()
так, как тогда вы получаете ссылку на внутреннее Ok
значение и не используете line
результат.
В качестве альтернативы, вы можете .filter_map(Result::ok)
на своем lines
, чтобы вообще избежать ( .as_ref()
) .unwrap()
.
Вы можете добавить это непосредственно в read_data
и даже просто возвращаемый тип, используя impl ...
.
fn read_data<P>(filename: P) -> io::Result<impl Iterator<Item = String>>
where
P: AsRef<Path>,
{
let file = File::open(filename)?;
let f = BufReader::new(file);
Ok(f.lines().filter_map(Result::ok))
}
Обратите внимание, что вы разделяете line
для каждого keywd
, что не нужно. Таким образом, вы также можете переместить это за пределы своего for
цикла.
В целом, это выглядит так:
if let Ok(mut lines) = read_data("test.txt") {
while let Some(line) = lines.next() {
let split: Vec<amp;str> = line.split(" ").collect();
for k in amp;keywd {
if split.contains(k) {
...
Учитывая, что мы заимствовали amp;keywd
, нам не нужно менять k
на amp;k
, как сейчас k
уже amp;amp;str
.
Комментарии:
1. Я думаю, это сделало свое дело. В последующем сопоставлении мне все равно пришлось разыменовывать
*k
, чтобы сравнить егоstr
, но пока, похоже, он запускается и делает то, что я хочу, в тестовом файле. Спасибо! Я люблю Rust за большую часть его дизайна, но я действительно не могу преодолеть проблемы с типом strings….2. Ах, да, я пропустил совпадение. Rust действительно может быть немного сложным, просто подождите, и достаточно скоро вы будете использовать
AsRef<str>
andCow<str>
.