Rust Lessons

Learn Rust by staring at code.

Lesson 2: PWD

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
#include <windows.h>
#include <stdio.h>
#include <tchar.h>

int _tmain(int argc, TCHAR* argv[]) {
    // (1) Probe for Buffer Size: Compiler injects call
    // to GetCurrentDirectory, returning required buffer 
    // size. Triggers kernel API interaction, marking 
    // CPU cycles for syscall handling. DWORD integral 
    // type maps to CPU register efficiency.
    DWORD requiredSize = GetCurrentDirectory(0, NULL);

    if (requiredSize == 0) { // (2) Handle Probe Failure
        // GetLastError interacts with thread-local 
        // storage, fetching last-error code. Calls 
        // context-switch to retrieve specific error. 
        // Low-level formatted I/O via _ftprintf, 
        // exploiting buffered I/O optimizations.
        _ftprintf(stderr, _T("Error 0x%lx: Buffer size probe failed\n"), GetLastError());
        return 1; // Non-zero exit triggers OS-level 
        // failure signal.
    }

    // (3) Allocate Memory Buffer: malloc triggers heap
    // allocation in process virtual address space. Size 
    // calculation uses sizeof(TCHAR) to ensure proper 
    // byte-alignment per character. TCHAR abstracts 
    // character width, enabling single code path for 
    // Unicode support.
    TCHAR* buffer = (TCHAR*)malloc(requiredSize * sizeof(TCHAR));

    if (!buffer) { // (4) Handle Allocation Failure
        // Buffer allocation failure traps memory 
        // constraints; error message via _ftprintf. 
        // Aligns with system I/O operations.
        _ftprintf(stderr, _T("Error: Alloc %lu chars failed\n"), requiredSize);
        return 1; // Propagates failure state to OS.
    }

    // (5) Retrieve Directory Path: GetCurrentDirectory
    // writes to allocated buffer, interacting with 
    // kernel-mode, transferring data from file system 
    // to user space. charsWritten holds count of 
    // characters written, internal buffer data alignment
    // optimized for sequential memory access.
    DWORD charsWritten = GetCurrentDirectory(requiredSize, buffer);

    if (charsWritten == 0) { // (6) Handle Path Retrieval Failure
        // Error handling engages GetLastError, buffering 
        // I/O through _ftprintf. Prevents memory leak 
        // via free(), deallocating heap space.
        _ftprintf(stderr, _T("Error 0x%lx: Path read failed\n"), GetLastError());
        free(buffer); // Critical to avoid resource leak.
        return 1; // Non-zero exit signals failure to OS.
    }

    // (7) Validate Path Length: Ensures charsWritten 
    // matches requiredSize - 1, detecting inconsistencies. 
    // charsWritten reflects accurate character count. 
    // Discrepancy here suggests potential issues in file 
    // system or path changes during execution.
    if (charsWritten != requiredSize - 1) {
        _ftprintf(stderr, _T("Error: Size mismatch (%lu vs %lu)\n"), requiredSize - 1, charsWritten);
        free(buffer); // Ensure memory release.
        return 1; // Exit signals resource validation fail.
    }

    // (8) Output Directory Path: _tprintf interacts with 
    // stdout, leveraging terminal I/O subsystems. 
    // Buffer data remains intact due to prior validation, 
    // ensuring correct output.
    _tprintf(_T("%s\n"), _T("The dir is \n"));
    _tprintf(_T("%s\n"), buffer);

    free(buffer); // Deallocates dynamic memory,
    // releases heap space, crucial for avoiding leaks.
    return 0; // Zero exit status signals success, 
    // concluding process cycle efficiently.
}

Rust Implementation

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
use std::env; // (1) `std::env` acts as a bridge between Rust
              //     and the underlying OS, using syscalls
              //     like `GetCurrentDirectory` (Windows) or
              //     `getcwd` (POSIX). These rely on syscall
              //     error handling, mapping to Rust's Result.

use std::io; // (2) IO imports connect to platform-specific
             //     stdio operations, heavily relying on libc.
             //     Provides abstractions for syscall-safe
             //     I/O, preventing UB caused by direct OS calls.

use std::path::PathBuf; // (3) `PathBuf` is an owned, mutable
                        //     representation of filesystem paths,
                        //     internally handling path separators
                        //     for cross-platform compatibility.

fn main() {
    // (4) `current_dir` syscall-like abstraction
    //     retrieves current working directory, translating
    //     OS-specific errors (e.g., errno) into Rust enums.
    let path = env::current_dir().unwrap_or_else(|e| {
        // (5) Error recovery logic uses lambdas (closures).
        //     `raw_os_error` extracts OS-level error codes,
        //     stored in platform-dependent formats (e.g.,
        //     Windows NTSTATUS or POSIX errno values).
        match e.raw_os_error() {
            Some(code) => eprintln!(
                "Error 0x{:x}: Path resolution failed",
                code
            ), // (6) `eprintln!` directly interacts with
               //     stderr using `libc`-backed syscalls like
               //     `write(2)`. Optimized for debug contexts.

            None => eprintln!("Unknown error occurred"),
        }

        std::process::exit(1); // (7) `exit` bypasses stack
                               //     unwinding, triggering immediate
                               //     OS termination via `exit_group`
                               //     syscall (Linux) or `_exit`
                               //     (Windows).
    });

    // (8) `to_str` converts the internal byte buffer in `PathBuf`
    //     to a UTF-8 `&str`, ensuring safe memory reads. If path
    //     contains invalid UTF-8, returns `None`, preventing UB
    //     common in direct C-style string manipulations.
    let path_str = path.to_str().unwrap_or_else(|| {
        eprintln!("Error: Invalid UTF-8 in path");
        std::process::exit(1);
    });

    // (9) `ends_with` inspects path buffer tail bytes without
    //     unnecessary allocations, leveraging slice comparisons.
    //     `trim_end_matches` avoids reallocation unless necessary.
    let formatted_path = if path_str.ends_with('\\') && path_str.len() == 3 {
        // (10) Edge case logic to handle root directories.
        //      Special cases for paths like `C:\` are preserved
        //      explicitly to mirror Windows behavior. Rust runtime
        //      avoids reallocating unchanged `&str` slices.
        path_str
    } else {
        path_str.trim_end_matches('\\') // (11) Optimized trim
                                         //      avoids intermediate
                                         //      memory copies by
                                         //      working directly
                                         //      on slices.
    };

    println!("{}", formatted_path); // (12) `println!` invokes
                                      //      buffered stdout syscalls
                                      //      (via `write` or `WriteFile`),
                                      //      ensuring atomic writes.
}

Exercise

Let us implement this in C++ using closures and compile time tricks.