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
Validate: two accepted, one rejected
The constructor accepts Saira and Diya and rejects the invalid 120 row.
Validate: two accepted, one rejectedSort: 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 SairaAverage: 170 / 2 = 85.0
The empty slice takes the early return and prints No students.
Average: 170 / 2 = 85.0
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 studentsFollow 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
Apply the rule
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.