Lifetime across function boundary

If you uncomment [A] and comment [B] then the code works. The only difference being that the same code is behind a function.

Is there a way to make the get function work?

struct Inner {}

impl Inner {
    fn mutable(&mut self, _s: &str) {}
}

struct Outer {
    inner: Inner,
    values: Vec<String>,
}

impl Outer {
    #[allow(dead_code)]
    fn get(&self, i: usize) -> &str {
        &self.values[i]
    }
}

fn main() {
    let mut outer = Outer {
        inner: Inner {},
        values: vec!["1".to_string(), "2".to_string(), "3".to_string()],
    };

    let n = outer.values.len();
    for i in 0..n {
        // let s = &outer.values[i]; // [A]
        let s = outer.get(i);        // [B]
        
        outer.inner.mutable(s);
    }
}

playground

Unfortunately, no. Within a single function, the Rust compiler can treat each structure field as a variable that can be borrowed independently of the others. Outer::get() borrows the entire structure, and holds that borrow until its return value is destroyed— There’s no way to give that return value to a function that needs to mutably reference the structure.

If you want to add methods to individual fields of Outer, your can wrap them in a newtype. Having the get method on a field allows the caller to only borrow part of the Outer struct. E.g.:

use std::ops::Deref;
struct Inner {}

impl Inner {
    fn mutable(&mut self, _s: &str) {}
}

struct Values(Vec<String>);
impl From<Vec<String>> for Values {
    fn from(x: Vec<String>) -> Self {
        Values(x)
    }
}
impl Deref for Values {
    type Target = Vec<String>;
    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

impl Values {
    #[allow(dead_code)]
    fn get(&self, i: usize) -> &str {
        &self[i]
    }
}
struct Outer {
    inner: Inner,
    values: Values,
}


fn main() {
    let mut outer = Outer {
        inner: Inner {},
        values: vec!["1".to_string(), "2".to_string(), "3".to_string()].into(),
    };

    let n = outer.values.len();
    for i in 0..n {
        // let s = outer.get(i);     // [B]
        let s = outer.values.get(i);
        
        outer.inner.mutable(s);
    }
}

An alternative would be to use a non-method function taking Vec<String>:

struct Inner {}

impl Inner {
    fn mutable(&mut self, _s: &str) {}
}

struct Outer {
    inner: Inner,
    values: Vec<String>,
}

impl Outer {
    #[allow(dead_code)]
    fn get(v: &Vec<String>, i: usize) -> &str {
        &v[i]
    }
}

fn main() {
    let mut outer = Outer {
        inner: Inner {},
        values: vec!["1".to_string(), "2".to_string(), "3".to_string()],
    };

    let n = outer.values.len();
    for i in 0..n {
        let s = Outer::get(&outer.values, i);
        
        outer.inner.mutable(s);
    }
}

There’s also crates like derive_more that simplify these Deref and From instances.

2 Likes

Thank you both for taking the time to look. I guess I was hoping for some application of explicit lifetimes that I'd overlooked.

In an ideal world I'd like to keep the external api simple without exposing the fields. That being said, the idea of wrapping the internal (values field) type and exposing that could be the next best thing. Thanks steffahn.

I just tried playing with Rc and RefCell which makes the example work, but is this typically done in this situation, or is the performance hit frowned upon?

use std::cell::RefCell;
use std::rc::Rc;

struct Inner {}

impl Inner {
    fn mutable(&mut self, _s: &str) {}
}

struct Outer {
    inner: Rc<RefCell<Inner>>,
    values: Vec<String>,
}

impl Outer {
    #[allow(dead_code)]
    fn get(&self, i: usize) -> &str {
        &self.values[i]
    }
}

fn main() {
    let outer = Outer {
        inner: Rc::new(RefCell::new(Inner {})),
        values: vec!["1".to_string(), "2".to_string(), "3".to_string()],
    };

    let n = outer.values.len();
    for i in 0..n {
        // let s = &outer.values[i]; // [A]
        let s = outer.get(i);        // [B]
        
        outer.inner.borrow_mut().mutable(s);
    }
}

Sure, that is a valid goal. You’re not doing a great job in demonstrating what the external API is supposed to look like in your example, so it’s hard to tell what the best approach will be. I think what the best approach here is really depends on what the specific API looks like.

Note that the RefCell workaround does not need any Rc. The main disadvantage IMO is not the performance overhead but instead the fact that compile-time errors get turned into runtime-errors, so that—depending on the API you provide in the end—it could be possible to run into panics from borrow_mut by using that external API incorrectly.

1 Like

I must have had an error at some point in the past that was resolved by wrapping a RefCell in an Rc. Habitually did it here too. :smile:

Just tried the first approach you mentioned and It's the definitely best compromise without using RefCell.

Thanks again.

Note that the Deref and From implementations will expose the Vec<String> field, it the Values type is public. You can work without those impls though.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.