Основи 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-код, який не є потворним і не потребує при цьому винятків.
Проте у наших прикладах є кілька недоліків. Краще використовувати функції: як правило, функції зрозуміліші та простіші в обслуговуванні за умови, що кожній функції відповідає лише одна ідея.
Інша проблема в тому, що ми не так добре опрацьовуємо помилки, як могли б. Наші програми все ще малі, тому ці недоліки не є великою проблемою, але зі зростанням програми буде все важче їх знайти і виправити, щоб привести код у норму.
Тому рекомендується починати рефакторинг на ранній стадії розробки програми, тому що набагато простіше відрефакторити менші обсяги коду. І недарма вище було сказано про функції, тому що подібні логічні цеглинки програми можна легко обробляти окремо.
Далі буде…
Favbet Tech – це ІТ-компанія зі 100% украінською ДНК, що створює досконалі сервіси для iGaming і Betting з використанням передових технологіи та надає доступ до них. Favbet Tech розробляє інноваційне програмне забезпечення через складну багатокомпонентну платформу, яка здатна витримувати величезні навантаження та створювати унікальний досвід для гравців.
Сообщить об опечатке
Текст, который будет отправлен нашим редакторам: