Newbie question on OOP

Hello! I'm newbie in rust so I meet some issues due to try to implement application that would parse some files. I have Format trait that describe parsed format somehow and create Reader object that is responsible for provide some data from file.
Please help me compile the code and understand how to correctly write such kind of application.

Here is my code. (also on playground Rust Playground)

Thanks a lot for any suggesions!

use std::io;
use std::io::BufRead;
use std::fs;

trait Reader
{
    fn read(&mut self) -> u32;
}

trait Format
{
    fn reader(&self) -> Box<dyn Reader>;
}

struct CsvReaderImpl<R : io::Read>
{
    reader : io::BufReader<R>,
}

impl<R: io::Read> CsvReaderImpl<R>
{
    fn new(reader : R) -> Self{
        Self{
            reader : io::BufReader::new(reader),
        }
    }
}

impl<R: io::Read> Reader for CsvReaderImpl<R>
{
    fn read(&mut self) -> u32
    {
        let mut line : String = Default::default();
        self.reader.read_line(&mut line);
        123
    }
}

struct Csv<R: io::Read>
{
    reader : CsvReaderImpl<R>,
}

impl<R: io::Read> Csv<R>{
    fn new(reader : R) -> Self{
        Csv{reader : CsvReaderImpl::new(reader)}
    }
}

impl<R: io::Read> Format for Csv<R>
{
    fn reader(&self) -> Box<dyn Reader>
    {
        Box::new(self.reader)
    }
}


#[derive(PartialEq)]
enum FormatType{Csv,Xml}

struct Detector;

impl Detector
{
    pub fn new() -> Self{
        Self{}
    }
    
    pub fn detect<'d, R : io::Read>(&'d self, reader: R, t: FormatType) -> Option<Box<dyn Format>>{
        if t == FormatType::Csv{
           return  Some(Box::new(Csv::new(reader)));
          }
          return None;
    }
}
 
fn main() -> io::Result<()>
{
    let file = fs::File::create("temp.txt")?;
    let detector = Detector::new();
    if let Some(format) = detector.detect(file, FormatType::Csv){
       let reader = format.reader();
       let v = reader.read();
       println!("{}", v);
    }
    Ok(())
}

Can you please put the code in triple backticks? Like

```
code block
```

@ zeroexcuses Thanks for advise

So the parameted R does not live long enough errors can be fixed by changing R: blah to R: blah + 'static

However, this error here:

impl<R: io::Read + 'static> Format for Csv<R>
{
    fn reader(&self) -> Box<dyn Reader>
    {
        Box::new(self.reader)
    }
}

I'm not sure what you intention is for fixing this. The problem is Box::new(...) wants to own the argument passed to it, but we only have a ref to self, so we only have a ref to self.reader.

What is best way to implement such hierarchy of objects. I just need detect format, return instance of Format and then get Reader from it. Later I would like to read records using the reader. Perhaps it should be Iterator implementation

  1. I do not understand what the intention of the code is, so I might have gotten the following wrong.

  2. I blindly fixed the compile errors and got:

use std::io;
use std::io::BufRead;
use std::fs;
//use std::marker;

trait Reader
{
    fn read(&mut self) -> u32;
}

trait Format
{
    fn reader(self: Box<Self>) -> Box<dyn Reader>;
}

struct CsvReaderImpl<R : io::Read>
{
    reader : io::BufReader<R>,
}

impl<R: io::Read> CsvReaderImpl<R>
{
    fn new(reader : R) -> Self{
        Self{
            reader : io::BufReader::new(reader),
        }
    }
}

impl<R: io::Read> Reader for CsvReaderImpl<R>
{
    fn read(&mut self) -> u32
    {
        let mut line : String = Default::default();
        self.reader.read_line(&mut line);
        123
    }
}

struct Csv<R: io::Read>
{
    reader : CsvReaderImpl<R>,
}

impl<R: io::Read> Csv<R>{
    fn new(reader : R) -> Self{
        Csv{reader : CsvReaderImpl::new(reader)}
    }
}

impl<R: io::Read + 'static> Format for Csv<R>
{
    fn reader(self: Box<Self>) -> Box<dyn Reader>
    {
        Box::new(self.reader)
    }
}


#[derive(PartialEq)]
enum FormatType{Csv,Xml}

struct Detector;

impl Detector
{
    pub fn new() -> Self{
        Self{}
    }
    
    pub fn detect<'d, R : io::Read + 'static>(&'d self, reader: R, t: FormatType) -> Option<Box<dyn Format>>{
        if t == FormatType::Csv{
           return  Some(Box::new(Csv::new(reader)));
          }
          return None;
    }
}
 
fn main() -> io::Result<()>
{
    let file = fs::File::create("temp.txt")?;
    let detector = Detector::new();
    if let Some(format) = detector.detect(file, FormatType::Csv){
       let mut reader = format.reader();
       let v = reader.read();
       println!("{}", v);
    }
    Ok(())
}

Let me know whether this solves it or if it breaks the intention of the code.

(needless to say, this is not idiomatic / suggested Rust style; just the result of manually fixing compile errors w/ the first idea that comes to mind)

The task is following. I have several trace files with different formats (all of them are CSV-like but with
different columns). I need to write a command line tool that gets one file, detect its format (which columns are there), read all record one by one and send each record to some destination (for example db or socket).
The code you fixed is compiled! Thanks!
Could you describe what is idiomatic way to solve the issue, please?

I've never dealt with csv in Rust. You might benefit from crates.io: Rust Package Registry

The problem is not in CSV parsing (CSV is just for example), but how to write such kind of app

Do not build too many abstractions. How large are the files? Are all formats text based? Is there a fixed set of formats?

From your description I would have a struct CSVReader, a trait DataRead with a read method, a DataFormat enum with a detect_format(&str) -> Self method and a send_data<T: DataRead> function. Your main could look like this

fn main() {
    let file_contents = fs::read_to_string(file_path);
    match detect_format(file_contents) {
        DataFormat::CSV => send_data(CSVReader::new(file_contents)),
        _ => todo!()
    }
}

Certainly enough for a small CLI if your files are small enough.

1 Like

Thanks a lot for suggestion. But I would like to understand how to make objects hierarchy. Main idea of the example is create entities which have some relationships. Here are Factory->Format->Reader. So Factory create formats with detect method. Each format provide it own reader. Reader return it own record
Later new formats can be added. One file can be big - more than 4 GB.

And also I have Pipeline object that receive Format, get reader from it, get records in loop, put record in filters and then in record processors

Sounds like you might be describing inheritance. Rust does have some object oriented features like encapsulation and associated methods, but inheritance is not one of them.

Rust doesn't lend itself well to traditional object oriented code with inheritance and what comes along with that, namely factories.

To be more precise, what Rust doesn’t have is implementation inheritance.

If your hierarchy is only made (except for the last child / leafs ) of pure virtual class / interface / class with only function but no attribute, then you can model your hierarchy using trait without issue. If your original hierarchy had attributes in any of the parents class, you will have to change the design. But keep also in mind that most class hierarchy can be replaced by enum.

class Animal {…};
class Cat: Animal {…};
class BlackCat: Cat {…};

Will most likely be translated in Rust as:

struct BlackCat;
enum Cat {
    Black(BlackCat),
    …
}
enum Animal {
    Cat(Cat),
    …
}

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.