Help with the Rust Book tutorial

Hello,

I'm currently working through the Rust Lang book tutorial and I got stuck.
In Chapter 12. Around Lsiting 12-19, where the Test should run well, I cannot get it to work.
I believve I have an exact copy of what I typed on the site but I get the error message that it Failed.

Apperantly;
let contents
in
pub fn run

is unused.
I re-read it a few times, And I can't seem to find what's wrong. Any suggestions>?
I actually copied the code from the page itself at that seemed to work fine, so I'm currently at a loss.

If you paste the exact code you're having problems with in https://play.rust-lang.org/ and post a permalink here I think it will be a lot easier for others to help you with this problem.

We have the complete code for each listing in the book's repo-- here's the directory for Listing 12-19, for example-- so that you can check all the files in the project if you need to.

1 Like

// So this is the complete code I wrote. I believed like the instruction showed, but obviously I am //missing something. Any help is usefull. Thanks in advance!
//=============================================================

use std::error::Error;
use std::fs;

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

impl Config{
    pub fn new(args: &[String]) -> Result<Config, &str> {
        if args.len() < 3 {return Err("not enough arguments");}
    let query = args[1].clone();
    let filename = args[2].clone();
    Ok(Config {query, filename})
}}

pub fn run(config: Config) -> Result<(), Box<dyn Error>>    {
let contents = fs::read_to_string(config.filename)?;

Ok(())
}

pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
    let mut results = Vec::new();
    for line in contents.lines() {
        if line.contains(query){
            results.push(line);
        }
    }
    results
}


#[cfg(test)]
mod tests {
    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));
    }
}

You have to surround your code with ```s to make it render correctly in the forum, i.e.

```
fn some_code() {}
```

becomes

fn some_code() {}

Possibly even more helpful would be if you posted to play.rust-lang.org just as @cfsamson suggested, since you can also double-check that the code as posted does produce the kinds of error message you claim it does.

The way you pasted it now as plain text it’s a bit useless since the markdown parser in this forum loves eating some of the <GenericArguments> in angled brackets.

Actually, it seems to only have eaten a single instance of <dyn Error>. So this is probably what your code looks like

use std::error::Error;
use std::fs;

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

impl Config {
    pub fn new(args: &[String]) -> Result<Config, &str> {
        if args.len() < 3 {
            return Err("not enough arguments");
        }
        let query = args[1].clone();
        let filename = args[2].clone();
        Ok(Config { query, filename })
    }
}

pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
    let contents = fs::read_to_string(config.filename)?;

    Ok(())
}

pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
    let mut results = Vec::new();
    for line in contents.lines() {
        if line.contains(query) {
            results.push(line);
        }
    }
    results
}

#[cfg(test)]
mod tests {
    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));
    }
}

(in the playground)

Any you’re getting both an (I guess expected) warning about contents being unused, and (unrelatedly) a test failure:

warning: unused variable: `contents`
  --> src/lib.rs:21:9
   |
21 |     let contents = fs::read_to_string(config.filename)?;
   |         ^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_contents`
   |
   = note: `#[warn(unused_variables)]` on by default
running 1 test
test tests::one_result ... FAILED

failures:

---- tests::one_result stdout ----
thread 'tests::one_result' panicked at 'assertion failed: `(left == right)`
  left: `["safe, fast. productive."]`,
 right: `["        safe, fast, productive."]`', src/lib.rs:48:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace


failures:
    tests::one_result

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

It gives you all the information needed. The difference is between

"safe, fast. productive."

and

"        safe, fast, productive."

That’s one period vs. comma and some spaces from indentation. Both is easily fixed of course (playground after the fix).

To clarify, the warning about contents being unused does also apply to the original source code. It is however only a warning, not an error. Also in general with Rust’s compiler it is almost always worth it to try and read the actual compiler output / error message in detail. If you use an IDE that doesn’t display the error messages in full (or you’re not sure how to make it show you the full error message) you can always still use the cargo command, e.g. in this case cargo test to get the output in your terminal if you’re otherwise unsure what’s going on.

2 Likes

@cfsamson Thanks for the tip about `` This indeed looks better!

About play.rust-lang.org;
Yes, that gives the same error message as 'cargo test' so not really helping.

And then; Yes! That's it. I used a . instead of a ,
That is easily fixed indeed! Thanks!

The helpful bit about providing a playground link is that the person you’re asking your question to gets to see that full error message as well. And has the ability to play around with the code, which can be immensely helpful, too. Changing the code and looking at the error message is like asking the compiler questions and getting answers from it. And it lets the person answering your question can confirm that a solution to a problem that they would like to suggest actually solves the problem. So you’re letting the compiler help the person helping you who in turn can help you better.

3 Likes

I see, thanks. As you can guess, i'm still rather new here. Once again thanks for the help.

And while we're on it. I ran into another error i really can't fix. Once again, i believe i have a good copy of the Rust Book example...

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=3e664419b05e1e45719f2de122c54d92

Also, can someone then explain why it is a problem in the search_case_insensitive function and not for the search_case_sensitive function

So the compile error is due to the fact that you named the test case search_case_insensitive instead of case_insensitive. The error complains about the wrong number of arguments being provided because in the

assert_eq!(vec!["Rust:", "Trust me."], search_case_insensitive(query, contents) );

line the test function name search_case_insensitive refers to the function being defined (i.e. a recursive call, and that test-case function indeed takes no arguments), not the other search_case_insensitive function whose functionality you’re trying to test. The reason why you’re not getting any error about some kind of name collision or something is that when imports with *, such as use super::*, are clashing with locally defined (or explicitly imported) items (e.g. use super::search_case_insensitive), the locally defined (or explicitly imported) one takes precedent.

After you rename the

    #[test]
    fn case_insensitive() {

into

    #[test]
    fn search_case_insensitive() {

the test will still fail, because the contents string is different from the one in the book. Fixing that makes everything pass.

Okay, I made it work again.
I learned a few things again. With hindsight I see it was an obvious error to double name the functions. I somehow thought the test functions and the ones they tested were the same.
Glad that's figured out.
Also,
I think I understand how putting a part of the string on a new line is indeed a different line... lol. So that messes up the parameters the assertion uses.

Also,
Do I understand it correctly that the postion of the contents string, like at character 0 or with a few tabs before hand, also makes a difference in how the logic interprets it?

Yes. The tabs/spaces that are the indentation will become part of the string. There’s only one case where they don’t: If the line ends with \ then the line break and all the following whitespace are ignored. E.g.

        // some indented code
        let contents = "\
Rust:
safe, fast, productive.
Pick three.
Duct tape.";
        // code
        // continues
        // here

can be written

        // some indented code
        let contents = "\
        Rust:\n\
        safe, fast, productive.\n\
        Pick three.\n\
        Duct tape.";
        // code
        // continues
        // here

or

        // some indented code
        let contents = "\
            Rust:\n\
            safe, fast, productive.\n\
            Pick three.\n\
            Duct tape.";
        // code
        // continues
        // here

and all mean/do exactly the same.

Note the need for explicit \n which is a bit ugly (and easy to forget for some line), so in this case I personally would actually prefer the other style, even if it kills the indentation a bit. Long string literals could also be defined outside of the function as a constant, e.g.

const CONTENTS: &str = "\
Rust:
safe, fast, productive.
Pick three.
Duct tape.";

#[test]
fn search_case_insensitive() {
    let query = "rUsT";

    assert_eq!(vec!["Rust:", "Trust me."], search_case_insensitive(query, CONTENTS));

}

this way you can avoid the need for indentation in the first place, and it also makes the function body more readable (especially if the string got even longer, like 10s or 100s of lines). For even longer literals, they could even be placed in a separate file and included with the include_str macro.

By the way, you can of course also work without the \ at the beginning, like this:

const CONTENTS: &str = "Rust:
safe, fast, productive.
Pick three.
Duct tape.";

but then again, that looks a bit worse IMO…

i see, i see. I like the constant option, it does make the code more read-able.
I'm going to dive in that include_str macro, should be usefull for a project I have in mind.

Is there a speed difference in using a constant for strings or the include_str macro?

It won’t make any difference at runtime. At compile time, maybe there’s a small but no significant difference (I wouldn’t know which one’s faster though). I’m actually not sure about the overhead of using include_str of the same file in multiple places in the source code, but either way (whether it does have overhead or not), it’s probably nicer anyways to do define some constant

const SOME_CONSTANT: &str = include_str!("path…");

in the case that you’re going to use the same constant SOME_CONSTANT in more than one place in your code.

Yea that probably does look the tidiest.
But then I'm thinking if calling a CONST that's calling a Macro, is the most efficient.
That's 2 function calls.
But then I guess everything besides a direct string notation is more function calls anyway.
So we're full circle. Which is always neat.

It's only about micro seconds but still. With thousands of lines of code that's still a significant difference.

Do you know of any runtime measuring programs?

Macros are expanded at compile time. They don't introduce any function-call overhead at run time.

Similarly, const items are substituted at compile time and don't introduce any function calls at run time.

Riiight. Thanks! I remember reading now, reading that somewhere.
Awesome then, there's no trading in efficiency for tidiness.
Another great thing about Rust