Refernce lifetime


#1

Hello,

I’ve stumbled upon a lifetime problem and as a beginer Rust programmer I have no clue how to do it right.

Consider the following code:

fn normalized_url(url: &String, base_url: Option<Url>) -> Option<Url> {
    let mut parser = UrlParser::new();
    match base_url {
        Some(base) => {
            parser.base_url(&base);
        },
        None => {}
    }
    match parser.parse(url) {
        Ok(url) => Some(url),
        Err(_) => None
    }
}

UrlParser comes from the url crate.

It has this weird API that accepts an URL in base_url. And it wants a reference. Also the method explicitly wants a &Url even though the field is Option<&Url>.

The problem is that this code upsets the compiler:

src/main.rs:32:30: 32:34 error: `base` does not live long enough
src/main.rs:32             parser.base_url(&base);
                                            ^~~~
src/main.rs:29:39: 40:2 note: reference must be valid for the block suffix following statement 0 at 29:38...
src/main.rs:29     let mut parser = UrlParser::new();
src/main.rs:30     match base_url {
src/main.rs:31         Some(base) => {
src/main.rs:32             parser.base_url(&base);
src/main.rs:33         },
src/main.rs:34         None => {}
               ...
src/main.rs:30:5: 35:6 note: ...but borrowed value is only valid for the match at 30:4
src/main.rs:30     match base_url {
src/main.rs:31         Some(base) => {
src/main.rs:32             parser.base_url(&base);
src/main.rs:33         },
src/main.rs:34         None => {}
src/main.rs:35     }

Is there a way to call base_url only when base_url is Some()? Or rather what is the Rust way of using this kind of API?


#2

Did you try:

match base_url {
    Some(ref base) => parser.base_url(base),
    None => {},
}

ref base allows the lifetime to last longer than with &base. It’s discussed a little here. I couldn’t find where it was discussed in the book. Here is an example in the playpen which works with ref but not with &. I didn’t try out the specific library you are using.


#3

You can also use if let, which is a bit more concise::

 if let Some(ref base) = base_url {
      parser.base_url(base);
 } 

If I understand correctly, if you don’t use ref, base_url is moved and thus destroyed at the end of the match block.


#4

Thank you. This works as I wanted.

There seem to be not much information on this ref. Here’s what I’ve found in the Reference:

7.2.22 Match expressions:

Patterns that bind variables default to binding to a copy or move of the
matched value (depending on the matched value’s type). This can be changed to
bind to a reference by using the ref keyword, or to a mutable reference using
ref mut.

I’m not sure what is the purpose of extra syntax as it looks like the following two lines are roughly equivalent:

let x = &y;
let ref x = y;

Both will result in x being &y.


#5

It’s basically the reverse of let &x = ref_to_something and lets you reference things inside other things. You can’t write the pattern Some(&thing) to get a reference to thing, because it would instead dereference thing, so you need some extra syntax to do the opposite. Patterns should mirror the type you give it, but they do things backwards, so if you say let &x = y it will dereference y before assigning to x and if you say let MyStruct { x, y } = z it will extract x and y from inside the MyStruct instance z.


#6

Or put another way:

let y = &3;

// These are equivalent
let x = *y; // Dereference
let &x = y; // Pattern match

let z = 4;

// Equivalent
let k = &z;
let ref k = z;

let s = Some(5);

// Not the same
let Some(&m) = s; // Failed pattern match
let Some(ref m) = s; // create variable `m` with a reference

let r = Some(6);

// So in a match
match r {
// create variable `n` and then create a temporary borrow with `&n`
// inside a new scope but don't assign a name to it. Then, as soon
// as the scope ends the borrow will be erased. If `foo()` tries to return
// the original reference, it will fail because that reference is now invalid
// (`n` still exists but the borrow from `n` doesn't exist).
    Some(n) => foo(&n),
// create variable `n` which is a reference. Moving that reference into the
// new scope will allow the reference to be returned later when `foo()` exits.
    Some(ref n) => foo(n),
}

#7
let r = Some(6);

match r {
    Some(a) => foo(&a),
    Some(ref b) => foo(b),
}

What is the scope of a and b? Doesn’t b goes out of scope at the end of match {}?


#8

b goes out of scope (at the comma, actually, I think; it would be out of scope in a third branch), but the location it points to is r, which stays longer.


#9

Makes sense. Thank you.