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::File
struct - File operations return
Result
types
- 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.