Skip to content

← Ground Rules: Values, Types, Control Flow step 16 of 24

Medium Primitives

Loop labels and labeled blocks

Find the first occurrence of target in the grid. Return a two-element vector containing its zero-based row and column indices, or [-1, -1] if it is absent.

pub fn find_in_grid(grid: &[Vec<i32>], target: i32) -> Vec<i64>

Search rows from top to bottom and values within each row from left to right. If there are multiple matches, return the earliest in that order. Rows can be empty or have different lengths. An empty grid, or a grid containing only empty rows, returns [-1, -1]. Compare the full i32 values, including negatives. Do not modify the grid.

Grid Target Result
[[1, 2], [3, 4]] 3 [1, 0]
[[], [7], [8, 9, 10]] 10 [2, 2]
[[5, 5], [5]] 5 [0, 0]
[[], []] 1 [-1, -1]

Repair the undeclared label

The starter uses break 'search without declaring 'search, which produces E0426. Its break also carries a vector value. Adding a label to the outer for loop alone is not sufficient: a for loop cannot be exited with a value. A labeled block around the search can receive that value, or you can restructure the function while preserving the required behavior.

A label selects the construct to leave

An ordinary break exits the innermost enclosing loop. A labeled break can exit an outer loop instead. This complete example stops both loops after visiting the first matching number:

fn main() {
    let rows = [[1, 2], [3, 4]];
    let mut visited = 0;
    'rows: for row in &rows {
        for &value in row {
            visited += 1;
            if value == 3 {
                break 'rows;
            }
        }
    }
    assert_eq!(visited, 3);
}

Write a label as an apostrophe, a name and a colon before the construct. continue 'rows would continue the next iteration of the labeled loop; it cannot target a plain block.

A labeled block can produce a value

A labeled block runs once. break 'label value ends it with that value; if no break is taken, its final expression supplies the value. These result paths must have compatible types.

fn first_positive(values: &[i32]) -> Option<i32> {
    'search: {
        for &value in values {
            if value > 0 {
                break 'search Some(value);
            }
        }
        None
    }
}

fn main() {
    assert_eq!(first_positive(&[-2, 0, 7, 9]), Some(7));
    assert_eq!(first_positive(&[-2, 0]), None);
}

For the grid search, the block can produce either the matching coordinates or the not-found vector. A loop expression can also receive a break value; for and while loops cannot.

Breaking from a block continues execution after that block. Returning exits the function, so it skips later statements in the function. Both operations still follow Rust's normal rules for dropping local values as their scopes end.

Index types

enumerate() gives usize indices. The return type uses i64 so it can also represent the sentinel -1. The task's indices fit in i64. A cast from a 64-bit usize to i64 is not a widening conversion: arbitrary unsigned values above i64::MAX would not retain their numeric value. Use the cast only with the index range in mind.

Your solution must compile, return the exact two-element vector and pass Clippy with warnings treated as errors.