В нашей последовательной серии материалов мы рассмотрим базовые основы новомодного языка 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-код, который не является уродливым и не нуждается при этом в исключениях.
Тем не менее, в наших примерах есть несколько недостатков. Лучше использовать функции: как правило, функции понятнее и проще в обслуживании, при условии, что каждой функции отвечает только одна идея.
Другая проблема в том, что мы не так хорошо обрабатываем ошибки, как могли бы. Наши программки все еще малы, поэтому эти недостатки не являются большой проблемой, но по мере роста программы будет все труднее их найти и исправить, чтобы привести код в норму.
Поэтому рекомендуется начинать рефакторинг на ранней стадии разработки программы, потому что гораздо проще отрефакторить меньшие объемы кода. И выше не зря было сказано про функции, потому что подобные логические кирпичики программы можно запросто обрабатывать по отдельности.
Продолжение следует…
Прокси (proxy), или прокси-сервер — это программа-посредник, которая обеспечивает соединение между пользователем и интернет-ресурсом. Принцип…
Согласитесь, было бы неплохо соединить в одно сайт и приложение для смартфона. Если вы еще…
Повсеместное распространение смартфонов привело к огромному спросу на мобильные игры и приложения. Миллиарды пользователей гаджетов…
В перечне популярных чат-ботов с искусственным интеллектом Google Bard (Gemini) еще не пользуется такой популярностью…
Скрипт (англ. — сценарий), — это небольшая программа, как правило, для веб-интерфейса, выполняющая определенную задачу.…
Дедлайн (от англ. deadline — «крайний срок») — это конечная дата стачи проекта или задачи…