6
votes

I'm new to the language and still fighting the borrow checker. I've seen that some libraries use new() functions a.k.a. constructors without parameters and it works. Basically this means, that the returned data is being created inside new's function scope, and it's not deleted at the end of new's scope.

When trying this myself the borrow checker won't let this code through. How does one make this work, other than passing the i32 mutable reference as a parameter to the constructor.

Am I missing something?

#[derive(Debug)]
struct B<'a> {
    b: &'a i32
}

#[derive(Debug)]
struct A<'a> {
    one: B<'a>
}

impl<'a> A<'a> {
    fn new() -> A<'a> {
        // let mut b = 10i32;
        A {
            one: B{b: &mut 10i32}
        }
    }
}

fn main() {
    let a = A::new();
    println!("A -> {:?}", a);
}

The compiler error.

main.rs:15:19: 15:24 error: borrowed value does not live long enough
main.rs:15          one: B{b: &mut 10i32}
                                   ^~~~~
main.rs:12:20: 17:3 note: reference must be valid for the lifetime 'a as defined on the block at 12:19...
main.rs:12  fn new() -> A<'a> {
main.rs:13      // let mut b = 10i32;
main.rs:14      A {
main.rs:15          one: B{b: &mut 10i32}
main.rs:16      }
main.rs:17  }
main.rs:12:20: 17:3 note: ...but borrowed value is only valid for the block at 12:19
main.rs:12  fn new() -> A<'a> {
main.rs:13      // let mut b = 10i32;
main.rs:14      A {
main.rs:15          one: B{b: &mut 10i32}
main.rs:16      }
main.rs:17  }
error: aborting due to previous error

As per request, here's the practical example i'm trying to work with. There's this GUI library(Conrod), and it has some steps to instantiate it. Like in the example below.

let assets = find_folder::Search::ParentsThenKids(3, 3)
    .for_folder("assets").unwrap();
let font_path = assets.join("fonts/NotoSans/NotoSans-Regular.ttf");
let theme = Theme::default();
let glyph_cache = GlyphCache::new(&font_path).unwrap();
let ui = &mut Ui::new(glyph_cache, theme);

My plan was to encapsulate the drawing of the app, into a struct. That would have a constructor and a few helper methods. For this to work i would have to have a field with a instance of the conrod::Ui<GlyphCache<'a>> type, which is the type for the ui variable above.

I think adding things to main(i mean having all the allocations done in main), might not be the best way to do things.

let mut app_ui = app::AppUi::new(); // This would encapsulate all of the above configuration lines.

// use the ui here
for e in evets {
    app_ui.handle_input();
    app_ui.render();
}

Implementation of AppUi. It's not complete, but should show the general idea. Just to make sure we're on the same page, the type conrod::Ui<GlyphCache<'a>> requires a life lifetime parameter. And i want to have the same lifetime as the struct. The only way i know how to do that is to make the struct get a lifetime parameter itself, and pass it down to the UI type.

pub struct AppUi<'a> {
  pub ui: conrod::Ui<GlyphCache<'a>>,
  pub count: u16
}

impl<'a> AppUi<'a> {
  pub fn new() -> AppUi<'a> {
    let assets = find_folder::Search::ParentsThenKids(3, 3)
    .for_folder("assets").unwrap();

    let font_path = assets.join("FiraSans-Regular.ttf");
    let theme = Theme::default();
    let glyph_cache = GlyphCache::new(&font_path).unwrap();

    AppUi {
      ui: conrod::Ui::new(glyph_cache, theme),
      count: 0
    }
  }
}

=======================

The solution i went with, and it worked in the end(at least it works for now). Was to create a helper function, that would return a glyph_cache and just used that. I'm not sure if it's idiomatic Rust, will just use it for now. Should probably get used to working with the borrow checker.

pub struct AppUi<'a> {
  pub ui: conrod::Ui<GlyphCache<'a>>,
  pub count: u16
}

impl<'a> AppUi<'a> {
  pub fn new() -> AppUi<'a> {

    AppUi {
      ui: conrod::Ui::new(GlyphCache::new(&get_default_font_path()).unwrap(), Theme::default()),
      count: 0
    }
  }
}

pub fn get_default_font_path() -> PathBuf {
  find_folder::Search::ParentsThenKids(3, 3)
    .for_folder("assets")
    .unwrap()
    .join("FiraSans-Regular.ttf")
}
2

2 Answers

6
votes

The key to understanding this is that an & reference represents a borrow, not an owned value. The lifetime annotations do not control how long value live; they only keep track to ensure that the referents of a borrowed reference outlive the borrowed reference itself, so that it will always be valid to dereference it.

A borrowed reference can refer to a value on the stack (statically allocated memory) or in the heap (dynamically allocated memory). Values on the stack have a lifetime that is fairly obvious; from when the variable is initialized, until the end of the block when those values are popped off the stack.

Values on the heap are owned by pointers that live on the stack; so their lifetime is determined by the pointer on the stack that owns to them. However, ownership can be moved between different variables, so you can actually have more flexible lifetimes of those values if you move the ownership of the pointer that refers to them from one stack variable to another.

If you write a function with a signature like the following:

fn new() -> A<'a> {}

what you are saying is that you will return an A in which the contained references have some lifetime 'a that is determined by the caller; but you can't do that, as you are not given any such references as input. You can't produce a value with an arbitrary input lifetime from within the new function.

From a new function, what you generally want to return are owned values; or possibly values with borrows that are based on some input parameters, but you need to provide those references as input parameters.

It might help if you describe a bit more about what you are trying to do, rather than just providing a toy example. There are several possible things that you could be trying to do here, but with just the toy example it's hard to determine which to describe. What is the purpose of returning an object that contains a reference? Is it so that the object could be heap-allocated, and thus only have to move around a single pointer when passing it around rather than copying the whole value? In that case, you probably want a Box or a Vec. Is it so that it can refer to some stack allocated variable? Then you need to allocate that in a containing stack frame, and pass a reference with that lifetime in to your function, so that there is a variable with an appropriate lifetime for it to refer to. If you are just trying to return an object that contains an integer, then you can do so by having the object contain the integer directly, rather than containing a reference to it.

5
votes

That's because &mut 10i32 does not live long enough in your program. You specified that one will have the same lifetime as a, but a lives longer than one as the i32 goes out of scope after new finishes. This code will work on the otherhand:

#[derive(Debug)]
struct B<'a> {
    b: &'a i32
}

#[derive(Debug)]
struct A<'a> {
    one: B<'a>
}

impl<'a> A<'a> {
    fn new(x: &'a mut i32) -> A<'a> {
        // let mut b = 10i32;
        A {
            one: B{b: x}
        }
    }
}

fn main() {
    let mut x = 10i32;
    let a = A::new(&mut x);
    println!("A -> {:?}", a);
}

Note that x now lives as long as a, so the your lifetimes are satisfied