Rust Lessons

Learn Rust by staring at code.

Lesson 0: CP

Co-relation with C

  1. Args: env::args()argc/argv (Rust is type-safe)
  2. Files: File::open()fopen() (Result<> vs NULL)
  3. Errors: match Result<>if(!fp) manual checks
  4. Buffers: [0u8; BUF_SIZE]unsigned char buffer[BUF_SIZE]
  5. Memory: Auto drop() ↔ Manual fclose()
  6. Exit: process::exit()exit() (same codes)

Rust Concepts for this Lesson

  1. Result Type: Rust uses Result<T, E> for error handling
    • Ok(T): Success case with value of type T
    • Err(E): Error case with error of type E
  2. File Operations:
    • Uses the std::fs::File struct
    • File operations return Result types
  3. Ownership Model:
    • Each value has a single owner
  4. Error Handling:
    • Pattern matching with match
  5. Command Line Arguments:
    • Safe iteration over arguments
    • UTF-8 validation built-in
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
// Included/Imported (1) Modules 
use std::env;
use std::fs::File;
use std::io::{Read, Write};
use std::process;

const BUF_SIZE: usize = 256;
// Constant (2): `BUF_SIZE`. Type: `usize`.
// Defines fixed size of data buffer.

fn main() {
    // Variable (3): `args`. Type: `Vec<String>`. Stores command
    // line args. Purpose is processing file copies
    // based on input args.
    let args: Vec<String> = env::args().collect();

    // Control Flow (4): Conditional `if`. Checks if number
    // of command line args is exactly 3 (program name,
    // input file path and output file path)
    // Edge Case: `args.len()` too short, too long.
    if args.len() != 3 {
        // Operation (5): Prints usage info on stderr.
        // Implies error in number of args.
        eprintln!("Usage: {} file1 file2", args[0]);
        // Operation (6): Terminate program with failure
        // code. Indicates incorrect usage.
        process::exit(1);
    }

    // Variable (7): `in_file`. Type: `File`. Represents
    // input file object which is opened for reading.
    // Purpose is to read contents from it.
    // Sub unit : File handle.
    // Result: `File`, or `Err`.
    let mut in_file = match File::open(&args[1]) {
        // Control Flow (8): `match` expression on `Result`.
        // `Ok` path opens file, `Err` handles open failure.
        Ok(file) => file,
        // Control Flow (9): Error handler for file open.
        Err(err) => {
            // Operation (10): Prints error details of open on
            // stderr. Indicates file access failure.
            eprintln!("{}: {}", args[1], err);
            // Operation (11): Exits program due to open
            // failure. Input file is critical.
            process::exit(2);
        }
    };

    // Variable (12): `out_file`. Type: `File`. Represents
    // output file object which is opened for writing.
    // Purpose is to write contents to it.
    // Sub unit : File handle.
    // Result: `File`, or `Err`.
    let mut out_file = match File::create(&args[2]) {
        // Control Flow (13): `match` expression on `Result`.
        // `Ok` creates file, `Err` handles create failure.
        Ok(file) => file,
        // Control Flow (14): Error handler for file create.
        Err(err) => {
            // Operation (15): Prints error details of file
            // create on stderr. Indicates file creation fail.
            eprintln!("{}: {}", args[2], err);
            // Operation (16): Exits program due to create
            // failure. Output file is critical.
            process::exit(3);
        }
    };

    // Variable (17): `buffer`. Type: `[u8; BUF_SIZE]`.
    // Fixed size buffer to store data read from input
    // file. Data unit: fixed size array of bytes.
    // Size: BUF_SIZE bytes.
    let mut buffer = [0u8; BUF_SIZE];

    // Control Flow (18): Infinite `loop`. Reads data from input file,
    // writes data to output file, terminates when
    // end-of-file is reached or error occurs
    loop {
        // Variable (19): `bytes_in`. Type: `usize`. Stores
        // number of bytes read from the file in current iteration.
        // Domain: Non-negative integers <= BUF_SIZE.
        // Range: 0 to BUF_SIZE.
        let bytes_in = match in_file.read(&mut buffer) {
            // Control Flow (20): `match` for result from read.
            // `Ok` provides number of bytes read.
            Ok(n) => n,
            // Control Flow (21): Error handler for read errors.
            Err(err) => {
                // Operation (22): Prints file read error to stderr.
                // Indicates low level file system issues.
                eprintln!("Error reading file: {}", err);
                // Operation (23): Terminates program on read error.
                // Input file data is necessary.
                process::exit(4);
            }
        };

        // Control Flow (24): Checks for end-of-file condition.
        // Checks if bytes read in last read were zero.
        if bytes_in == 0 {
            // Operation (25): Breaks infinite loop upon EOF.
            // Terminates read write cycle.
            break;
        }

        // Control Flow (26): Conditional `if let Err()`.
        // Writes bytes read to output file, checks for error
        if let Err(err) = out_file.write_all(&buffer[..bytes_in]) {
            // Operation (27): Print error message on stderr.
            //  Indicates critical failure in file write.
            eprintln!("Fatal write error: {}", err);
            // Operation (28): Terminate program upon write fail
            // Output file write is necessary.
            process::exit(5);
        }
    }
}

Exercise

Let us do byte by byte copy, exactly one byte. Read exactly one, write exactly one. No more and no less.