ProgramGuru

Build a validated student report

Build a validated student report

We can now combine typed records, parsing, iteration and reporting into a small school project. Each record must have a nonempty name and a mark from zero to 100. Invalid rows should be explained rather than silently included in the average.

What you’ll learn

  • Validate records before they affect a report.
  • Produce sorted output, a fractional average and an empty-register result.

Build a validated student report in action

  1. Validate: two accepted, one rejected

    The constructor accepts Saira and Diya and rejects the invalid 120 row.

    Validate: two accepted, one rejected
  2. Sort: Diya then Saira

    The report sorts the accepted records into Diya then Saira, sums 170 and divides by two as floating-point arithmetic.

    Sort: Diya then Saira
  3. Average: 170 / 2 = 85.0

    The empty slice takes the early return and prints No students.

    Average: 170 / 2 = 85.0
The constructor accepts Saira and Diya and rejects the invalid 120 row. The report sorts the accepted records into Diya then Saira, sums 170 and divides by two as floating-point arithmetic. The empty slice takes the early return and prints No students.

Build a validated student report explained

The Student constructor owns a cleaned name and validates the mark before constructing the record. The report sorts accepted students by name and computes an f64 average from their integer total. An empty collection needs its own outcome to avoid dividing by zero. This project remains a console exercise rather than a school database.

Connect this idea with structs and named fields and result and recoverable errors and iterators, closures and transformations.

Work through the Rust program

Save the complete source as main.rs in the Rust environment. No standard input is needed.

Rust 2024 · complete program

struct Student { name: String, score: u32 }
impl Student {
  fn new(name: &str, score: u32) -> Result<Self, &'static str> {
    if name.trim().is_empty() { return Err("Name is required"); }
    if score > 100 { return Err("Mark must be 0 to 100"); }
    Ok(Self { name: name.trim().to_string(), score })
  }
}
fn report(students: &mut [Student]) {
  if students.is_empty() { println!("No students"); return; }
  students.sort_by(|a, b| a.name.cmp(&b.name));
  let total: u32 = students.iter().map(|s| s.score).sum();
  for student in students.iter() { println!("{}: {}", student.name, student.score); }
  println!("Average: {:.1}", f64::from(total) / students.len() as f64);
}
fn main() {
  let mut students = Vec::new();
  for (name, score) in [("Saira", 90), ("Diya", 80), ("Invalid", 120)] {
    match Student::new(name, score) {
      Ok(student) => students.push(student),
      Err(error) => println!("Skipped {name}: {error}"),
    }
  }
  report(&mut students);
  report(&mut []);
}

Expected output

Skipped Invalid: Mark must be 0 to 100
Diya: 80
Saira: 90
Average: 85.0
No students

Follow the execution

The constructor accepts Saira and Diya and rejects the invalid 120 row. The report sorts the accepted records into Diya then Saira, sums 170 and divides by two as floating-point arithmetic. The empty slice takes the early return and prints No students.

Try the complete program in the Rust compiler. For tests, Cargo commands and local files, use the specified local environment.

Try a small variation

Your task: If Saira’s valid mark becomes 70, what is the accepted report average?

Rust 2024 · complete program

struct Student { name: String, score: u32 }
impl Student {
  fn new(name: &str, score: u32) -> Result<Self, &'static str> {
    if name.trim().is_empty() { return Err("Name is required"); }
    if score > 100 { return Err("Mark must be 0 to 100"); }
    Ok(Self { name: name.trim().to_string(), score })
  }
}
fn report(students: &mut [Student]) {
  if students.is_empty() { println!("No students"); return; }
  students.sort_by(|a, b| a.name.cmp(&b.name));
  let total: u32 = students.iter().map(|s| s.score).sum();
  for student in students.iter() { println!("{}: {}", student.name, student.score); }
  println!("Average: {:.1}", f64::from(total) / students.len() as f64);
}
fn main() {
  let mut students = Vec::new();
  for (name, score) in [("Saira", 90), ("Diya", 80), ("Invalid", 120)] {
    match Student::new(name, score) {
      Ok(student) => students.push(student),
      Err(error) => println!("Skipped {name}: {error}"),
    }
  }
  report(&mut students);
  report(&mut []);
}

Worked answer

The average becomes 75.0: Diya has 80 and Saira has 70, giving a total of 150 for two accepted students. The rejected row remains excluded.

Mistakes and limits to recognize

The example uses u32 totals suitable for a small exercise; a huge dataset needs overflow-aware accumulation. Sorting uses string ordering rather than a locale-aware collation policy. Private fields and controlled constructors are important when enforcing invariants across real module boundaries.

Check your understanding

If Saira’s valid mark becomes 70, what is the accepted report average?
Choose one answer. You can try again.

Answer and explanation

The average becomes 75.0: Diya has 80 and Saira has 70, giving a total of 150 for two accepted students.

The average becomes 75.0: Diya has 80 and Saira has 70, giving a total of 150 for two accepted students. The rejected row remains excluded.

Apply the rule

A row parses as 120. Should it affect this report average?
Choose one answer. You can try again.

Answer and explanation

No, the constructor rejects it before reporting.

The accepted collection excludes rejected marks.

Take the idea into your next program

The average becomes 75.0: Diya has 80 and Saira has 70, giving a total of 150 for two accepted students. The rejected row remains excluded. The example uses u32 totals suitable for a small exercise; a huge dataset needs overflow-aware accumulation.

Further reading: the official Rust Book on build a validated student report.

Reaching the recap and completing the quizzes are separate milestones.

    Course overview →