1

I am trying to create a struct to manipulate file storage, but after I change the value it can not be used. I'm sure it's about lifetimes, but I do not understand how I can fix this.

use std::error::Error;
use std::fs::{File, OpenOptions};
use std::io::{BufRead, BufReader};
use std::option::Option;
use std::path::Path;

pub struct Storage<'a> {
    path_str: &'a str,
    file: Option<File>,
}

const LOCKED_STORAGE: Storage<'static> = Storage {
    path_str: &"/tmp/bmoneytmp.bms",
    file: None,
};

pub fn get_instance() -> Storage<'static> {
    if LOCKED_STORAGE.file.is_none() {
        LOCKED_STORAGE.init();
    }

    LOCKED_STORAGE
}

impl Storage<'static> {
    // Create a file for store all data, if does not alred exists
    fn init(&mut self) {
        let path = Path::new(self.path_str);

        self.file = match OpenOptions::new()
            .read(true)
            .write(true)
            .create(true)
            .open(path)
        {
            Err(e) => panic!("Couldn't create the storage file at {}", e.description()),
            Ok(file) => Some(file),
        };

        if self.file.is_none() {
            panic!("Error on init??"); // This line is not called. Ok :)
        }
    }

    // Check if section exists
    pub fn check_section(&self, name: String) -> bool {
        if self.file.is_none() {
            panic!("Error on check??"); // This line is called. :(
        }
        true
    }
}

fn main() {
    let storage = get_instance();
    storage.check_section("accounts".to_string());
}

playground

This fails:

thread 'main' panicked at 'Error on check??', src/main.rs:48:13

I am trying to use a method to open a file and read this opened file, but in second method the instance of the file is not opened. Using Option<File>, I change the value with Same/None but the variable continues to be None.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Batels
  • 77
  • 1
  • 6

2 Answers2

3

When programming, it's a very important skill to learn how to create a Minimal, Complete, and Verifiable example. Here is one for your problem:

const EXAMPLE: Option<i32> = Some(42);

fn main() {
    EXAMPLE.take();
    println!("{:?}", EXAMPLE);
}

This prints Some(42) — the value of EXAMPLE is not changed.

A const variable has no guarantees about how many instances it will have. The compiler is allowed to have zero, one, or multiple instances of it. Every time you make use of a const, it's as if you created a brand new value right there, pasting in the definition of the constant:

fn main() {
    Some(42).take();
    println!("{:?}", Some(42));
}

Instead, you want to create a singleton.

See also:

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
-3

Thanks @shepmaster. I am learned much with your answer. But i changed my approach and fix my problem using Mutex global static and lazy_static.

Also i read the article https://bryce.fisher-fleig.org/blog/strategies-for-returning-references-in-rust/index.html and it helped me to understand my error.

My new code:

#[macro_use]
extern crate lazy_static;

use std::error::Error;
use std::fs::{File, OpenOptions};
use std::io::{BufRead, BufReader};
use std::path::Path;
use std::option::Option;
use std::sync::Mutex;

pub struct Storage {
    path_str: String,
    file: Option<File>
}

lazy_static! {
    pub static ref LOCKED_STORAGE: Mutex<Storage> = Mutex::new(start_storage());
}

fn start_storage() -> Storage {
    let mut st = Storage { path_str: "/tmp/bmoneytmp.bms".to_string(), file: None };
    st.init();
    st
}

impl Storage {

    // Create a file for store all data, if does not alred exists
    fn init(&mut self) {

        let path = Path::new(&self.path_str);

        self.file = match OpenOptions::new()
            .read(true)
            .write(true)
            .create(true)
            .open(path)
        {
            Err(e) => panic!("Couldn't create the storage file at {}", e.description()),
            Ok(file) => Some(file),
        };
    }

    // Check if section exists
    pub fn check_section(&self, name: String) -> bool {

        let file = match &self.file {
            Some(file) => file,
            None => panic!("File of storage not opened")
        };

        for line in BufReader::new(file).lines() {
            println!("{}{:?}", name, line); // Working!!
        }

        true
    }
}

fn main() {
    // run in your bash before: echo "Row 1" >> /tmp/bmoneytmp.bms
    LOCKED_STORAGE.lock().unwrap().check_section("accounts".to_string());
}

You can build this on playground: https://play.rust-lang.org/?gist=bbd47a13910e0f7cda908dc82ba290eb&version=beta&mode=debug&edition=2018

Full code of my project: https://github.com/fernandobatels/blitz-money

Full code of my fix: https://github.com/fernandobatels/blitz-money/blob/9dc04742a57e6cd99742f2400a6245f210521f5d/src/backend/storage.rs https://github.com/fernandobatels/blitz-money/blob/9dc04742a57e6cd99742f2400a6245f210521f5d/src/backend/accounts.rs

Batels
  • 77
  • 1
  • 6
  • You can't answer a *"why"* question with *"I simply avoided the problem"*. Shepmaster's answer should be the accepted one, it actually answers your *"why"* question. If it is not the question you wanted an answer to, then you should not have put it in the title. – Finomnis Jan 02 '23 at 08:44