Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to call struct methods inside constructor?

Tags:

rust

I'm relatively new to Rust, and running into an idiosyncracy with how I'd write a constructor in another language. I have a struct, S3Logger, which creates a temporary on disk file, and when a certain amount of data is written to this file will upload to S3 and rotate to another file.

I would like my new function to use a method on the class, gen_new_file, which will open a new file with the right name in the write location. However, in order to make it a method, I must already have a S3Logger to pass in, which I don't have yet during the new function. In the example below, I show how I might do this in another language, to partially construct the object before using object methods to finish the construction; however this obviously doesn't work in Rust.

I could use an Option for the current_log_file, but this feels a little gross. I'd like to enforce the invariant that if I have an S3Logger, I know it has an open file.

What is the best practice in Rust for such problems?

pub struct S3Logger {
    local_folder: PathBuf,
    destination_path: String,
    max_log_size: usize,

    current_log_file: File,
    current_logged_data: usize
}

impl S3Logger {
    pub fn new<P: AsRef<Path>>(
        local_folder: P,
        destination_path: &str,
        max_log_size: usize
    ) -> Self {
        std::fs::create_dir_all(&local_folder).unwrap();

        let mut ret = Self {
            local_folder: local_folder.as_ref().to_path_buf(),
            destination_path: destination_path.to_string(),
            max_log_size: max_log_size,
            current_logged_data: 0
        }; 
        // fails ^^^^ missing `current_log_file

        ret.gen_new_file();
        return ret
    }

    fn gen_new_file(&mut self) -> () {
        let time = Utc::now().to_rfc3339();
        let file_name = format!("{}.log", time);
        let file_path = self.local_folder.join(file_name);
        self.current_log_file = File::create(&file_path).unwrap();
    }
}
like image 567
sprw121 Avatar asked Jun 22 '26 11:06

sprw121


2 Answers

The easiest way would be making gen_new_file take local_folder: P instead of &mut self, return the File from there and call that in the constructor:

use std::fs::File;
use std::path::{Path, PathBuf};
use chrono::Utc;

pub struct S3Logger {
    local_folder: PathBuf,
    destination_path: String,
    max_log_size: usize,

    current_log_file: File,
    current_logged_data: usize
}

impl S3Logger {
    pub fn new<P: AsRef<Path>>(
        local_folder: P,
        destination_path: &str,
        max_log_size: usize
    ) -> Self {
        std::fs::create_dir_all(&local_folder).unwrap();
        let current_log_file = Self::gen_new_file(&local_folder);
        Self {
            local_folder: local_folder.as_ref().to_path_buf(),
            destination_path: destination_path.to_string(),
            max_log_size: max_log_size,
            current_logged_data: 0,
            current_log_file,
        }
    }

    fn gen_new_file<P: AsRef<Path>>(local_folder: P) -> File {
        let time = Utc::now().to_rfc3339();
        let file_name = format!("{}.log", time);
        let file_path = local_folder.as_ref().join(file_name);
        File::create(&file_path).unwrap()
    }
}

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=ee447eabc799035e8db246d3bccb225b

If you need to replace current_log_file later, you can do it the same way:

fn replace_log_file<P: AsRef<Path>>(&mut self, new_path: P) {
    let new_file = Self::gen_new_file(&new_path);
    self.current_log_file = new_file;
}
like image 149
sebpuetz Avatar answered Jun 25 '26 04:06

sebpuetz


One way, as you noted would be indeed to make the field optional.

You can also divide your struct into 2 parts.

struct S3LoggerInternal {
    pub local_folder: PathBuf,
    pub destination_path: String,
    pub max_log_size: usize,
}
pub struct S3Logger {
    internal: S3LoggerInternal,
    current_log_file: File,
    current_logged_data: usize,
}

impl S3Logger {
    pub fn new(...) -> Result<Self, ...> {
        let internal = S3LoggerInternal {...};
        let file = internal.open_file()?;
        Self {internal, current_log_file: file, current_logged_data: 0}
    }
}

impl S3LoggerInternal {
    fn open_file(&self) -> Result<File, ...> {
        // ... The code in your gen_new_file function
    }
}
like image 42
Kushagra Gupta Avatar answered Jun 25 '26 04:06

Kushagra Gupta



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!