Lesson 0: CP
Co-relation with C
- Args:
env::args()↔argc/argv(Rust is type-safe) - Files:
File::open()↔fopen()(Result<>vsNULL) - Errors:
match Result<>↔if(!fp)manual checks - Buffers:
[0u8; BUF_SIZE]↔unsigned char buffer[BUF_SIZE] - Memory: Auto
drop()↔ Manualfclose() - Exit:
process::exit()↔exit()(same codes)
Rust Concepts for this Lesson
- Result Type: Rust uses
Result<T, E>for error handlingOk(T): Success case with value of type TErr(E): Error case with error of type E
- File Operations:
- Uses the
std::fs::Filestruct - File operations return
Resulttypes
- Uses the
- Ownership Model:
- Each value has a single owner
- Error Handling:
- Pattern matching with
match
- Pattern matching with
- 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.