🎥 Rust: рефакторинг для новичков



📌 Отсутствие идиом Rust

Опытным программистам Rust режет глаз тот факт, что следующая функция возвращает кортеж, а не Result<>:



fn add_student() -> (Student, bool)



Данный подход не только не является идиоматическим, но и вводит в заблуждение читателя кода. Непонятно, что подразумевает логическое значение bool. В ответ на вывод этой функции придется написать что-то сложное, как показано ниже:



// Добавление студента на курс

let (st, err) = add_student();



// Проверка наличия ошибки. В случае ошибки продолжить цикл

if !err {

continue;

}




Получается 5 строк с комментариями, объясняющими код. Это считается плохой практикой, так же как и короткие имена переменных.



📌 Рефакторинг

Сначала проведем рефакторинг этих фрагментов. То, что было:



fn add_student() -> (Student, bool) {

// ...

let mut st = Student {

name: "".to_string(),

age: 0,

};

// ...

if student_name.len() < 3 {

// ...

return (st, false);

}

// ...

(st, true)

}




преобразуем в более идиоматический и читаемый вариант:



fn add_student() -> Result<Student, &'static str> {

// ...

if student_name.len() < 3 {

// ...

return Err("Student's name too short");

}

// ...

let age = age.parse.map_err(|_| "Cannot parse student's age")?;



Ok(Student {

name: student_name,

age

})

}




Понятно, что возврат статических строк в качестве ошибок не относится к разряду привычных практик, но вполне подойдет для данного примера.



◾️Метод .map_err() позволяет преобразовать экземпляр типа, содержащийся в значении перечисления Err(e), в такой, который поддерживается функцией.



В этом случае объявленным типом является &'static str (эквивалент Rust для идиомы типа const char* в С), чем объясняется совпадение текстов в кавычках. Оператор ?  — одна из лучших функциональностей Rust. Он проверяет стоящий перед ним экземпляр Result<>. Если значение равно Err(e), возвращает результат, в противном случае продолжает работу. В старом коде встречался макрос try!().



В итоге проверка ожидаемого вывода функции выглядит так:



let student = if let Ok(student) = add_student() {

student

} else {

continue;

}



student_db.push(student.clone());




Это неидеальное условие, поскольку оно фактически исключает любую ошибку. Действуя таким образом, мы исходим из предположения допустимости такого подхода, но предусматриваем обработку перечисления Err(e) на индивидуальной основе.



📌 Ошибка новичков: бесконечный цикл



Читать



@rust_code