Why is it so difficult to create "classes" with Rust?

After studying the language I decided to create something with it, and I already realized that creating classes is a lot of work, I was racking my brain to be able to define a std :: fs :: File in my class to be started after the constructor and I was unsuccessful. I feel that Rust forces you to start everything as soon as possible, even when that is not desirable or possible at the moment.
Sorry for the bad English and in case my doubt doesn't make much sense.

If you want to create a File later, you can use an Option<File>.

4 Likes

Rust forces to you acknowledge whenever something could possibly not be there by making it an Option. In this way we avoid the null value throughout the entire language and you never have to do checks like if something != null all over the place because you never know when something might possibly be null.

By making something an Option you tell Rust that this could potentially be a None variant and not actually Some(File) yet. This means that you will have to .unwrap() that option or use an if let statement to get to that file later, though, because Rust also forces you to acknowledge that the file might not be there yet.

For instance:

struct MyClass {
    file: Option<std::fsFile>
}

impl MyClass {
    fn new(path: String) -> Self {
        MyClass {
            path,
            // File starts off None
            file: None,
        }
    }

    fn load_file(&mut self) {
        // We later set the file to Some(File)
        self.file = Some(std::fs::OpenOptions::new().read(true).path(self.path).open().unwrap());
    }
    
    fn read_data() -> String {
        // We have to check whether or not the file is Some(file) first
        // before we can get to the file. This is similar to checking
        // `if file != null` in other languages, but in Rust we can't
        // forget to do it.
        if let Some(file) = self.file {
            let data = String::new();
            file.read_to_string(&mut data).unwrap();

            // return the data
            data
        
        // We have to figure out what to do in case the file is not
        // there yet.
        } else {
            // We could panic...
            panic!("You can't read the file data before you've loaded it!");

            // or maybe we could just return an empty string
            String::new()

            // The best thing to do would be to return a `Result` which
            // would clearly indicate a potential failure, but that is
            // another lesson ;)
        }
    }
}
3 Likes

Using Option with Box solved my lazy init problem well. I forgot to describe the issue of files better. Initially I wanted to use File but then I wanted to use the "everything is a file" approach. Thanks for the tip and the explanation.

use std::io::{BufReader, Read};
use std::fs::File;

struct SourceTranslator {
    reader_impl: Option<Box<dyn Read>>
}

impl SourceTranslator {
    pub fn new() -> Self {
        Self { reader_impl: None }
    }

    pub fn reader(&mut self, reader_impl: Box<dyn Read>) {
        self.reader_impl = Some(reader_impl);
    }

    pub fn translate(&mut self) {
        if let Some(r) = self.reader_impl.as_mut(){
            let mut buf:[u8;128] = [0; 128];
            r.read(&mut buf);

            println!("{}", String::from_utf8_lossy(&buf));
        }
    }
}

#[test]
fn rust_file() {
    let f = File::open("hello.msk").unwrap();
    let mut t = SourceTranslator::new();

    t.reader(Box::new(BufReader::new(f)));
    t.translate();
}
1 Like