## Testing the Library's Functionality

Writing tests for the core functionality of our code is now easier since we extracted the logic into src/lib.rs and left all the argument parsing and error handling in src/main.rs. We can now call our code directly with various arguments and check return values without having to call our binary from the command line.

We're going to write a function named `grep` that takes our search term and the text to search and produces a list of search results. Let's remove that `println!` from `run` (and from src/main.rs as well, as we don't really need those anymore either), and call the new `grep` function with the options we've collected. We'll add a placeholder implementation of the function for now, and a test that specifies the behavior we'd like the `grep` function to have. The test will fail with our placeholder implementation, of course, but we can make sure the code compiles and that we get the failure message we expect. Listing 12-14 shows these modifications:

Filename: src/lib.rs

    # #![allow(unused_variables)]
    #
    #fn main() {
    # use std::error::Error;
    # use std::fs::File;
    # use std::io::prelude::*;
    #
    # pub struct Config {
    #     pub search: String,
    #     pub filename: String,
    # }
    #
    // ...snip...

    fn grep<'a>(search: &str, contents: &'a str) -> Vec<&'a str> {
         vec![]
    }

    pub fn run(config: Config) -> Result<(), Box<Error>>{
        let mut f = File::open(config.filename)?;

        let mut contents = String::new();
        f.read_to_string(&mut contents)?;

        grep(&config.search, &contents);

        Ok(())
    }

    #[cfg(test)]
    mod test {
        use grep;

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

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

    #}

Listing 12-14: Creating a function where our logic will go and a failing test for that function

Notice that we need an explicit lifetime `'a` declared in the signature of `grep` and used with the `contents` parameter and the return value. Remember, lifetime parameters are used to specify which function parameters' lifetimes connect to the lifetime of the return value. In this case, we're indicating that the vector we're returning is going to contain string slices that reference slices of the parameter `contents`, as opposed to referencing slices of the parameter `search`. Another way to think about what we're telling Rust is that the data returned by the `grep` function will live as long as the data passed into this function in the `contents` parameter. This is important! Given that the data a slice references needs to be valid in order for the reference to be valid, if the compiler thought that we were making string slices of `search` rather than `contents`, it would do its safety checking incorrectly. If we tried to compile this function without lifetimes, we would get this error:

    error[E0106]: missing lifetime specifier
      --> src\lib.rs:37:46
       |
    37 | fn grep(search: &str, contents: &str) -> Vec<&str> {
       |                                              ^ expected lifetime parameter
       |
       = help: this function's return type contains a borrowed value, but the
           signature does not say whether it is borrowed from `search` or
               `contents`

Rust can't possibly know which of the two parameters we need, so it needs us to tell it. Because `contents` is the parameter that contains all of our text and we want to return the parts of that text that match, we know `contents` is the parameter that should be connected to the return value using the lifetime syntax.

Connecting parameters to return values in the signature is something that other programming languages don't make you do, so don't worry if this still feels strange! Knowing how to specify lifetimes gets easier over time, and practice makes perfect. You may want to re-read the above section or go back and compare this example with the Lifetime Syntax section in Chapter 10.

Now let's try running our test:

    $ cargo test
    ...warnings...
        Finished debug [unoptimized + debuginfo] target(s) in 0.43 secs
         Running target/debug/deps/greprs-abcabcabc

    running 1 test
    test test::one_result ... FAILED

    failures:

    ---- test::one_result stdout ----
        thread 'test::one_result' panicked at 'assertion failed: `(left == right)`
    (left: `["safe, fast, productive."]`, right: `[]`)', src/lib.rs:16
    note: Run with `RUST_BACKTRACE=1` for a backtrace.

    failures:
        test::one_result

    test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured

    error: test failed

Great, our test fails, exactly as we expected. Let's get the test to pass! It's failing because we always return an empty vector. Here's what we're going to do to implement `grep`:

1. Iterate through each line of the contents.
2. Check if the line contains our search string.
   - If it does, add it to the list of values we're returning.
   - If not, do nothing.
3. Return the list of results that match.

Let's take each step at a time, starting with iterating through lines. Strings have a helpful method to handle this, conveniently named `lines`:

Filename: src/lib.rs

    fn grep<'a>(search: &str, contents: &'a str) -> Vec<&'a str> {
        for line in contents.lines() {
            // do something with line
        }
    }

We're using a `for` loop along with the `lines` method to get each line in turn. Next, let's see if our line contains the search string. Luckily, strings have a helpful method named `contains` that does this for us! Using the `contains` method looks like this:

Filename: src/lib.rs

    fn grep<'a>(search: &str, contents: &'a str) -> Vec<&'a str> {
        for line in contents.lines() {
            if line.contains(search) {
                // do something with line
            }
        }
    }

Finally, we need a way to store the lines that contain our search string. For that, we can make a mutable vector before the `for` loop and call the `push` method to store a `line` in the vector. After the `for` loop, we return the vector. Listing 12-15 has the full implementation:

Filename: src/lib.rs

    # #![allow(unused_variables)]
    #
    #fn main() {
    fn grep<'a>(search: &str, contents: &'a str) -> Vec<&'a str> {
        let mut results = Vec::new();

        for line in contents.lines() {
            if line.contains(search) {
                results.push(line);
            }
        }

        results
    }

    #}

Listing 12-15: Fully functioning implementation of the `grep` function

Let's give it a try:

    $ cargo test
    running 1 test
    test test::one_result ... ok

    test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured

         Running target/debug/greprs-2f55ee8cd1721808

    running 0 tests

    test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured

       Doc-tests greprs

    running 0 tests

    test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured

Great! It works. Now that our test is passing, we could consider opportunities for refactoring the implementation of `grep` and be certain we maintain the same functionality while we do so. This code isn't bad, but it isn't taking advantage of some useful features of iterators. We'll be coming back to this example in Chapter 13 where we'll explore iterators in detail and see how to improve it.

Now that the `grep` function is working, we need to do one last thing inside of the `run` function: we never printed out the results! We'll do that by adding a `for` loop that prints each line returned from the `grep` function:

Filename: src/lib.rs

    pub fn run(config: Config) -> Result<(), Box<Error>> {
        let mut f = File::open(config.filename)?;

        let mut contents = String::new();
        f.read_to_string(&mut contents)?;

        for line in grep(&config.search, &contents) {
            println!("{}", line);
        }

        Ok(())
    }

Now our whole program should be working! Let's try it out:

    $ cargo run the poem.txt
       Compiling greprs v0.1.0 (file:///projects/greprs)
        Finished debug [unoptimized + debuginfo] target(s) in 0.38 secs
         Running `target\debug\greprs.exe the poem.txt`
    Then there's a pair of us - don't tell!
    To tell your name the livelong day

    $ cargo run a poem.txt
        Finished debug [unoptimized + debuginfo] target(s) in 0.0 secs
         Running `target\debug\greprs.exe a poem.txt`
    I'm nobody! Who are you?
    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!

Excellent! We've built our own version of a classic tool, and learned a lot about how to structure applications. We've also learned a bit about file input and output, lifetimes, testing, and command line parsing.