Основи Rust: читання з файлів
У нашій серії матеріалів ми розглянемо базові основи новомодної мови Rust. А в другій частині циклу на основі вивченого спробуємо написати найпростіші смарт-контракти для таких блокчейн-проєктів, як Solana. У цьому туторіалі буде багато прикладів, мало теорії та швидкий темп просування.

Цей пост — вільний переклад ось цієї оригінальної статті (з нашими доповненнями у місцях, де це здалося потрібним), яку написав Стів Донован. Початок можна знайти ось тут, а зміст усієї серії — ось тут.
Читання з файлів
Наступним важливим кроком на шляху до відкриття наших програм світові є техніка читання файлів.
Згадайте, що expect схожий на unwrap, але видає повідомлення користувача про помилку. У наступній програмі, цілком гарній на вигляд, ми отримаємо кілька помилок. Далі по тексту розберемося, чому:
// file1.rs
use std::env;
use std::fs::File;
use std::io::Read;
fn main() {
let first = env::args().nth(1).expect("please supply a filename");
let mut file = File::open(&first).expect("can't open the file");
let mut text = String::new();
file.read_to_string(&mut text).expect("can't read the file");
println!("file had {} bytes", text.len());
}
Це дає такий висновок:
src$ file1 file1.rs
file had 366 bytes
src$ ./file1 frodo.txt
thread 'main' panicked at 'can't open the file: Error { repr: Os { code: 2, message: "No such file or directory" } }', ../src/libcore/result.rs:837
note: Run with `RUST_BACKTRACE=1` for a backtrace.
src$ file1 file1
thread 'main' panicked at 'can't read the file: Error { repr: Custom(Custom { kind: InvalidData, error: StringError("stream did not contain valid UTF-8") }) }', ../src/libcore/result.rs:837
note: Run with `RUST_BACKTRACE=1` for a backtrace.
Примітка: Запустіть із ключем `RUST_BACKTRACE=1` для отримання зворотного трасування.
Отже, розбираємось: open може не спрацювати в реальному житті, тому що файл не існує або нам не дозволено його читати, а read_to_string може не спрацювати, тому що файл не містить правильного UTF-8. Щоб передбачити цю можливість, можна додатково використовувати read_to_end та помістити вміст у вектор байтів. Для файлів, які не надто великі, читання в один прийом є ефективним і простим.
Якщо ви знаєте щось про роботу з файлами в інших мовах, вам може бути цікаво, як обробляти ситуацію, коли файл закривається. Якби ми реально записали дані у цей файл, його незакриття могло б призвести до втрати даних. Але тут файл автоматично закривається, коли функція завершується і файлова змінна обнуляється.
Отже, тепер ми повинні поговорити про те, що саме повертає File::open. Якщо Option — це значення, яке може містити щось або нічого, то Result — це значення, яке може містити щось або код помилки. Вони обидва розуміються як unwrap (та його двоюрідний брат expect), але вони є абсолютно різними.
Result визначається двома параметрами типу: значення Ok і значення Err. Умовна скринька Result має два відділення, одне з яких позначено Ok, а інше Err. Ось приклад:
fn good_or_bad(good: bool) -> Result<i32,String> {
if good {
Ok(42)
} else {
Err("bad".to_string())
}
}
fn main() {
println!("{:?}",good_or_bad(true));
//Ok(42)
println!("{:?}",good_or_bad(false));
//Err("bad")
match good_or_bad(true) {
Ok(n) => println!("Cool, I got {}",n),
Err(e) => println!("Huh, I just got {}",e)
}
// Cool, I got 42
}
Фактичний тип 'error' довільний — багато людей використовують рядки, доки не опанують типи помилок Rust. Це зручний спосіб повернути або одне значення, або інше.
Ця версія функції читання файлів не є аварійною. Вона повертає результат, і саме сторона, що викликає, має вирішити, як обробити потенційну помилку.
// file2.rs
use std::env;
use std::fs::File;
use std::io::Read;
use std::io;
fn read_to_string(filename: &str) -> Result<String,io::Error> {
let mut file = match File::open(&filename) {
Ok(f) => f,
Err(e) => return Err(e),
};
let mut text = String::new();
match file.read_to_string(&mut text) {
Ok(_) => Ok(text),
Err(e) => Err(e),
}
}
fn main() {
let file = env::args().nth(1).expect("please supply a filename");
let text = read_to_string(&file).expect("bad file man!");
println!("file had {} bytes", text.len());
}
Перший збіг безпечно отримує значення з Ok, яке стає значенням матчингу. Якщо це Err, то повертається помилка, обгорнута Err.
Друга відповідність повертає рядок, обернений в Ok, або знову помилку. Фактичний вміст Ok не має конкретного значення, тому ми ігноруємо його за допомогою оператора _.
Не дуже красиво виглядає, коли більшість функції — це обробник помилок. Go має тенденцію також виявляти цю проблему з великою кількістю явних ранніх повернень або просто ігноруванням помилок.
На щастя, є короткий шлях.
Модуль std::io визначає псевдонім типу io::Result<T>, він такий самий, як Result<T,io::Error>, і його простіше набирати.
fn read_to_string(filename: &str) -> io::Result<String> {
let mut file = File::open(&filename)?;
let mut text = String::new();
file.read_to_string(&mut text)?;
Ok(text)
}
Оператор ? робить майже те саме, що і збіг в File::open — якщо результатом була помилка, то він негайно повертає цю помилку. В іншому випадку він повертає результат Ok. В кінці нам все ще потрібно повернути рядок як результат.
2017 був хорошим роком для Rust, а ? був одним із тих крутих речей, які стали офіційно стабільними. Але все ще можна зустріти застарілий макрос try!, який використовується в старому коді:
fn read_to_string(filename: &str) -> io::Result<String> {
let mut file = try!(File::open(&filename));
let mut text = String::new();
try!(file.read_to_string(&mut text));
Ok(text)
}
Фінальне побажання
Ця частина не тільки про запис файлів, якщо ви помітили. Сьогодні ми також принагідно обговорили, що можна написати абсолютно безпечний Rust-код, який не є потворним і не потребує при цьому винятків.
Проте у наших прикладах є кілька недоліків. Краще використовувати функції: як правило, функції зрозуміліші та простіші в обслуговуванні за умови, що кожній функції відповідає лише одна ідея.
Інша проблема в тому, що ми не так добре опрацьовуємо помилки, як могли б. Наші програми все ще малі, тому ці недоліки не є великою проблемою, але зі зростанням програми буде все важче їх знайти і виправити, щоб привести код у норму.
Тому рекомендується починати рефакторинг на ранній стадії розробки програми, тому що набагато простіше відрефакторити менші обсяги коду. І недарма вище було сказано про функції, тому що подібні логічні цеглинки програми можна легко обробляти окремо.
Далі буде…












Сообщить об опечатке
Текст, который будет отправлен нашим редакторам: