Я изучаю Rust и хотел бы знать, как я могу улучшить код ниже.
У меня есть вектор кортежей формы (u32, String)
. Значения u32
представляют номера строк, а String
s - текст в соответствующих строках. Поскольку все значения String могут быть успешно проанализированы как целые числа, я хочу вернуть Ok<Vec<i32>>
, содержащий только что проанализированные String
значения, но если нет, я хочу вернуть ошибку какой-либо формы (просто Err<String>
в пример ниже).
Я пытаюсь научиться избегать изменчивости и использовать функциональные стили, где это уместно, а вышеприведенное просто сделать функционально, если это было все, что было нужно. Вот что я придумал в этом случае:
fn data_vals(sv: &Vec<(u32, String)>) -> Result<Vec<i32>, String> {
sv.iter()
.map(|s| s.1.parse::<i32>()
.map_err(|_e| "*** Invalid data.".to_string()))
.collect()
}
Однако небольшая уловка заключается в том, что я хочу напечатать сообщение об ошибке для каждого недопустимого значения (а не только первого), и сообщения об ошибках должны содержать как номер строки, так и значения строки в обидном кортеже.
Мне удалось сделать это с помощью следующего кода:
fn data_vals(sv: &Vec<(u32, String)>) -> Result<Vec<i32>, String> {
sv.iter()
.map(|s| (s.0, s.1.parse::<i32>()
.or_else(|e| {
eprintln!("ERROR: Invalid data value at line {}: '{}'",
s.0, s.1);
Err(e)
})))
.collect::<Vec<(u32, Result<i32, _>)>>() // Collect here to avoid short-circuit
.iter()
.map(|i| i.1
.clone()
.map_err(|_e| "*** Invalid data.".to_string()))
.collect()
}
Это работает, но кажется довольно запутанным и громоздким - особенно набранный collect()
в середине, чтобы избежать короткого замыкания, поэтому все ошибки выводятся на печать. Вызов clone()
также раздражает, и я не совсем уверен, зачем он нужен - иначе компилятор говорит, что я перехожу из заимствованного контента, но я не совсем уверен, что перемещается. Есть ли способ сделать это более чисто? Или я должен вернуться к более процедурному стилю? Когда я попытался, я получил изменяемые переменные и флаг, указывающий на успех и неудачу, что выглядит менее элегантно:
fn data_vals(sv: &Vec<(u32, String)>) -> Result<Vec<i32>, String> {
let mut datavals = Vec::new();
let mut success = true;
for s in sv {
match s.1.parse::<i32>() {
Ok(v) => datavals.push(v),
Err(_e) => {
eprintln!("ERROR: Invalid data value at line {}: '{}'",
s.0, s.1);
success = false;
},
}
}
if success {
return Ok(datavals);
} else {
return Err("*** Invalid data.".to_string());
}
}
Может кто-нибудь посоветовать мне лучший способ сделать это? Должен ли я придерживаться процедурного стиля здесь, и если так, это может быть улучшено? Или есть более чистый функциональный способ сделать это? Или смесь двух? Любой совет приветствуется.