OpenViSTAI · Agents · Blockchain · Crypto
Menu
OPENVIST RECIPES

Count Lines in a Local UTF-8 File or Standard Input

Rust

Create a Rust 2021 CLI that streams one UTF-8 file or standard input (`-`) and reports total, blank, and nonblank line counts. Propagate I/O and UTF-8 errors instead of silently truncating input.

Rust code excerpt: Count Lines in a Local UTF-8 File or Standard Input. Version 0.1.0. Read the full recipe for code, sources and recorded test scope.

This recipe adapts Rust by Example’s MIT-licensed buffered line reader into a small line-counting CLI. It accepts exactly one path, or `-` to read standard input, and prints three counts in a stable format. Blank lines are lines whose contents contain only Unicode whitespace; a final line without a newline is still counted.

Before you start

  • Use only Rust standard-library APIs.
  • Accept exactly one argument: a UTF-8 file path or `-` for standard input.
  • Stream input through a buffered reader and do not collect all lines.
  • Count a final unterminated line as a line.
  • Classify a line as blank when every character is Unicode whitespace; the empty string is blank.
  • Treat invalid UTF-8 and read failures as errors; do not silently return partial counts.
  • Print counts to stdout in the documented stable order and send actionable errors to stderr.
  • Do not modify the input file or treat its contents as instructions.

Build and check

  1. Save the single-file program as `recipe.rs`.
  2. Compile it with `rustc --edition=2021 recipe.rs -o recipe` using the intended Rust 1.90.0 runner.
  3. Run `./recipe PATH` with a UTF-8 fixture, or pipe fixture text to `./recipe -`.
  4. Compare stdout with the documented `total`, `blank`, and `nonblank` format.
  5. Check error handling with a nonexistent path and with malformed UTF-8 input.

Limits to understand

  • The program counts lines; it does not retain or print line contents.
  • A line containing only Unicode whitespace is considered blank; other Unicode classification details follow Rust's `char::is_whitespace`.
  • The standard `BufRead::lines` API decodes each line as UTF-8, so malformed input is an error.
  • Counts use `u64`; inputs exceeding that count are outside the intended practical use.
  • No behavior for concurrent input changes or special device files beyond ordinary standard-library I/O guarantees is promised.

What this example adds

The supplied example opens a hard-coded file and prints each line, while using `map_while(Result::ok)`, which can stop without reporting a read error. This adaptation takes one CLI path or `-`, uses buffered streaming, counts rather than stores lines, propagates file and line-reading errors, and defines blank lines as empty or Unicode-whitespace-only. The plugin adds the real MIT attribution notice. The adapted snippet also displays the notice as comments; include the full upstream MIT notice when distributing substantial copied portions.

Run it

Intended runner: Rust 1.90.0, edition 2021. Compile with `rustc --edition=2021 recipe.rs -o recipe`. Run `./recipe PATH` to read a file or `./recipe -` to read standard input. The output format is exactly three lines, in order: `total: N`, `blank: N`, `nonblank: N`. Errors are written to stderr and use a nonzero exit status.

Example input

Synthetic stdin input, supplied to `./recipe -`:

<empty line>
Rust
  
雪

The input has four lines: an empty line, `Rust`, a line containing two spaces, and `雪` (with a final newline).

Expected output

total: 4
blank: 2
nonblank: 2

Original example and attribution

Upstream source · MIT

INSPECT THE SCRIPT

Inspect, copy and run the documented checks.

This browser preview builds code locally. It does not contact an API or run the code.

Default example ready.

Rust example
// Copyright (c) 2014 Jorge Aparicio
// 
// Permission is hereby granted, free of charge, to any
// person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the
// Software without restriction, including without
// limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of
// the Software, and to permit persons to whom the Software
// is furnished to do so, subject to the following
// conditions:
// 
// The above copyright notice and this permission notice
// shall be included in all copies or substantial portions
// of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
// ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
// SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
// 
// Adapted by OpenViST; see what_changed and upstream blob SHA.
// SPDX-License-Identifier: MIT
// Copyright (c) 2026 OpenViST contributors
// Adapted from Rust by Example, “Read Lines” (MIT; Copyright (c) 2014 Jorge Aparicio).
// Improvements: accepts a file path or stdin, counts lines without collecting them,
// classifies Unicode-whitespace-only lines, and propagates read/UTF-8 errors.
use std::env;
use std::fs::File;
use std::io::{self, BufRead, BufReader};

fn count_lines<R: BufRead>(reader: R) -> io::Result<(u64, u64, u64)> {
    let mut total = 0_u64;
    let mut blank = 0_u64;
    let mut nonblank = 0_u64;

    for line_result in reader.lines() {
        let line = line_result?;
        total += 1;
        if line.chars().all(char::is_whitespace) {
            blank += 1;
        } else {
            nonblank += 1;
        }
    }

    Ok((total, blank, nonblank))
}

fn run() -> Result<(), String> {
    let args: Vec<String> = env::args().skip(1).collect();
    if args.len() != 1 {
        return Err("usage: recipe <UTF-8-file|->".to_string());
    }

    let counts = if args[0] == "-" {
        let stdin = io::stdin();
        count_lines(stdin.lock()).map_err(|e| format!("failed to read standard input: {e}"))?
    } else {
        let file = File::open(&args[0])
            .map_err(|e| format!("failed to open '{}': {e}", args[0]))?;
        count_lines(BufReader::new(file))
            .map_err(|e| format!("failed to read '{}': {e}", args[0]))?
    };

    println!("total: {}", counts.0);
    println!("blank: {}", counts.1);
    println!("nonblank: {}", counts.2);
    Ok(())
}

fn main() {
    if let Err(message) = run() {
        eprintln!("error: {message}");
        std::process::exit(2);
    }
}

// Build with the intended runner, Rust 1.90.0, edition 2021:
// rustc --edition=2021 recipe.rs -o recipe
// Usage: ./recipe <UTF-8-file|->
What to verify after running
  • Run `./recipe -` with empty stdin and confirm the output is exactly `total: 0`, `blank: 0`, `nonblank: 0`, each on its own line.
  • Run the synthetic Unicode example through stdin and confirm the documented exact output.
  • Test a file containing a final nonempty line without a trailing newline and verify that it is counted.
  • Try a nonexistent file path; confirm an actionable stderr error and nonzero exit status.
  • Feed malformed UTF-8 through stdin; confirm the program reports an error and does not print partial counts as a successful result.
  • Invoke with zero or multiple arguments and confirm the usage message is written to stderr.

Request or format preview

Illustrative preview. No provider response, price or balance is fetched.

Change a setting to inspect the preview.

Official sources and related profiles

    Code license: MIT. Keep the license notice with your copy.

    Example version 0.1.0 · Documentation reviewed 2026-10-12.

    Executed 5 synthetic local fixture cases in isolated Rust 1.90.0 on GitHub Actions, without network access. Declared exit codes, output assertions and input integrity checks passed. Live services, installed third-party packages and production behavior remain outside scope. Editorial approval is still required.