Skip to content

← Shape Your Data: Structs, Enums, Pattern Matching step 25 of 26

Easy Primitives

Protect rectangle invariants with private fields

The shape module validates rectangle dimensions before storing them. Keep its fields private and add a public method for calculating area, so callers can use a rectangle without accessing its representation.

Required behavior

Both functions receive (width, height) pairs:

  • rect_areas(dims: Vec<(i64, i64)>) -> Vec<Option<u64>> returns Some(width * height) for valid dimensions and None otherwise.
  • orientations(dims: Vec<(i64, i64)>) -> Vec<String> returns "portrait" when width is smaller than height, "landscape" when width is larger, and "square" when they are equal. Invalid dimensions produce "invalid".

Both sides must be between 1 and u32::MAX, inclusive. Validate both before calculating an area or orientation. Keep the input order and return one result per pair, including duplicates. Empty input produces an empty vector from each function.

For [(3, 4), (5, 5), (0, 2)], the areas are [Some(12), Some(25), None] and the orientations are ["portrait", "square", "invalid"]. The harness serializes None as JSON null.

Fix the private-field error

Rect is public, but its w and h fields are private. The starter tries to read them from rect_areas, outside the shape module. Rust reports E0616.

Add pub fn area(&self) -> u64 to the existing impl Rect inside shape, then call it from rect_areas. Keep Rect::new and orientation working as supplied. Making the fields public would avoid the compiler error but would not meet this exercise's API requirement.

Convert each side to u64 before multiplying. The largest valid product is u32::MAX squared, or 18_446_744_065_119_617_025, which fits in u64. Multiplying as u32 first could overflow.

What privacy protects

An invariant is a condition that valid values of a type must satisfy. Here it is “both sides are positive.” Private fields prevent callers outside shape and its descendants from constructing or modifying those fields directly. Constructing a Rect literal there produces E0451.

Privacy makes the module responsible for preserving the invariant. Code inside the module and its child modules can still access the fields, so every constructor and mutating method exposed by the module must preserve the rule. Adding an unchecked constructor or a mutable field accessor would weaken that guarantee.

Private fields are not the only way to express invariants. For example, NonZeroU32 represents a nonzero integer in its type. Public fields still obey their field types; what they cannot enforce on their own is an extra rule such as a relationship between two unrestricted integer fields.

Struct and enum visibility

A public struct's fields remain private unless given their own visibility. Enum variants and their fields inherit the enum's visibility instead. That is why orientations can match shape::Orientation::Portrait even though it cannot read rect.w.

Public fields on an enum variant do not imply public access to private fields inside a separate struct carried by that variant. An API can use such a struct to expose a variant while keeping its payload's representation private.

Struct update syntax, such as Rect { ..existing }, does not bypass private-field checks. A Default implementation does not change that.

Visibility also depends on the path used to reach an item. A public item inside a private module can be used from scopes that can access that module, as this starter demonstrates. A public re-export can give an item another accessible path. Restricted forms include pub(crate) for the current crate, pub(super) for the parent module and its descendants, and pub(in path) for a specified ancestor module's scope.

Validation and conversion are separate

u32::try_from rejects negative inputs and values above u32::MAX, but accepts zero. The constructor's w > 0 && h > 0 guard supplies the extra check this type needs. A cast such as w as u32 would not validate the input; for example, -1_i64 as u32 is u32::MAX.

The submission must compile, pass the output cases and pass Clippy with warnings denied. Output cases alone do not prove that the fields stayed private, so preserve the module boundary as part of the implementation.