Конзолни приложения
3 декември 2024
Административни неща
- Второто домашно приключи
- Без лекция на 19 декември, четвъртък
IO типове и trait-ове
std::io::Read,std::io::Write: Базови trait-ове за четене и писане
IO типове и trait-ове
std::io::Read,std::io::Write: Базови trait-ове за четене и писанеFile,Stdin,&[u8],BufReader-- имплементиратReadBufReader-- имплементираBufRead
IO типове и trait-ове
std::io::Read,std::io::Write: Базови trait-ове за четене и писанеFile,Stdin,&[u8],BufReader-- имплементиратReadBufReader-- имплементираBufReadFile,StdoutиStderr,Vec<u8>,BufWriter-- имплементиратWrite
IO типове и trait-ове
Добре е да ползваме trait-ове когато можем.
impl InputFile {
fn from(mut contents: impl io::Read) -> io::Result<InputFile> {
// ...
}
// или:
fn from<R: io::Read>(mut contents: R) -> io::Result<InputFile> {
// ...
}
}
Така, функцията InputFile::from може да се извика с файл, но може и със stdin, а може и просто със slice от байтове, идващи от някакъв тестов низ.
"Program to an interface, not an implementation" -- често срещан съвет, който често е прав.
Странична бележка
"Program to an interface, not an implementation" -- не е закон, просто добър съвет.
FilevsTcpStreamFilevsVec<u8>FilevsStdin/StdoutFile-- няма име
SemVer и Cargo
Reference: https://doc.rust-lang.org/cargo/reference/semver.html
String vs &str
fn from_str(s: &str)-> Защо&str, а неString?
String vs &str
fn from_str(s: &str)-> Защо&str, а неString?- Със
&str-- можем да извикаме функцията и със&str, и със&String from_str("foobar")from_str(&String::from("foobar"))
String vs &str
fn from_str(s: &str)-> Защо&str, а неString?- Със
&str-- можем да извикаме функцията и със&str, и със&String from_str("foobar")from_str(&String::from("foobar"))- Аналогично, обикновено използваме
&[T]като вход на функция, а не&Vec<T>
PathBuf и Path
PathBuf и Path
std::path::PathBuf-- стойност със ownership (катоString,Vec)std::path::Path-- нейния "slice" тип (еквивалент на&str,&[T])
PathBuf и Path
std::path::PathBuf-- стойност със ownership (катоString,Vec)std::path::Path-- нейния "slice" тип (еквивалент на&str,&[T])
let home_dir = PathBuf::from("/home/andrew");
println!("{:?}", home_dir);
let home_dir_slice: &Path = home_dir.as_path();
println!("{:?}", home_dir_slice);
"/home/andrew" "/home/andrew"
use std::path::{PathBuf,Path};
fn main() {
let home_dir = PathBuf::from("/home/andrew");
println!("{:?}", home_dir);
let home_dir_slice: &Path = home_dir.as_path();
println!("{:?}", home_dir_slice);
}
PathBuf и Path
let file_path = PathBuf::from("/home/andrew/file.txt");
println!("{:?}", file_path);
let only_file: &Path = file_path.strip_prefix("/home/andrew").unwrap();
println!("{:?}", only_file);
let standalone_path: &Path = Path::new("file.txt");
println!("{:?}", standalone_path);
"/home/andrew/file.txt" "file.txt" "file.txt"
use std::path::{PathBuf,Path};
fn main() {
let file_path = PathBuf::from("/home/andrew/file.txt");
println!("{:?}", file_path);
let only_file: &Path = file_path.strip_prefix("/home/andrew").unwrap();
println!("{:?}", only_file);
let standalone_path: &Path = Path::new("file.txt");
println!("{:?}", standalone_path);
}
PathBuf и Path
let file_path: &Path = Path::new("file.txt");
println!("{:?}", file_path.to_path_buf());
println!("{:?}", file_path.to_owned());
println!("{:?}", file_path.canonicalize());
"file.txt" "file.txt" Err(Os { code: 2, kind: NotFound, message: "No such file or directory" })
use std::path::Path;
fn main() {
let file_path: &Path = Path::new("file.txt");
println!("{:?}", file_path.to_path_buf());
println!("{:?}", file_path.to_owned());
println!("{:?}", file_path.canonicalize());
}
Debug & Display
println!("{:?}", PathBuf::from("file.txt"));
"file.txt"
use std::path::PathBuf;
fn main() {
println!("{:?}", PathBuf::from("file.txt"));
}
println!("{}", PathBuf::from("file.txt"));
error[E0277]: `PathBuf` doesn't implement `std::fmt::Display` --> src/bin/main_7c03549f224654e1a7d4fd9f1586b8229e8e2a81.rs:3:16 | 3 | println!("{}", PathBuf::from("file.txt")); | ^^^^^^^^^^^^^^^^^^^^^^^^^ `PathBuf` cannot be formatted with the default formatter; call `.display()` on it | = help: the trait `std::fmt::Display` is not implemented for `PathBuf` = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead = note: call `.display()` or `.to_string_lossy()` to safely print paths, as they may contain non-Unicode data = note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info) For more information about this error, try `rustc --explain E0277`. error: could not compile `rust` (bin "main_7c03549f224654e1a7d4fd9f1586b8229e8e2a81") due to 1 previous error
use std::path::PathBuf;
fn main() {
println!("{}", PathBuf::from("file.txt"));
}
Debug & Display
println!("{}", PathBuf::from("file.txt").display());
println!("{}", Path::new("file.txt").display());
file.txt file.txt
use std::path::{PathBuf,Path};
fn main() {
println!("{}", PathBuf::from("file.txt").display());
println!("{}", Path::new("file.txt").display());
}
Пътищата на файловата система не са задължително UTF-8-кодирани. Как точно са кодирани зависи супер много от операционната система.
Пътища и низове
pub fn as_os_str(&self) -> &OsStr-- Връща ви низ, както е кодиран natively за операционната система.pub fn to_str(&self) -> Option<&str>-- Връща ви UTF-8 string slice, ако е възможно да се кодира като UTF-8.pub fn to_string_lossy(&self) -> Cow<'_, str>-- Връща ви низ с въпросчета за невалиден UTF-8 (U+FFFD: �).
Пътища и низове
pub fn as_os_str(&self) -> &OsStr-- Връща ви низ, както е кодиран natively за операционната система.pub fn to_str(&self) -> Option<&str>-- Връща ви UTF-8 string slice, ако е възможно да се кодира като UTF-8.pub fn to_string_lossy(&self) -> Cow<'_, str>-- Връща ви низ с въпросчета за невалиден UTF-8 (U+FFFD: �).- Ако искате да манипулирате пътища -- ползвате методите на
PathBufиPath. - Ако искате да ги показвате на потребителя, викате им
.display(). - Side note:
std::borrow::Cowе интересен тип.
Полезни пакети
- walkdir: За ефективно обикаляне на файловата система.
- dirs: За намиране на "стандартни" директории в зависимост от операционната система.
- directories: Подобно на
dirs, но включва и конфигурационни директории за инсталиране на приложения.
Файлове
std::fs
Файлове
std::fsFile::open-- отваря път за четене
Файлове
std::fsFile::open-- отваря път за четенеFile::open<P: AsRef<Path>>(path: P) -> fs::Result<File>
Файлове
std::fsFile::open-- отваря път за четенеFile::open<P: AsRef<Path>>(path: P) -> fs::Result<File>AsRef<Path>-- нещо, което може евтино да се конвертира доPath, с метода.as_ref()File::open(PathBuf::from("..."))File::open(Path::new("..."))File::open("...")
Файлове
File::create-- отваря файл за писане- Ако не съществува, ще го създаде, ако съществува, ще премахне съдържанието му
Файлове
fs::OpenOptions::new -- отваря файл за четене, писане, append-ване, каквото ви душа иска
let file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open("foo.txt");
Файлове
fs::read_to_string -- най-лесния и удобен начин за четене на целия файл директно в низ. Ползвайте го смело за тестове, пък и не само. Понякога, просто знаете, че файла няма да е огромен и че ще ви трябва целия in-memory.
Файлове
std::io::stdin() -> Stdin-- имплементираReadstd::io::stdout() -> Stdout-- имплементираWritestd::io::stderr() -> Stderr-- имплементираWrite
Файлове
std::io::stdin() -> Stdin-- имплементираReadstd::io::stdout() -> Stdout-- имплементираWritestd::io::stderr() -> Stderr-- имплементираWrite- Буферират се по редове -- при писане, може да ползвате
.flush()за да изкарате нещо, което не е цял ред.
Файлове
std::io::stdin() -> Stdin-- имплементираReadstd::io::stdout() -> Stdout-- имплементираWritestd::io::stderr() -> Stderr-- имплементираWrite- Буферират се по редове -- при писане, може да ползвате
.flush()за да изкарате нещо, което не е цял ред. - Lock-ват се автомагично с мутекс, но можете ръчно да викнете
.lock()за ексклузивен достъп.
include макроси
- Четене на данни по време на компилация.
include макроси
- Четене на данни по време на компилация.
include_bytes!-- набива някакъв файл в кода като статичен комплект от байтове (&'static [u8; N]).include_str!-- набива някакъв файл в кода като&'static str.
include макроси
- Четене на данни по време на компилация.
include_bytes!-- набива някакъв файл в кода като статичен комплект от байтове (&'static [u8; N]).include_str!-- набива някакъв файл в кода като&'static str.
const MAIN_JS: &'static str = include_str!("../res/js/main.js");
const MAIN_CSS: &'static str = include_str!("../res/style/main.css");
const GITHUB_CSS: &'static str = include_str!("../res/style/github.css");
С include_bytes! можете да вкарвате и други ресурси, като картинки, аудио…
std::env
env::args() -- дава итератор по входа на програмата. Примерно:
$ cargo run one two three
use std::env;
fn main() {
println!("{:?}", env::args().collect::<Vec<String>>());
// => ["target/debug/scratch", "one", "two", "three"]
use std::ffi::OsString;
println!("{:?}", env::args_os().collect::<Vec<OsString>>());
// => ["target/debug/scratch", "one", "two", "three"]
}
std::env
env::var() -- достъп до environment променливи, може да извади грешка, ако не са валиден unicode, имат \0 байтове и т.н. Ако супер много се налага, има env::var_os().
if env::var("DEBUG").is_ok() {
println!("Debug info: ...");
}
$ DEBUG=1 cargo run
Env logger
Удобен проект за по-сериозно log-ване: env_logger + log (пример от друг проект, rust-quickmd)
fn init_logging() {
// Release logging:
// - Warnings and errors
// - No timestamps
// - No module info
#[cfg(not(debug_assertions))]
env_logger::builder().
default_format_module_path(false).
default_format_timestamp(false).
filter_level(log::LevelFilter::Warn).
init();
// Debug logging:
// - All logs
// - Full info
#[cfg(debug_assertions)]
env_logger::builder().
filter_level(log::LevelFilter::Debug).
init();
}
Env logger
Примерна употреба:
debug!("Building HTML:");
debug!(" > home_path = {}", home_path);
debug!(" > scroll_top = {}", scroll_top);
При пускане на програмата в release mode, премахваме много допълнителна информация като модули и timestamp, и показваме всичко от "warning" нагоре.
При пускане в debug mode (стандарното при cargo run):
[2019-11-29T08:57:33Z DEBUG quickmd::assets] Building HTML:
[2019-11-29T08:57:33Z DEBUG quickmd::assets] > home_path = /home/andrew
[2019-11-29T08:57:33Z DEBUG quickmd::assets] > scroll_top = 0
[2019-11-29T08:57:33Z DEBUG quickmd::ui] Loading HTML:
[2019-11-29T08:57:33Z DEBUG quickmd::ui] > output_path = /tmp/.tmpWq7EYh/output.html
[2019-11-29T08:57:33Z DEBUG quickmd::background] Watching ~/.quickmd.css
По-сложна обработка на аргументи чрез structopt
[dependencies]
structopt = "*"
structopt-derive = "*"
По-сложна обработка на аргументи чрез structopt
$ hangman --wordlist=words.txt --attempts=10 --debug
$ hangman -w words.txt -a 10 -d
$ hangman --help
$ hangman --version
По-сложна обработка на аргументи чрез structopt
use structopt_derive::StructOpt;
#[derive(StructOpt, Debug)]
#[structopt(name="hangman", about="A game of Hangman")]
pub struct Options {
#[structopt(short="w", long="wordlist", help="The path to a word list")]
wordlist_path: Option<PathBuf>,
#[structopt(short="a", long="attempts", help="The number of attempts to guess the word", default_value="10")]
attempts: u32,
#[structopt(short="d", long="debug", help="Show debug info")]
debug: bool,
}
Същото става и чрез clap::Parser
#[derive(clap::Parser, Debug)]
#[command(name="hangman", about="A game of Hangman")]
pub struct Options {
#[arg(short='w', long="wordlist", help="The path to a word list")]
pub wordlist_path: Option<PathBuf>,
#[arg(short='a', long="attempts", help="The number of attempts to guess the word", default_value="10")]
pub attempts: u32,
#[arg(short='d', long="debug", help="Show debug info")]
pub debug: bool,
}
Като ви трябва clap = { version = "*", features = ["derive"] } в Cargo.toml
Вижте примерно https://github.com/clap-rs/clap/blob/25f9fda0a3aac97bce6af78bb1ae1f01aaa7806a/examples/tutorial_derive/01_quick.rs за употреба.
По-сложна обработка на аргументи чрез structopt
$ hangman --help
hangman 0.1.0
A game of Hangman
USAGE:
hangman [FLAGS] [OPTIONS]
FLAGS:
-d, --debug Show debug info
-h, --help Prints help information
-V, --version Prints version information
OPTIONS:
-a, --attempts <attempts> The number of attempts to guess the word [default: 10]
-w, --wordlist <wordlist-path> The path to a word list
(С cargo run ще е cargo run -- --help)
Ограничения на structopt/clap-derive
- Трудно дебъгване на грешки (доста compile-time магия)
- Липса на гъвкавост?
- В краен случай, може би решението е (използвания отдолу пакет) clap
- Като алтернативи вижте:
Локално инсталиране на пакет
cargo install --path .- Инсталира нещата в
~/.cargo/bin/ hangman -w wordlist.txt- Може да имаме повече от един executable file. Слагаме ги в
src/bin/, всеки си има собствен main. Може да ги викаме локално съсcargo run --bin <име-на-файла>, а при cargo install ще си се инсталират като отделни binary-та. Пример от друг проект: rust-id3-image
Features
Може да си намалите малко броя dependencies, като премахнете default features:
[dependencies]
structopt = { version = "*", default-features = false }
structopt-derive = "*"
Документацията е в clap: https://docs.rs/clap/*/clap/#features-enabled-by-default
Версии
Окей е да използваме * за версия, когато си пишем кода -- конкретните версии се фиксират в Cargo.lock. Ако искаме да го публикуваме като пакет в crates.io, използвайки cargo publish, трябва да фиксираме конкретни версии, примерно:
[dependencies]
dirs = "2.0.2"
rand = "0.7.2"
structopt = { version = "0.3.5", default-features = false }
structopt-derive = "0.3.5"
Може да намерите плъгини за редакторите си, които да ви помогнат с избора на версии: