Конзолни приложения

3 декември 2024

Административни неща

Hangman

Demo

(Initial commit: ea0a5982b477481a24262b6775c9369b1f3f61d3)

Пълния код: rust-hangman

Видео запис от 2022: видео 1, видео 2

IO типове и trait-ове

IO типове и trait-ове

IO типове и trait-ове

IO типове и trait-ове

Добре е да ползваме trait-ове когато можем.

1 2 3 4 5 6 7 8 9 10
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" -- не е закон, просто добър съвет.

SemVer и Cargo

Reference: https://doc.rust-lang.org/cargo/reference/semver.html

String vs &str

String vs &str

String vs &str

PathBuf и Path

PathBuf и Path

PathBuf и Path

1 2 3 4 5
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

1 2 3 4 5 6 7 8
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

1 2 3 4 5
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

1
println!("{:?}", PathBuf::from("file.txt"));
"file.txt"
use std::path::PathBuf;
fn main() {
println!("{:?}", PathBuf::from("file.txt"));
}
1
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

1 2
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-кодирани. Как точно са кодирани зависи супер много от операционната система.

Пътища и низове

Пътища и низове

Полезни пакети

Файлове

Файлове

Файлове

Файлове

Файлове

Файлове

fs::OpenOptions::new -- отваря файл за четене, писане, append-ване, каквото ви душа иска

1 2 3 4 5
let file = OpenOptions::new()
            .read(true)
            .write(true)
            .create(true)
            .open("foo.txt");

Файлове

fs::read_to_string -- най-лесния и удобен начин за четене на целия файл директно в низ. Ползвайте го смело за тестове, пък и не само. Понякога, просто знаете, че файла няма да е огромен и че ще ви трябва целия in-memory.

Файлове

Файлове

Файлове

include макроси

include макроси

include макроси

1 2 3
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() -- дава итератор по входа на програмата. Примерно:

1
$ cargo run one two three
1 2 3 4 5 6 7 8 9 10
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().

1 2 3
if env::var("DEBUG").is_ok() {
    println!("Debug info: ...");
}
1
$ DEBUG=1 cargo run

Env logger

Удобен проект за по-сериозно log-ване: env_logger + log (пример от друг проект, rust-quickmd)

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
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

Примерна употреба:

1 2 3
debug!("Building HTML:");
debug!(" > home_path  = {}", home_path);
debug!(" > scroll_top = {}", scroll_top);

При пускане на програмата в release mode, премахваме много допълнителна информация като модули и timestamp, и показваме всичко от "warning" нагоре.

При пускане в debug mode (стандарното при cargo run):

1 2 3 4 5 6
[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

1 2 3
[dependencies]
structopt = "*"
structopt-derive = "*"

По-сложна обработка на аргументи чрез structopt

1 2 3 4
$ hangman --wordlist=words.txt --attempts=10 --debug
$ hangman -w words.txt -a 10 -d
$ hangman --help
$ hangman --version

По-сложна обработка на аргументи чрез structopt

1 2 3 4 5 6 7 8 9 10 11 12 13 14
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

1 2 3 4 5 6 7 8 9 10 11 12
#[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

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
$ 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

Локално инсталиране на пакет

Features

Може да си намалите малко броя dependencies, като премахнете default features:

1 2 3
[dependencies]
structopt = { version = "*", default-features = false }
structopt-derive = "*"

Документацията е в clap: https://docs.rs/clap/*/clap/#features-enabled-by-default

Версии

Окей е да използваме * за версия, когато си пишем кода -- конкретните версии се фиксират в Cargo.lock. Ако искаме да го публикуваме като пакет в crates.io, използвайки cargo publish, трябва да фиксираме конкретни версии, примерно:

1 2 3 4 5
[dependencies]
dirs = "2.0.2"
rand = "0.7.2"
structopt = { version = "0.3.5", default-features = false }
structopt-derive = "0.3.5"

Може да намерите плъгини за редакторите си, които да ви помогнат с избора на версии:

Още полезни пакети

Въпроси