Unsure why `"\` fails in documentation test?

Surely missing something obvious here but couldn't figure it out.

When running cargo test for the following snippet:

/**
 * Returns all lines in the file matching the Query.
 * This function is case insensitive.
 *
 * # Example
    ```rust
        let query = "ducT";
        let contents = "\
Rust: safe, fast, prodUCtive.\
Pick three.";
        assert_eq!(
            vec!["safe, fast, prodUCtive."],
            minigrep::search_insensitive(query, contents)
        );
    ```
*/
pub fn search_insensitive<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
    let mut result: Vec<&str> = Vec::new();
    let query_lowercase: &str = &query.to_lowercase(); // coerce
    for line in contents.lines() {
        // to_lowercase() returns a new String
        if line.to_lowercase().contains(query_lowercase) {
            result.push(line); // line is the original
        }
    }
    result
}

fails with

running 1 test
test src/lib.rs - search_insensitive (line 67) ... FAILED

failures:

---- src/lib.rs - search_insensitive (line 67) stdout ----
error[E0765]: unterminated double quote string
 --> src/lib.rs:69:20
  |
2 |     let contents = "\
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0765`.
Couldn't compile the test.

failures:
    src/lib.rs - search_insensitive (line 67)

This doesn't fail to compile as actual test, nor makes any trouble in the playground, but for the playground this doesn't quite matter since it runs the code only.

Searched on GitHub but no issues from what I can tell.

Rustdoc is just confused by the formatting of your comment. It strips the leading * from lines that have it, then tries to parse the rest as markdown, and I bet this is where things go wrong. It probably sees ```rust indented 4 or more spaces, and treats that and the subsequent indented lines as an indented code block, stopping at the first non-indented line.

If you write it like this, it looks nicer and the doctest at least parses correctly:

/**
 * Returns all lines in the file matching the Query.
 * This function is case insensitive.
 *
 * # Example
 * ```rust
 * let query = "ducT";
 * let contents = "\
 * Rust: safe, fast, prodUCtive.\
 * Pick three.";
 * assert_eq!(
 *     vec!["safe, fast, prodUCtive."],
 *     minigrep::search_insensitive(query, contents)
 * );
 * ```
 */
pub fn search_insensitive<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
    let mut result: Vec<&str> = Vec::new();
    let query_lowercase: &str = &query.to_lowercase(); // coerce
    for line in contents.lines() {
        // to_lowercase() returns a new String
        if line.to_lowercase().contains(query_lowercase) {
            result.push(line); // line is the original
        }
    }
    result
}

More narrowly: A Markdown code block cannot contain lines that are less indented than the start and end of the code block.

You need to think of this as three nested syntaxes (Rust in Markdown in Rust doc comment), not as a single syntax where the multiline literal contains exactly the text between the quotes in the original source file.

3 Likes

As a side note: don't you want \n\ instead of a single backslash? Like this, it will merge your lines, whereas it looks like you want a string with several lines.

(The expected string should also contains "Rust: " at the beginning.)

Yes, I think it should've been:

        let query = "ducT";
        let contents = "\
Rust: safe, fast, prodUCtive.
Pick three.";

which also has the newline included.

That's interesting. I may take some time to figure out how to think of those levels at once.

I couldn't quite reproduce what I understand from your sentence. For example, this won't fail:

   let a = 5;
let b = 6;

Nor does it fail if the "```rust" is tabbed inwards with respect to the text.
It does fix the problem to indent properly in my case though, but seems internal to the string.

So these pass as well:

```rust
let query = "ducT";
    let contents = "\
Rust:
safe, fast, prodUCtive.
Pick three.";
assert_eq!(
    vec!["safe, fast, prodUCtive."],
    minigrep::search_insensitive(query, contents)
);
     ```rust
let query = "ducT";
    let contents = "\
Rust:
safe, fast, prodUCtive.
Pick three.";
assert_eq!(
    vec!["safe, fast, prodUCtive."],
    minigrep::search_insensitive(query, contents)
);

PS: I guess the markdown constrain only affects the "string stuff" in this case, and the let are understood better, removing spaces first. Not so easy to be sure for me.

Sorry, I think I was incorrect. I don't understand what is going on here as well as I thought.

1 Like

AFAIC rustdoc follows commonmark, so the relevant information is found here.

It sounds like (more easy to see actually by just scrolling down a bit for the examples) the rule is that the introducing ```’s indentation is what counts, and code lines with less indentation are allowed, but don't differ in output compared to lines with the full equivalent indentation of the leading ```rust. However:

If the end of the containing block (or document) is reached and no closing code fence has been found, the code block contains all of the lines after the opening code fence until the end of the containing block (or document).

So: some “block” structure takes precendence over code block fences! In particular the way that list items work is such that anything indented less that… wait why lists? Because you use * -leaders on lines, those are lists in markdown. Apparently rustdoc has some (severely underdocumented?) feature of making “C-style”

/**
 * Doc comments
 * over multiple lines
 * with asterisks repeated…
 */

magically work, even though the native Rust multiline-doc comment syntax would have this rather look like

/*
Doc comments
over multiple lines
with asterisks repeated…
*/

or

/**
    Doc comments
    over multiple lines
    with asterisks repeated…
*/

or so. The “magic” appears to be assuming though that every single line starts with that *, otherwise it is just included in the markdown interpretation of things, and thus it’s a markdown list where “*” symbolizes the bullet points.

So back to the topic, list items work such that anything indented less than the indentation of this bullet point (+1) will not be considered part of the item, and thus that ends the “block” that list item constitutes

In

/**
 * Returns all lines in the file matching the Query.
 * This function is case insensitive.
 *
 * # Example
    ```rust
        let query = "ducT";
        let contents = "\
Rust: safe, fast, prodUCtive.\
Pick three.";
        assert_eq!(
            vec!["safe, fast, prodUCtive."],
            minigrep::search_insensitive(query, contents)
        );
    ```
*/

the line Rust: safe, fast, prodUCtive.\ is indented less than the 3 spaces indentation level of the leading 2 * characters plus one space; and thus the code block ends right there. And thus the rust compiler, being passed the contents of this code block containing

    let query = "ducT";
    let contents = "\

complains about the unterminated string literal.

I don't get (but got it from your last line I think):

Examples I tried

Works (expected):

/**
 * # Example
    ```rust
    let user_query = "my_query";
    assert_eq!("hello", "hell");
    ```
*/

Doesn't (ends the codeblock):

/**
 * # Example
    ```rust
    let user_query = "my_query";
assert_eq!("hello", "hell");
    ```
*/

But this one does (does not end the codeblock):

/** // case 1, all level!
 * # Example
```rust
    let user_query = "my_query";
assert_eq!("hello", "hell");
    ```// indented in purpose for rendering.
*/

A few rules

Re-writing in my own words:

  1. Align everything within the ``` column.
  2. Some lines can be further indented, not outdented with respect to ```rust (as shown with examples).
  3. Lines may be shifted wrt each other, but must comply with 1.

In the case of the original text-string:

that's :bullseye:.

The text is ending the code-block. I think if I add 4 blanks, it passes the compiler, but fails the test (because I'm adding blanks!)

So the way to go is to unindent, or add *.

I think you were correct, but it was passing the tests simply because it was ending the code-block, like the accepted answer and my reply mentions.

It's still much easier to simply remember what you wrote there.

You might also want to actually look at the generated documentation :wink: [which could differ between ”really broken” or “good” depending on this choice, if it – as mentioned – switch over to treading the preceding *s as markers for itemization]. Unless this item isn’t publicly exported anywhere, in which case though you could try looking at it with the --document-private-items flag enabled.

1 Like

I built it with this style, lib.rs Is there anything wrong there in that style (for the code-blocks only) ?


Just side notes but

Found out as well that 4 blanks in here makes for a code-block. (Unless you add bullet to this paragraph, in which case it's another item)

just four spaces, no backticks here 
same

Is there anything wrong there in that style (for the code-blocks only) ?

If it works, it works, the Rust Style Guide recommends line comments over block comments and most crates seem to follow that suggestion in practice.

1 Like