Improving rust code, text to json objects

Hello there, Rustaceans.
This is my first post here, and I've just been learning Rust for two weeks. I have a text file containing [topic, questions, response options, and the correct answer].
I wrote a Python script to parse the file and output each question as a JSON object.
I ported this script to rust as a practice and got it to operate properly. However, I would implement your suggestions for improvement.
text file sample:

NEW QUESTION 1

  • (Topic 1)
    How many tasks does the product-based planning technique describe?

A. One
B. Two
C. Three
D. Four

Answer: D

NEW QUESTION 2

  • (Topic 1)
    Identify the missing words in the following sentence. An objective of the Directing a Project process is to [?] throughout a project's life,

A. document how risks will bemanaged
B. identify who will be responsible for quality
C. provide management direction and control
D. define the benefits to be realized

Answer: C

NEW QUESTION 3

  • (Topic 1)
    Which provides a single source of reference that may be used by people joining a project after it has been initiated so they can quickly and easily find out how the
    project is being
    managed?

A. Project Brief
B. Project Initiation Documentation
C. Project mandate
D. Project Product Description

Answer: B

NEW QUESTION 4

  • (Topic 1)
    Which process provides the Project Board with the information it requires in order to commit
    resources to the project?

A. Managing Product Delivery
B. Initiating a Project
C. Controlling a Stage
D. Directinga Project

Answer: B

here is my rust code:

use serde::{Deserialize, Serialize};
use std::fs::File;
use std::io::prelude::*;
use std::io::{self, BufReader};
use std::path::Path;

#[derive(Serialize, Deserialize)]
struct Question {
    topic: String,
    question: String,
    choice_a: String,
    choice_b: String,
    choice_c: String,
    choice_d: String,
    answer: String,
}

impl Default for Question {
    fn default() -> Question {
        Question {
            topic: "".to_string(),
            question: "".to_string(),
            choice_a: "".to_string(),
            choice_b: "".to_string(),
            choice_c: "".to_string(),
            choice_d: "".to_string(),
            answer: "".to_string(),
        }
    }
}

fn main() {
    let question = Question::default();
    let mut qs = vec![];
    qs.push(&question);
    if let Ok(lines) = read_lines("prince.txt") {
        let mut qa = Question::default();
        for line in lines {
            if let Ok(ip) = line {
                if is_topic(&ip) {
                    let push_topic = ip.clone();
                    qa.topic = push_topic;
                    continue;
                } else if is_question(&ip) {
                    let push_question = ip.clone();
                    qa.question = push_question;
                    continue;
                } else if is_choice(&ip) {
                    let choice = ip.clone();
                    if ip.starts_with("A.") {
                        qa.choice_a = choice;
                        continue;
                    } else if ip.starts_with("B.") {
                        qa.choice_b = choice;
                        continue;
                    } else if ip.starts_with("C.") {
                        qa.choice_c = choice;
                        continue;
                    } else if ip.starts_with("D.") {
                        qa.choice_d = choice;
                        continue;
                    }
                } else if is_answer(&ip) {
                    let push_answer = ip.clone();
                    qa.answer = push_answer.to_string().chars().skip(8).take(1).collect();
                    continue;
                } else {
                    let question_continued = ip.clone();
                    qa.question.push_str(&question_continued);
                }
            }

            let serialized = serde_json::to_string(&qa).unwrap();
            println!("{}", serialized);
        }
    }

    fn read_lines<P>(filename: P) -> io::Result<io::Lines<BufReader<File>>>
    where
        P: AsRef<Path>,
    {
        let file = File::open(filename)?;
        Ok(io::BufReader::new(file).lines())
    }

    fn is_choice(line: &String) -> bool {
        let choices = &["A. ", "B. ", "C. ", "D. "];
        let mut a: bool = false;
        for choice in choices {
            match line.starts_with(choice) {
                false => continue,
                true => a = true,
            }
        }
        return a;
    }

    fn is_question(line: &String) -> bool {
        let qeustion_start = &[
            "Who", "Which", "Where", "When", "What", "Why", "How", "Identify",
        ];
        let mut a: bool = false;

        for q in qeustion_start {
            match line.starts_with(q) {
                false => continue,
                true => a = true,
            }
        }
        return a;
    }

    fn is_answer(line: &String) -> bool {
        if line.starts_with("Answer: ") {
            return true;
        } else {
            return false;
        }
    }

    fn is_topic(line: &String) -> bool {
        if line.starts_with("- (Topic") {
            return true;
        } else {
            return false;
        }
    }
}


I appreciate your valuable opinions

Please format your code using rust-fmt. It is almost unreadable.

A few minor suggestions:

  • Try to leverage the APIs from the Result or Option types. For example, a Topic::parse method with the signature (string: &String) -> Result<Topic, SomeError> reflects better the intent of what you are trying to achieve and follows the monadic style of concatenating code, rather than an unreadable series of if conditions because your functions return a bool.

  • There are tons of unnecessary string clones (every time that you do ip.clone). You already have a String, and it will only be consumed once.

1 Like

I have formatted it. thank you.

regarding the second suggestion i removed all the clones and the code worked just fine. Thank you.
regarding the first suggestion i am rewrtining to follow the monadic style. really appricated your advices.

you could derive the default impl for the Question struct like:

#[derive(Serialize, Deserialize, Default)]
struct Question {
    topic: String,
    question: String,
    choice_a: String,
    choice_b: String,
    choice_c: String,
    choice_d: String,
    answer: String,
}

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.