简体   繁体   中英

Why can I return a reference to an owned value of a function?

In chapter 19.2 of The Rust Programming Language , the following example compiles without any error. I found out from issue #1834 that there is a new lifetime elision rule that implicitly makes 's longer than 'c .

Although I couldn't find a detailed explanation of this new elision rule, I guess that it is not more than just an implicit version of the longer, more explicit constraint: <'c, 's: 'c> . I think however my confusion is probably not about this new elision rule but of course I could be wrong about this.

My understanding is, that parse_context takes ownership of context as it has not been borrowed but actually moved to the function. That alone implies to me that the lifetime of context should match the lifetime of the function it is owned by regardless of the lifetimes and constraint we defined in Context , and Parser .

Based on those definitions, the part where context outlives the temporary Parser makes perfect sense to me (after all, we defined a longer lifetime), but the part where the &str reference is not dropped when context goes out of scope at the end of parse_context and I can still safely return it -- makes me puzzled.

What have I missed? How can the compiler reason about the lifetime of the returned &str ?

UPDATED EXAMPLE

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)
    }
}

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

fn main()
{
    let mut s = String::new();
    s += "Avail";
    s += "able?";
    if let Err(text) = parse_context(Context(&s))
    {
        println!("{}", text);
    }
}

You are not returning a reference to the owned value of the function. You are returning a copy of the reference passed in.

Your function is an elaborate version of the identity function:

fn parse_context(s: &str) -> &str {
    s
}

In your real code, you take a reference to a struct containing a string slice, then another reference to the string slice, but all of those references are thrown away.

For example, there's an unneeded reference in parse :

fn parse(&self) -> Result<(), &'s str> {
    Err( self.context.0)
    //  ^ no & needed
}

Additionally, if you enable some more lints, you'll be forced to add more lifetimes to your function signature, which might make things more clear:

#![deny(rust_2018_idioms)]

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

See also:

Although I couldn't find a detailed explanation of this new elision rule,

T: 'a inference in structs in the edition guide.

struct Context<'s>(&'s str);

→ Values of type Context hold a string with some lifetime 's . This lifetime is implicitly at least as long as the lifetime of the context, but it may be longer.

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

→ Values of type Parser hold aa reference to context with some lifetime 'c . This context holds a string with some other lifetime 's .

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

→ Function parse returns a value with lifetime 's , ie. with the same lifetime as the string that is stored inside the context, which is not the same as the lifetime of the context itself.

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

→ I'm not sure exactly where this is specified, but obviously the compiler infers that the lifetime for the returned string is the same as the 's parameter used for the context. Note that even though the context itself is moved into parse_context , this only affects the context itself, not the string that it contains.

fn main()
{
    let mut s = String::new();

→ Create a new string valid until the end of main

    s += "Avail";
    s += "able?";
    if let Err(text) = parse_context(Context(&s))

→ Create a new context and move it into parse_context . It will automatically be dropped at the end of parse_context . It holds a reference to string s which is valid until the end of main and parse_context returns a string with the same lifetime as stext is valid until the end of main .

    {
        println!("{}", text);
    }
}

→ No problem: text is valid until the end of main .

Thank to the comments of Jmb and some bits of Shepmaster's answer it is indeed clear to me now that my confusion was about the RAII rules and the ownership.

That is, the string was created in the main scope, the anonymous Context instance is not taking ownership of the string it is only borrowing a reference, therefore even when the instance is dropped at the end of parse_context along with the borrowed reference, a reference copied to the Err object still exists and points to the existing string -- hence we are using the constrained lifetime variables and the compiler is able to reason about the lifetime of the internal string reference.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM