๊ด€๋ฆฌ ๋ฉ”๋‰ด

IT’s Portfolio

[Rust] Start Rust (Day 21) - An I/O Project: Building a Command Line Program ๋ณธ๋ฌธ

Development Study/Rust

[Rust] Start Rust (Day 21) - An I/O Project: Building a Command Line Program

f1r3_r41n 2023. 8. 24. 15:14
728x90
๋ฐ˜์‘ํ˜•

๐Ÿฆ€ Rust Day 21

๐Ÿณ๏ธ An I/O Project: Building a Command Line Program

  • ๋Ÿฌ์ŠคํŠธ์˜ ์†๋„์™€ ์•ˆ์ „์„ฑ, ๋‹จ์ผ ๋ฐ”์ด๋„ˆ๋ฆฌ ์ถœ๋ ฅ ๊ทธ๋ฆฌ๊ณ  ๊ต์ฐจ ํ”Œ๋žซํผ ์ง€์› ๋“ฑ์˜ ํŠน์ง•์€ ๋ช…๋ น์ค„ ๋„๊ตฌ๋ฅผ ๋งŒ๋“œ๋Š” ๋ฐ ์ข‹์Œ
  • grep(globally search a regular experssion and print)

1๏ธโƒฃ ๋ช…๋ น์ค„ ์ธ์ˆ˜ ์ฒ˜๋ฆฌํ•˜๊ธฐ

  • ํ”„๋กœ์ ํŠธ ์ด๋ฆ„ : minigrep
    • ํŒŒ์ผ๋ช…๊ณผ ๊ฒ€์ƒ‰ํ•  ๋ฌธ์ž์—ด ๋“ฑ ๋‘ ๊ฐœ์˜ ๋ช…๋ น์ค„ ์ธ์ˆ˜๋ฅผ ์ฒ˜๋ฆฌ
      • cargo run [searchstring] [example-filename.txt]

๐Ÿค” ์ธ์ˆ˜์˜ ๊ฐ’ ์ฝ์–ด์˜ค๊ธฐ

  • std::env::args : ํ‘œ์ค€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์—์„œ ์ œ๊ณตํ•˜๋Š” ์ „๋‹ฌ๋œ ๋ช…๋ น์ค„ ์ธ์ˆ˜๋ฅผ ์ฝ๋Š” ํ•จ์ˆ˜
    • ๋ช…๋ น์ค„ ์ธ์ˆ˜์˜ ๋ฐ˜๋ณต์ž(iterator) ์ œ๊ณต
use std::env;

fn main() {
    let args = env::args().collect::<Vec<String>>();
    println!("{:?}", args);
}
  • ์›ํ•˜๋Š” ํ•จ์ˆ˜๊ฐ€ ํ•˜๋‚˜ ์ด์ƒ์˜ ๋ชจ๋“ˆ์— ์ค‘์ฒฉ๋˜์—ˆ์„ ๋•Œ๋Š” ํ•จ์ˆ˜ ๋Œ€์‹  ๋ถ€๋ชจ ๋ชจ๋“ˆ์„ ๋ฒ”์œ„๋กœ ๊ฐ€์ ธ์˜ค๋Š” ๊ฒƒ์ด ํŽธ๋ฆฌํ•จ
  • std::env::args ํ•จ์ˆ˜๋Š” ์ธ์ˆ˜์— ์œ ํšจํ•˜์ง€ ์•Š์€ ์œ ๋‹ˆ์ฝ”๋“œ๊ฐ€ ํฌํ•จ๋˜์–ด ์žˆ์œผ๋ฉด ํŒจ๋‹‰์„ ๋ฐœ์ƒ์‹œํ‚ด
  • std::env::args_os ํ•จ์ˆ˜๋Š” ์œ ํšจํ•˜์ง€ ์•Š์€ ์œ ๋‹ˆ์ฝ”๋“œ๋ฅผ ํ—ˆ์šฉํ•˜์ง€๋งŒ String์ด ์•„๋‹Œ OsString ๊ฐ’์˜ ๋ฐ˜๋ณต์ž๋ฅผ ๋ฆฌํ„ดํ•จ
    • OsString ๊ฐ’์€ ํ”Œ๋žซํผ๋งˆ๋‹ค ๋‹ค๋ฅด๋ฉฐ String๋ณด๋‹ค ํ›จ์”ฌ ๋ณต์žกํ•จ
Input:
    cargo run
Output:
    ["target/debug/minigrep"]
Input:
    cargo run Hi, bash!
Output:
    ["target/debug/minigrep", "Hi,", "bash!"]
  • ๋ฒกํ„ฐ๋Š” ๋ฐ”์ด๋„ˆ๋ฆฌ์˜ ์ด๋ฆ„์„ ์„ ๋‘๋กœ ๊ณต๋ฐฑ์„ ๊ธฐ์ค€์œผ๋กœ ์ธ์ž๊ฐ€ ๋‚˜์—ด๋˜์–ด ์žˆ์Œ

๐Ÿค” ์ธ์ˆซ๊ฐ’์„ ๋ณ€์ˆ˜์— ์ €์žฅํ•˜๊ธฐ

use std::env;

fn main() {
    let args = env::args().collect::<Vec<String>>();
    let q = &args[1];
    let f = &args[2];
    println!("๊ฒ€์ƒ‰์–ด: {q}\n๋Œ€์ƒ ํŒŒ์ผ: {f}");
}
Input: 
    cargo run test sample.txt
Output:
    ๊ฒ€์ƒ‰์–ด: test
    ๋Œ€์ƒ ํŒŒ์ผ: sample.txt

2๏ธโƒฃ ํŒŒ์ผ ์ฝ๊ธฐ

I'm nobody! Who are you?
Are you nobody, too?
Then there's a pair of us - don't tell!
They'd banish us, you know.

How dreary to be somebody!
How public, like a frog
To tell your name the livelong day
To an admiring bog!
  • ๊ฐ™์€ ํ…์ŠคํŠธ๊ฐ€ ์—ฌ๋Ÿฌ ์ค„์— ๊ฑธ์ณ ๋ฐ˜๋ณต๋œ ์ž‘์€ ํฌ๊ธฐ์˜ ํ…์ŠคํŠธ ํŒŒ์ผ poem.txt
use std::env;

fn main() {
    let args = env::args().collect::<Vec<String>>();
    let q = &args[1];
    let f = &args[2];
    let c = std::fs::read_to_string(f).unwrap();
    println!("๊ฒ€์ƒ‰์–ด: {q}\n๋Œ€์ƒ ํŒŒ์ผ: {f}\nํŒŒ์ผ ๋‚ด์šฉ:\n{c}");
}
  • std::fs : ํŒŒ์ผ์„ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ชจ๋“ˆ

3๏ธโƒฃ ๋ชจ๋“ˆํ™”์™€ ์—๋Ÿฌ ์ฒ˜๋ฆฌ ํ–ฅ์ƒ์„ ์œ„ํ•œ ๋ฆฌํŒฉํ† ๋ง

  • ํ•จ์ˆ˜ ํ•˜๋‚˜๊ฐ€ n ๊ฐ€์ง€์˜ ์ž‘์—…์„ ๋ชจ๋‘ ์ฒ˜๋ฆฌํ•˜๋ฉด ๋น„ํšจ์œจ์ ์ž„
    • ํ•จ์ˆ˜์˜ ๋ชฉ์ ์„ ๋ช…ํ™•ํžˆ ํ•˜๊ธฐ๊ฐ€ ์–ด๋ ค์›Œ์ง€๋Š” ๊ฒƒ์€ ๋ฌผ๋ก , ํ…Œ์ŠคํŠธ๋„ ์‰ฝ์ง€ ์•Š๊ฑฐ๋‹ˆ์™€ ๋‹ค๋ฅธ ๋™์ž‘์— ์˜ํ–ฅ์„ ์ฃผ์ง€ ์•Š์œผ๋ฉด์„œ ์ฝ”๋“œ๋ฅผ ๋ณ€๊ฒฝํ•˜๊ธฐ๋„ ์–ด๋ ค์›Œ์ง
    • ํ•จ์ˆ˜๊ฐ€ ๊ธธ์–ด์งˆ์ˆ˜๋ก ๋” ๋งŽ์€ ๋ณ€์ˆ˜๋ฅผ ์„ ์–ธํ•ด์•ผ ํ•˜๊ณ , ๋ฒ”์œ„์— ๋” ๋งŽ์€ ๋ณ€์ˆ˜๋ฅผ ์„ ์–ธํ• ์ˆ˜๋ก ๊ฐ ๋ณ€์ˆ˜์˜ ๋ชฉ์ ์„ ๋ช…ํ™•ํžˆ ํ•˜๊ธฐ ์–ด๋ ค์›Œ์ง
      • ์„ค์ • ๋ณ€์ˆ˜๋ฅผ ํ•˜๋‚˜์˜ ๊ตฌ์กฐ์ฒด์— ๋ชจ์•„์„œ ๊ทธ ๋ชฉ์ ์„ ๋ช…ํ™•ํžˆ ํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Œ

๐Ÿค” ๋ฐ”์ด๋„ˆ๋ฆฌ ํ”„๋กœ์ ํŠธ์˜ ๊ด€์‹ฌ ๋ถ„๋ฆฌ

  • ๋ฐ”์ด๋„ˆ๋ฆฌ ํ”„๋กœ๊ทธ๋žจ main ํ•จ์ˆ˜์˜ ํฌ๊ธฐ๊ฐ€ ์ฆ๊ฐ์— ๋”ฐ๋ผ ๊ด€์‹ฌ์„ ๋ถ„๋ฆฌํ•˜๊ธฐ ์œ„ํ•œ ์ง€์นจ
    • ํ”„๋กœ๊ทธ๋žจ์„ main.rs์™€ lib.rs ํŒŒ์ผ๋กœ ๋ถ„๋ฆฌํ•˜๊ณ  ํ”„๋กœ๊ทธ๋žจ์˜ ๋กœ์ง์„ lib.rs ํŒŒ์ผ๋กœ ์˜ฎ๊ธด๋‹ค.
    • ๋ช…๋ น์ค„ ๊ตฌ๋ฌธ๋ถ„์„ ๋กœ์ง์ด ์ถฉ๋ถ„ํžˆ ์ž‘๋‹ค๋ฉด main.rs ํŒŒ์ผ์— ๋‚จ๊ฒจ๋‘”๋‹ค.
    • ๋ช…๋ น์ค„ ๊ตฌ๋ฌธ๋ถ„์„ ๋กœ์ง์ด ๋ณต์žกํ•ด์ง€๊ธฐ ์‹œ์ž‘ํ•˜๋ฉด main.rs ํŒŒ์ผ์—์„œ ์ถ”์ถœํ•ด lib.rs ํŒŒ์ผ๋กœ ์˜ฎ๊ธด๋‹ค.
  • main ํ•จ์ˆ˜์— ๋‚จ๊ฒจ์ง€๋Š” ์—ญํ• 
    • ์ธ์ˆซ๊ฐ’์„ ์ด์šฉํ•ด ๋ช…๋ น์ค„ ๊ตฌ๋ฌธ๋ถ„์„ ๋กœ์ง์„ ํ˜ธ์ถœ
    • ๊ธฐํƒ€ ๋‹ค๋ฅธ ์„ค์ • ์ ์šฉ
    • lib.rs ํŒŒ์ผ์˜ run ํ•จ์ˆ˜ ํ˜ธ์ถœ
    • run ํ•จ์ˆ˜๊ฐ€ ์—๋Ÿฌ๋ฅผ ๋ฆฌํ„ดํ•  ๊ฒฝ์šฐ ์ด์— ๋Œ€ํ•œ ์ฒ˜๋ฆฌ
  • main.rs ํŒŒ์ผ์€ ํ”„๋กœ๊ทธ๋žจ์˜ ์‹คํ–‰์„ ๋‹ด๋‹นํ•˜๋ฉฐ lib.rs ํŒŒ์ผ์€ ์ฒ˜๋ฆฌํ•  ๋ชจ๋“  ์ž‘์—…์˜ ๋กœ์ง์„ ๋‹ด๋‹น

(1) ์ธ์ˆ˜ ๊ตฌ๋ฌธ๋ถ„์„ ๋ถ„๋ฆฌํ•˜๊ธฐ

  • main.rs ํŒŒ์ผ์—์„œ ์ƒˆ๋กœ์šด ํ•จ์ˆ˜ ์ œ์ž‘

(2) ์„ค์ •๊ฐ’์˜ ๊ทธ๋ฃนํ™”

  • ๋ณตํ•ฉ ํƒ€์ž…(complex type) ์„ ์‚ฌ์šฉํ•˜๋Š” ํŽธ์ด ๋” ์ ์ ˆํ•œ ์ƒํ™ฉ์—์„œ ๊ธฐ๋ณธ ์ž๋ฃŒํ˜•์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์€ ๊ธฐ๋ณธ ์ž๋ฃŒํ˜• ๊ฐ•๋ฐ•(primitive obsession) ์ด๋ผ๋Š” ์•ˆํ‹ฐ ํŒจํ„ด(anti-pattern) ์ž„

(3) ๊ตฌ์กฐ์ฒด์˜ ์ƒ์„ฑ์ž

use std::env;

struct Config {
    query: String,
    filename: String,
}

impl Config {
    fn new(args: &[String]) -> Config {
        Config {
            query: args[1].clone(),
            filename: args[2].clone(),
        }
    }
}

fn main() {
    let args = env::args().collect::<Vec<String>>();
    let config = Config::new(&args);
    let c = std::fs::read_to_string(&config.filename).unwrap();
    println!("๊ฒ€์ƒ‰์–ด: {}\n๋Œ€์ƒ ํŒŒ์ผ: {}\nํŒŒ์ผ ๋‚ด์šฉ:\n{c}", config.query, config.filename);
}
๊ฒ€์ƒ‰์–ด: the
๋Œ€์ƒ ํŒŒ์ผ: poem.txt
ํŒŒ์ผ ๋‚ด์šฉ:
I'm nobody! Who are you?
Are you nobody, too?
Then there's a pair of us - don't tell!
They'd banish us, you know.

How dreary to be somebody!
How public, like a frog
To tell your name the livelong day
To an admiring bog!

๐Ÿค” ์—๋Ÿฌ ์ฒ˜๋ฆฌ ๊ฐœ์„ ํ•˜๊ธฐ

use std::env;
use std::process;

struct Config {
    query: String,
    filename: String,
}

impl Config {
    fn new(args: &[String]) -> Result<Config, &'static str> {
        if args.len() < 3 {
            return Err("ํ•„์š”ํ•œ ์ธ์ˆ˜๊ฐ€ ์ง€์ •๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.");
        }
        Ok(Config {
            query: args[1].clone(),
            filename: args[2].clone(),
        })
    }
}

fn main() {
    let args = env::args().collect::<Vec<String>>();
    let config = Config::new(&args).unwrap_or_else(
        |err| {
            println!("์ธ์ˆ˜๋ฅผ ๊ตฌ๋ฌธ๋ถ„์„ํ•˜๋Š” ๋™์•ˆ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค: {}", err);
            process::exit(1);
        }
    );
    let c = std::fs::read_to_string(&config.filename).unwrap();
    println!("๊ฒ€์ƒ‰์–ด: {}\n๋Œ€์ƒ ํŒŒ์ผ: {}\nํŒŒ์ผ ๋‚ด์šฉ:\n{c}", config.query, config.filename);
}
  • unwrap_or_else
    • ํ‘œ์ค€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ Result<T, E> ํƒ€์ž…์— ์ •์˜ํ•œ ๋ฉ”์„œ๋“œ
    • Result ๊ฐ’์ด Ok ๊ฐ’์ด๋ฉด unwrap ๋ฉ”์„œ๋“œ์™€ ์œ ์‚ฌํ•˜๊ฒŒ ๋™์ž‘ํ•จ
      • Ok ์—ด๊ฒƒ๊ฐ’์— ์ €์žฅ๋œ ๊ฐ’ ๋ฆฌํ„ด
    • Err ๊ฐ’์ด๋ฉด ํด๋กœ์ €๋ฅผ ์ด์šฉํ•ด unwrap_or_else ๋ฉ”์„œ๋“œ์— ์ „๋‹ฌํ•œ ์ต๋ช… ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•จ
      • ์ •์  ๋ฌธ์ž์—ด์„ ์ต๋ช… ํ•จ์ˆ˜์˜ ํŒŒ์ดํ”„ ๋ฌธ์ž ์‚ฌ์ด์— ์„ ์–ธํ•œ ์ธ์ˆ˜์— ์ „๋‹ฌ
์ธ์ˆ˜๋ฅผ ๊ตฌ๋ฌธ๋ถ„์„ํ•˜๋Š” ๋™์•ˆ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค: ํ•„์š”ํ•œ ์ธ์ˆ˜๊ฐ€ ์ง€์ •๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.

๐Ÿค” main ํ•จ์ˆ˜์—์„œ ๋กœ์ง ๋ถ„๋ฆฌํ•˜๊ธฐ

use std::env;
use std::process;

struct Config {
    query: String,
    filename: String,
}

impl Config {
    fn new(args: &[String]) -> Result<Config, &'static str> {
        if args.len() < 3 {
            return Err("ํ•„์š”ํ•œ ์ธ์ˆ˜๊ฐ€ ์ง€์ •๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.");
        }
        Ok(Config {
            query: args[1].clone(),
            filename: args[2].clone(),
        })
    }
}

fn run(config: Config) {
    let c = std::fs::read_to_string(config.filename)
        .unwrap();
    println!("ํŒŒ์ผ ๋‚ด์šฉ:\n{c}");
}

fn main() {
    let args = env::args().collect::<Vec<String>>();
    let config = Config::new(&args).unwrap_or_else(
        |err| {
            println!("์ธ์ˆ˜๋ฅผ ๊ตฌ๋ฌธ๋ถ„์„ํ•˜๋Š” ๋™์•ˆ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค: {}", err);
            process::exit(1);
        }
    );
    println!("๊ฒ€์ƒ‰์–ด: {}\nํŒŒ์ผ ์ด๋ฆ„: {}", config.query, config.filename);
    run(config);
}
  • run ํ•จ์ˆ˜๋กœ ๋กœ์ง ๋ถ„๋ฆฌ
fn run(config: Config) -> Result<(), Box<dyn Error>> {
    let c = std::fs::read_to_string(config.filename)?;
    println!("ํŒŒ์ผ ๋‚ด์šฉ:\n{c}");
    Ok(())
}
  • Result ํƒ€์ž…์„ ๋ฆฌํ„ดํ•˜๋Š” run ํ•จ์ˆ˜
  • Ok ์ƒํ™ฉ์—์„œ๋Š” ์œ ๋‹› ํƒ€์ž… () ๋ฆฌํ„ด
  • Err ์ƒํ™ฉ์—์„œ๋Š” ํŠธ๋ ˆ์ดํŠธ ๊ฐ์ฒด์ธ Box<dyn Error> ๋ฆฌํ„ด
    • Box<dyn Error> : Error ํŠธ๋ ˆ์ดํŠธ๋ฅผ ๊ตฌํ˜„ํ•˜๋Š” ํƒ€์ž…์„ ๋ฆฌํ„ดํ•˜์ง€๋งŒ, ๋ฆฌํ„ด๋  ๊ฐ’์˜ ํƒ€์ž…์„ ํŠน์ •ํ•˜์ง€๋Š” ์•Š์Œ
      • ์—ฌ๋Ÿฌ ์—๋Ÿฌ ์ƒํ™ฉ์—์„œ ๊ฐ๊ธฐ ๋‹ค๋ฅธ ํƒ€์ž…์„ ๋ฆฌ๋„กํ•  ์ˆ˜ ์žˆ๋Š” ์œ ์—ฐ์„ฑ์„ ํ™•๋ณดํ•  ์ˆ˜ ์žˆ์Œ
  • expect ๋ฉ”์„œ๋“œ ๋Œ€์‹ ์— ? ์—ฐ์‚ฐ์ž ์‚ฌ์šฉ
    • ? : ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•  ๋•Œ ํ˜„์žฌ ํ•จ์ˆ˜์˜ ํ˜ธ์ถœ์ž์—๊ฒŒ ์—๋Ÿฌ๊ฐ’์„ ๋ฆฌํ„ดํ•  ์ˆ˜ ์žˆ์Œ
fn main() {
    let args = env::args().collect::<Vec<String>>();
    let config = Config::new(&args).unwrap_or_else(
        |err| {
            println!("์ธ์ˆ˜๋ฅผ ๊ตฌ๋ฌธ๋ถ„์„ํ•˜๋Š” ๋™์•ˆ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค: {}", err);
            process::exit(1);
        }
    );
    println!("๊ฒ€์ƒ‰์–ด: {}\nํŒŒ์ผ ์ด๋ฆ„: {}", config.query, config.filename);

    if let Err(e) = run(config) {
        println!("์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์—๋Ÿฌ: {}", e);
        process::exit(1);
    }
}
  • ์œ„์˜ run ํ•จ์ˆ˜๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” main ํ•จ์ˆ˜

๐Ÿค” ์ฝ”๋“œ๋ฅผ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ํฌ๋ ˆ์ดํŠธ๋กœ ๋ถ„๋ฆฌํ•˜๊ธฐ

// src/lib.rs
use std::error::Error;

pub struct Config {
    pub query: String,
    pub filename: String,
}

impl Config {
    pub fn new(args: &[String]) -> Result<Config, &'static str> {
        if args.len() < 3 {
            return Err("ํ•„์š”ํ•œ ์ธ์ˆ˜๊ฐ€ ์ง€์ •๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.");
        }
        Ok(Config {
            query: args[1].clone(),
            filename: args[2].clone(),
        })
    }
}

pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
    let c = std::fs::read_to_string(config.filename)?;
    println!("ํŒŒ์ผ ๋‚ด์šฉ:\n{c}");
    Ok(())
}
// src/main.rs
use std::{env, process};
use minigrep::Config;

fn main() {
    let args = env::args().collect::<Vec<String>>();
    let config = Config::new(&args).unwrap_or_else(
        |err| {
            println!("์ธ์ˆ˜๋ฅผ ๊ตฌ๋ฌธ๋ถ„์„ํ•˜๋Š” ๋™์•ˆ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค: {}", err);
            process::exit(1);
        }
    );
    println!("๊ฒ€์ƒ‰์–ด: {}\nํŒŒ์ผ ์ด๋ฆ„: {}", config.query, config.filename);

    if let Err(e) = minigrep::run(config) {
        println!("์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์—๋Ÿฌ: {}", e);
        process::exit(1);
    }
}

4๏ธโƒฃ ํ…Œ์ŠคํŠธ ์ฃผ๋„ ๋ฐฉ๋ฒ•์œผ๋กœ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์˜ ๊ธฐ๋Šฅ ๊ฐœ๋ฐœํ•˜๊ธฐ

  • ํ…Œ์ŠคํŠธ ์ฃผ๋„ ๊ฐœ๋ฐœ(TDD; Test-Driven Development)
    • ์‹คํŒจํ•˜๋Š” ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ•œ ๋‹ค์Œ ์˜๋„ํ•œ ์ด์œ  ๋•Œ๋ฌธ์— ์‹คํŒจํ•˜๋Š”์ง€ ํ™•์ธ
    • ํ…Œ์ŠคํŠธ์— ์„ฑ๊ณตํ•˜๊ธฐ์— ์ถฉ๋ถ„ํ•œ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๊ฑฐ๋‚˜ ์ˆ˜์ •
    • ์ถ”๊ฐ€ํ–ˆ๊ฑฐ๋‚˜ ์ˆ˜์ •ํ•œ ์ฝ”๋“œ๋ฅผ ๋ฆฌํŒฉํ† ๋งํ•˜๋ฉด์„œ ํ…Œ์ŠคํŠธ๊ฐ€ ๊ณ„์† ์„ฑ๊ณตํ•˜๋Š”์ง€ ํ™•์ธ
    • ์•ž ๊ณผ์ •์„ ๊ณ„์† ๋ฐ˜๋ณต

๐Ÿค” ์‹คํŒจํ•˜๋Š” ํ…Œ์ŠคํŠธ ์ž‘์„ฑํ•˜๊ธฐ

// src/lib.rs
pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
    vec![]
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn one_result() {
        let query = "duct";
        let contents = "\
Rust:
safe, fast, productive.
Pick three.";

        assert_eq!(
            vec!["safe, fast, productive."],
            search(query, contents)
        );
    }
}
  • ์ธ์ˆ˜์˜ ์ˆ˜๋ช…์ด ๋ฆฌํ„ด๊ฐ’์˜ ์ˆ˜๋ช…๊ณผ ์—ฐ๊ฒฐ๋˜์—ˆ์Œ
    • ๋ฆฌํ„ด๋˜๋Š” ๋ฒกํ„ฐ๋Š” contents ์ธ์ˆ˜๋กœ ์ „๋‹ฌ๋œ ๋ฌธ์ž์—ด ์Šฌ๋ผ์ด์Šค๋ฅผ ์ฐธ์กฐํ•˜๋Š” ์Šฌ๋ผ์ด์Šค๋ฅผ ํฌํ•จํ•˜๊ณ  ์žˆ์–ด์•ผ ํ•จ์„ ์˜๋ฏธํ•จ
  • ๋Ÿฌ์ŠคํŠธ์—๊ฒŒ search ํ•จ์ˆ˜๊ฐ€ ๋ฆฌํ„ดํ•˜๋Š” ๋ฐ์ดํ„ฐ๋Š” search ํ•จ์ˆ˜์˜ contents ์ธ์ˆ˜๋กœ ์ „๋‹ฌ๋œ ๋ฐ์ดํ„ฐ์™€ ๊ฐ™์€ ์ˆ˜๋ช…์„ ๊ฐ€์ ธ์•ผ ํ•œ๋‹ค๋Š” ๊ฒƒ์„ ์•Œ๋ ค์คŒ
running 1 test
thread 'test::one_result' panicked at 'assertion failed: `(left == right)`
  left: `["safe, fast, productive."]`,
 right: `[]`', src/lib.rs:42:9

...

 test test::one_result ... FAILED

failures:

failures:
    test::one_result

test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s

error: test failed, to rerun pass '-p minigrep --lib'

๐Ÿค” ํ…Œ์ŠคํŠธ๊ฐ€ ์„ฑ๊ณตํ•˜๋„๋ก ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๊ธฐ

pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
    let mut v = vec![];
    for l in contents.lines() {
        if l.contains(query) {
            v.push(l);
        }
    }
    v
}
running 1 test
test test::one_result ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
// src/lib.rs
pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
    let mut f = std::fs::File::open(config.filename)?;
    let mut c = String::new();
    f.read_to_string(&mut c)?;

    for l in search(&config.query, &c) {
        println!("{l}");
    }

    Ok(())
}
  • search ํ•จ์ˆ˜๋ฅผ ํ™œ์šฉํ•˜๋Š” run ํ•จ์ˆ˜
Input:
    cargo run frog poem.txt
Output:
    ๊ฒ€์ƒ‰์–ด: frog
    ํŒŒ์ผ ์ด๋ฆ„: poem.txt
    How public, like a frog
Input:
    cargo run body poem.txt
Output:
    ๊ฒ€์ƒ‰์–ด: body
    ํŒŒ์ผ ์ด๋ฆ„: poem.txt
    I'm nobody! Who are you?
    Are you nobody, too?
    How dreary to be somebody!
Input: 
    cargo run hihi poem.txt
Ounput:
    ๊ฒ€์ƒ‰์–ด: hihi
    ํŒŒ์ผ ์ด๋ฆ„: poem.txt

5๏ธโƒฃ ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ๋‹ค๋ฃจ๊ธฐ

  • ์‚ฌ์šฉ์ž๊ฐ€ ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋ฅผ ์ด์šฉํ•ด ๋ฌธ์ž์—ด ๊ฒ€์ƒ‰์— ๋Œ€์†Œ๋ฌธ์ž๋ฅผ ๊ตฌ๋ถ„ํ•˜์ง€ ์•Š๋„๋ก ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋Š” ๊ธฐ๋Šฅ

๐Ÿค” ํ…Œ์ŠคํŠธ ์ž‘์„ฑํ•˜๊ธฐ

pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
    let mut v = vec![];
    for l in contents.lines() {
        if l.contains(query) {
            v.push(l);
        }
    }
    v
}

pub fn search_case_insensitive<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
    let query = query.to_lowercase();
    let mut v = vec![];
    for l in contents.lines() {
        if l.to_lowercase().contains(&query) {
            v.push(l);
        }
    }
    v
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn case_sensitive() {
        let q = "duct";
        let c = "\
Rust:
safe, fast, productive.
Pick three.
Duct tape.";

        assert_eq!(
            vec!["safe, fast, productive."],
            search(q, c)
        );
    }

    #[test]
    fn case_insensitive() {
        let q = "rUsT";
        let c = "\
Rust:
safe, fast, productive.
Pick three.
Trust me.";

        assert_eq!(
            vec!["Rust:", "Trust me."],
            search_case_insensitive(q, c)
        );
    }
}
running 2 tests
test test::case_insensitive ... ok
test test::case_sensitive ... ok

test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

๐Ÿค” ๋กœ์ง ์ˆ˜์ •ํ•˜๊ธฐ

// src/lib.rs
use std::error::Error;
use std::io::Read;
use std::env;

pub struct Config {
    pub query: String,
    pub filename: String,
    pub case_sensitive: bool,
}

impl Config {
    pub fn new(args: &[String]) -> Result<Config, &'static str> {
        if args.len() < 3 {
            return Err("ํ•„์š”ํ•œ ์ธ์ˆ˜๊ฐ€ ์ง€์ •๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.");
        }
        Ok(Config {
            query: args[1].clone(),
            filename: args[2].clone(),
            case_sensitive: env::var("CASE_INSENSITIVE").is_err()
        })
    }
}

pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
    let mut f = std::fs::File::open(config.filename)?;
    let mut c = String::new();
    f.read_to_string(&mut c)?;

    let r = if config.case_sensitive {
        search(&config.query, &c)
    } else {
        search_case_insensitive(&config.query, &c)
    };

    for l in r {
        println!("{l}");
    }

    Ok(())
}

pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
    let mut v = vec![];
    for l in contents.lines() {
        if l.contains(query) {
            v.push(l);
        }
    }
    v
}

pub fn search_case_insensitive<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
    let query = query.to_lowercase();
    let mut v = vec![];
    for l in contents.lines() {
        if l.to_lowercase().contains(&query) {
            v.push(l);
        }
    }
    v
}
  • std::env : ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋ฅผ ๋‹ค๋ฃจ๋Š” var ํ•จ์ˆ˜๋ฅผ ์ œ๊ณตํ•˜๋Š” ๋ชจ๋“ˆ
    • ํ™˜๊ฒฝ ๋ณ€์ˆ˜๊ฐ€ ์„ค์ •๋˜์–ด ์žˆ์œผ๋ฉด ์ฝ์–ด์˜จ ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ๊ฐ’์„ ์ €์žฅํ•œ Ok ๊ฐ’์„ ๋ฆฌํ„ดํ•˜๊ณ , ํ™˜๊ฒฝ ๋ณ€์ˆ˜๊ฐ€ ์„ค์ •๋˜์–ด ์žˆ์ง€ ์•Š์œผ๋ฉด Err ๊ฐ’์„ ๋ฆฌํ„ดํ•˜๋Š” Result ํƒ€์ž…
    • Result ๊ฐ’์ด ๋ฆฌํ„ด๋˜๋ฉด is_err ๋ฉ”์„œ๋“œ๋ฅผ ์ด์šฉํ•ด ๋ฆฌํ„ด๋œ ๊ฐ’์ด ์—๋Ÿฌ์ธ์ง€ ํ™•์ธ
Input:
    cargo run to poem.txt
Output:
    ๊ฒ€์ƒ‰์–ด: to
    ํŒŒ์ผ ์ด๋ฆ„: poem.txt
    Are you nobody, too?
    How dreary to be somebody!
Input:
    export CASE_INSENSITIVE=1
    cargo run to poem.txt
Output:
    ๊ฒ€์ƒ‰์–ด: to
    ํŒŒ์ผ ์ด๋ฆ„: poem.txt
    Are you nobody, too?
    How dreary to be somebody!
    To tell your name the livelong day
    To an admiring bog!

6๏ธโƒฃ stderr์„ ์ด์šฉํ•ด ์—๋Ÿฌ ๋ฉ”์‹œ์ง€ ์ถœ๋ ฅํ•˜๊ธฐ

  • println! ๋งคํฌ๋กœ๋Š” ํ‘œ์ค€ ์ถœ๋ ฅ์—๋งŒ ์ง€์ •๋œ ๋ฉ”์‹œ์ง€๋ฅผ ์ถœ๋ ฅํ•˜๋ฏ€๋กœ ํ‘œ์ค€ ์—๋Ÿฌ๋ฅผ ์ด์šฉํ•ด ๋ฉ”์‹œ์ง€๋ฅผ ์ถœ๋ ฅํ•˜๋ ค๋ฉด ๋‹ค๋ฅธ ๋ฐฉ๋ฒ•์„ ์‚ฌ์šฉํ•ด์•ผ ํ•จ

๐Ÿค” ์—๋Ÿฌ์˜ ๊ธฐ๋ก ์—ฌ๋ถ€ ํ™•์ธํ•˜๊ธฐ

  • ํ‘œ์ค€ ์—๋Ÿฌ ์ŠคํŠธ๋ฆผ์€ ํŒŒ์ผ๋กœ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธํ•˜์ง€ ์•Š์œผ๋ฏ€๋กœ ํ‘œ์ค€ ์—๋Ÿฌ์— ์ถœ๋ ฅ๋œ ๋ฉ”์‹œ์ง€๋Š” ํ™”๋ฉด์„ ํ†ตํ•ด ํ™•์ธํ•  ์ˆ˜ ์žˆ์Œ
  • ํ‘œ์ค€ ์ถœ๋ ฅ ์ŠคํŠธ๋ฆผ์„ ํŒŒ์ผ๋กœ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธํ•œ ์ƒํ™ฉ์—์„œ๋„ ์—๋Ÿฌ ๋ฉ”์‹œ์ง€๋Š” ํ‘œ์ค€ ์—๋Ÿฌ ์ŠคํŠธ๋ฆผ์— ์ถœ๋ ฅํ•ด์„œ ํ™”๋ฉด์œผ๋กœ ๋ฉ”์‹œ์ง€๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋„๋ก ๊ตฌํ˜„
cargo run > output.txt
// output.txt
์ธ์ˆ˜๋ฅผ ๊ตฌ๋ฌธ๋ถ„์„ํ•˜๋Š” ๋™์•ˆ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค: ํ•„์š”ํ•œ ์ธ์ˆ˜๊ฐ€ ์ง€์ •๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.

๐Ÿค” ์—๋Ÿฌ๋ฅผ stderr์— ์ถœ๋ ฅํ•˜๊ธฐ

use std::{env, process};
use minigrep::Config;

fn main() {
    let args = env::args().collect::<Vec<String>>();
    let config = Config::new(&args).unwrap_or_else(
        |err| {
            eprintln!("์ธ์ˆ˜๋ฅผ ๊ตฌ๋ฌธ๋ถ„์„ํ•˜๋Š” ๋™์•ˆ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค: {}", err);
            process::exit(1);
        }
    );
    println!("๊ฒ€์ƒ‰์–ด: {}\nํŒŒ์ผ ์ด๋ฆ„: {}", config.query, config.filename);

    if let Err(e) = minigrep::run(config) {
        eprintln!("์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์—๋Ÿฌ: {}", e);
        process::exit(1);
    }
}
  • ํ‘œ์ค€ ์—๋Ÿฌ ์ŠคํŠธ๋ฆผ์— ๋ฉ”์‹œ์ง€๋ฅผ ์ถœ๋ ฅํ•˜๋Š” eprintln! ๋งคํฌ๋กœ ์‚ฌ์šฉ
  • ํ”„๋กœ๊ทธ๋žจ์˜ ์‹คํ–‰ ์„ฑ๊ณต ๋ฉ”์‹œ์ง€๋Š” ํ‘œ์ค€ ์ถœ๋ ฅ์œผ๋กœ ์ถœ๋ ฅํ•˜๊ณ  ์—๋Ÿฌ ๋ฉ”์‹œ์ง€๋Š” ํ‘œ์ค€ ์—๋Ÿฌ์— ์ถœ๋ ฅ

Summary

  • ๋ช…๋ น์ค„ ์ธ์ˆ˜, ํŒŒ์ผ, ํ™˜๊ฒฝ ๋ณ€์ˆ˜, ๊ทธ๋ฆฌ๊ณ  ์—๋Ÿฌ์˜ ์ถœ๋ ฅ์„ ์œ„ํ•œ eprintln! ๋งคํฌ๋กœ ๋“ฑ์€ ๋ช…๋ น์ค„ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๊ฐœ๋ฐœํ•˜๊ธฐ ์œ„ํ•œ ๊ธฐ๋ณธ์ ์ธ ๋„๊ตฌ๋“ค
728x90
๋ฐ˜์‘ํ˜•
Comments