Lazy initialization

I have a design question: I have a struct that has a heavy constructor (i.e. slow file read operations). However the files to read depends on the operations the client will do with the struct. Right now I read everything on the constructor, to be conservative:

struct Foo {
    a: i32,
    b: i32,
}

impl Foo {
    pub fn new() -> Self {
        // load everything here
        // and return Foo instance
    }
    pub fn get_a(&self) -> i32 {
        // simply return cached field 'a'
        self.a
    }
    pub fn get_b(&self) -> i32 {
        self.b
    }
}

What pattern should I use to make a light constructor and lazily initialize struct fields on demand? I should point out that getter methods should not take &mut self in this case. Are there problems with multi-threading in that case?

If construction requires mutation and you can't use &mut self as a receiver, then your only choice is interior mutability. There are three primary ways of doing interior mutability with std: std::cell::Cell, std::cell::RefCell and std::sync::Mutex. Cell only works for types that satisfy Copy (which would work with your example code, since both of Foo's fields are Copy). RefCell works for most any type, but it will prevent you (or anyone else using Foo) from using Arc<Foo> usefully across multiple threads. If you want to enable that use case, then you'll need to do synchronization. The simplest way to do that is to store your state in a Mutex, which provides the same kind of interior mutability that RefCell does, except it does it in a way that is thread safe. Given that you want to cache something and then probably read from it many times, you'll probably want to use std::sync::RwLock instead (which allows many readers simultaneously, but only one writer).

2 Likes

Wow, quite a lot of options, thanks! Any documentation pointers on that? Or example projects? I found the rust book to be somehow obscure on the topic.

Are you referring to this section? Mutability

If you find it confusing, please note down you confusion for @steveklabnik, who can then act on it. You would really help a lot. (Or even: once you understood the subject, rewrite parts)

Precisely that section. I think it is too small. @BurntSushi answer is already better than the whole section (for my specific problem at least). Maybe the section requires the same refactor has been done for error handling, with basics, std types involved and some case study. That's only my opinion, of course.

1 Like

@mbrt I'm not aware of anything specific targeting your use case. For now, I'd recommend trying to understand to interior mutability and Send/Sync, and then attacking your problem. I'm short on time, so the best I can do is fire some links at you:

  1. As @skade linked, the book is one way to start: Mutability and Send and Sync - The Rustonomicon
  2. Good stuff on Send/Sync from @huon: Some notes on Send and Sync | Huon on the internet (Note that this was published before Rust 1.0 was released, but it still looks OK to me.)
  3. @Manishearth's blog post on types you'll want to learn more about for your problem: Wrapper Types in Rust: Choosing Your Guarantees - In Pursuit of Laziness
  4. Another @Manishearth blog post that follows up the previous one with more goodies on Send/Sync: How Rust Achieves Thread Safety - In Pursuit of Laziness

I think you probably want to start with (1), then (3) then (2)/(4).

Thanks for sharing. I don't think my problem is very uncommon, as it is only an application of interior mutability. Don't you think it's better to explain better in the docs why someone would ever choose to use it and what are the interactions with Send/Sync and all the stuff you mentioned?

1 Like

Of course! I didn't mean to imply otherwise, I only meant to say, "this is the best material to read that I know of." :slight_smile:

Sure, that's very valuable. The whole docs part is a huge amount of work and we appreciate detailed feedback a lot. Thank you.

Perfect, thanks for helping out! I'll try with your pointers, and if I'll come up with something useful, I'll ping someone as suggested by @skade.

Manish's "choosing your guarnatees" is a book chapter now as well.

@steveklabnik Wow! fastest documentation fix ever :smile:

I am in this situation and want to cache a value, whilst still being thread-safe. I feel like a RwLock is overkill, because after the initial write, the data no longer needs interior mutability, and getting a read-lock has overhead. Does anyone know of any crates that do a single write then read-only?

Hi Derek

I think it is better to create a new issue with probably a code example included instead of waking up old threads.

Thanks

I've used LazyCell as it is designed for this purpose.

I found lazy-init. This provides LazyTransform.