Code from "Advanced Lifetimes" in Rust book giving unexpected errors

So I've been working through the Rust book, and have been doing fine, until I got to the "Advanced Features" chapter. Is the book wrong, or have I missed something subtle? Example code below is taken from the "Advanced Lifetimes" section of chapter 19 of the Rust book.

Here's the code from an early example, using a single lifetime for Parser.
This does fail to compile as predicted, but the error I get is different from the book's.

/*
  Expected: error[E0491]: in type `&'c Context<'s>`, reference has a longer 
  lifetime than the data it references
     --> src/lib.rs:4:5
*/
/*
  I get:
  error[E0515]: cannot return value referencing function parameter `context`
   --> src/main.rs:37:9
     |
37 |         Parser { context: &context }.parse()
     |         ^^^^^^^^^^^^^^^^^^--------^^^^^^^^^^
     |         |                 |
     |         |                 `context` is borrowed here
     |         returns a value referencing data owned by the current function
 */
fn main3() {
    struct Context<'a>(&'a str);

    struct Parser<'a> {
        context: &'a Context<'a>,
    }

    impl<'a> Parser<'a> {
        fn parse(&self) -> Result<(), &'a str> {
            Err(&self.context.0[1..])
        }
    }

    fn parse_context(context: Context) -> Result<(), &str> {
        Parser { context: &context }.parse()
    }
}

This is code from the example with two lifetimes, but no subtyping. This should create a compilation error according to the book, but in fact does not.

fn main2() {
    struct Context<'s>(&'s str);

    struct Parser<'c, 's> {
        context: &'c Context<'s>,
    }

    impl<'c, 's> Parser<'c, 's> {
        fn parse(&self) -> Result<(), &'s str> {
            Err(&self.context.0[1..])
        }
    }

    fn parse_context(context: Context) -> Result<(), &str> {
        Parser { context: &context }.parse()
    }
}

This is the final code example, with lifetime subtyping. It compiles fine, as expected.

#![allow(unused_variables, dead_code)]
fn main() {
    struct Context<'s>(&'s str);

    struct Parser<'c, 's: 'c> {
        context: &'c Context<'s>,
    }

    impl<'c, 's> Parser<'c, 's> {
        fn parse(&self) -> Result<(), &'s str> {
            Err(&self.context.0[1..])
        }
    }

    fn parse_context(context: Context) -> Result<(), &str> {
        Parser { context: &context }.parse()
    }
}

This is likely because of the following change in Rust 2018 (1.31): https://rust-lang-nursery.github.io/edition-guide/rust-2018/ownership-and-lifetimes/inference-in-structs.html

Try compiling that code with an older Rust version (<= 1.30), and you should see the error.

2 Likes

Thanks! Looks like the book needs some serious updating in that section.

Or just setting edition = "2015" in Cargo.toml would work too rather than having to install an older toolchain?

In fact, I think that because edition = "2018" has to be explicitly specified in order to make it rust > 1.30, just omitting the edition part that's automatically added by Cargo upon project creation works as well.

It doesn't look like the edition matters with 1.31 - the code compiles regardless of it. You can see this either on the playground, or in this godbolt setup (aside: godbolt is a great way to easily test different rustc versions, provided the sample code is simple/standalone enough).

I'm not sure if this is a bug or not, but probably nobody will really complain short of outdated docs/examples, such as this thread :slight_smile:.

cc @steveklabnik in case he's not aware of this issue in the book

1 Like

I am, thanks! And yes, that feature is not edition specific.

2 Likes