Rust Files and I/O

This chapter introduces the I/O operations of the Rust language.

Receiving Command Line Arguments

Command line programs are the most basic form of computer programs. Almost all operating systems support command line programs and base the execution of visual programs on the command line mechanism.

A command line program must be able to receive arguments from the command line environment. These arguments usually appear after a command line command and are separated by spaces.

In many languages (such as Java and C/C++), environment arguments are passed to the program as parameters of the main function (often a string array). However, in Rust, the main function is a parameterless function, and environment arguments need to be retrieved by the developer through the std::env module. The process is very simple:

Example

fn main() {
    let args = std::env::args();
    println!("{:?}", args);
}

Now run the program directly:

Args { inner: ["D:\\rust\\greeting\\target\\debug\\greeting.exe"] }

Perhaps the result you get is much longer than this, which is normal. In this result, the Args structure contains an inner array that holds only a single string, representing the location of the currently running program.

But this data structure is hard to understand. That's okay; we can simply iterate over it:

Example

fn main() {
    let args = std::env::args();
    for arg in args {
        println!("{}", arg);
    }
}

Output:

D:\rust\greeting\target\debug\greeting.exe

Arguments are generally meant to be iterated over, aren't they?

Now let's open the long-untouched launch.json, find "args": [], where we can set runtime arguments. We'll change it to "args": ["first", "second"], then save and run the previous program again. The output:

D:\rust\greeting\target\debug\greeting.exe
first
second

As a real command line program, we have never actually used it. As a language tutorial, we won't describe how to run Rust programs from the command line here. But if you are a well-trained developer, you should be able to find the location of the executable file. You can try entering the directory and using command line commands to test the program's reception of command line environment arguments.

Command Line Input

Earlier chapters described in detail how to use command line output, which is necessary for language learning since programs cannot be debugged without output. However, getting input information from the command line is still quite important for a command line program.

In Rust, the std::io module provides functionality related to standard input (which can be considered command line input):

Example

use std::io::stdin;

fn main() {
let mut str_buf = String::new();

    stdin().read_line(&mut str_buf)
        .expect("Failed to read line.");

    println!("Your input line is \n{}", str_buf);
}

Making the VSCode environment support command line input is a very cumbersome matter, involving cross-platform issues and non-debuggable issues, so we will run the program directly in the VSCode terminal. Run it from the command line:

D:\rust\greeting> cd ./target/debug
D:\rust\greeting\target\debug> ./greeting.exe
EXAMPLE
Your input line is 
EXAMPLE

std::io::Stdio contains the read_line method, which can read a line of string into a buffer. The return value is the Result enum, used to convey errors that occur during reading, so the expect or unwrap functions are commonly used to handle errors.

Note: Currently, the Rust standard library does not yet provide a method to directly read numbers or formatted data from the command line. We can read a line of string and use string parsing functions to process the data.

File Reading

We create a file text.txt in the D:\ directory of the computer, with the following content:

This is a text file.

This is a program that reads the contents of a text file into a string:

Example

use std::fs;

fn main() {
    let text = fs::read_to_string("D:\\text.txt").unwrap();
    println!("{}", text);
}

Output:

This is a text file.

In Rust, reading an entire file that fits in memory is an extremely simple task. The read_to_string method in the std::fs module can easily complete reading a text file.

But if the file to be read is a binary file, we can use the std::fs::read function to read a collection of u8 type:

Example

use std::fs;

fn main() {
    let content = fs::read("D:\\text.txt").unwrap();
    println!("{:?}", content);
}

Output:

[84, 104, 105, 115, 32, 105, 115, 32, 97, 32, 116, 101, 120, 116, 32, 102, 105, 108, 101, 46]

The above two methods are one-time reads, which are very suitable for web application development. However, for some low-level programs, the traditional stream-based reading method is still irreplaceable, because in most cases the file size may far exceed the memory capacity.

File stream reading in Rust:

Example

use std::io::prelude::*;
use std::fs;

fn main() {
    let mut buffer = [0u8; 5];
    let mut file = fs::File::open("D:\\text.txt").unwrap();
    file.read(&mut buffer).unwrap();
    println!("{:?}", buffer);
    file.read(&mut buffer).unwrap();
    println!("{:?}", buffer);
}

Output:

[84, 104, 105, 115, 32] 
[105, 115, 32, 97, 32]

The File class in the std::fs module is a class that describes files and can be used to open files. After opening a file, we can use the File's read method to read the next bytes of the file into a buffer (the buffer is a u8 array) in a stream, with the number of bytes read equal to the length of the buffer.

Note: VSCode currently does not have the ability to automatically add standard library references, so sometimes errors like "function or method does not exist" may be caused by standard library reference issues. We can check the standard library's documentation comments (which appear when hovering over it) to manually add the standard library.

The open method of std::fs::File opens a file in "read-only" mode, and there is no corresponding close method, because the Rust compiler can automatically close the file when it is no longer used.

File Writing

File writing is divided into one-time writing and stream writing. Stream writing requires opening the file, and there are two ways to open it: "create" and "append".

One-time writing:

Example

use std::fs;

fn main() {
    fs::write("D:\\text.txt", "FROM RUST PROGRAM")
        .unwrap();
}

This is as simple and convenient as one-time reading. After running the program, the content of the D:\text.txt file will be rewritten to FROM RUST PROGRAM. Therefore, use one-time writing with caution! Because it will directly delete the file content (no matter how large the file is). If the file does not exist, it will create the file.

If you want to write file content in a stream, you can use the create method of std::fs::File:

Example

use std::io::prelude::*;
use std::fs::File;

fn main() {
    let mut file = File::create("D:\\text.txt").unwrap();
    file.write(b"FROM RUST PROGRAM").unwrap();
}

This program is equivalent to the previous one.

Note: The opened file must be stored in a mutable variable in order to use File's methods!

There is no append static method in the File class, but we can use OpenOptions to open a file with a specific method:

Example

use std::io::prelude::*;
use std::fs::OpenOptions;

fn main() -> std::io::Result<()> {
   
    let mut file = OpenOptions::new()
            .append(true).open("D:\\text.txt")?;

    file.write(b" APPEND WORD")?;

    Ok(())
}

After running, the content of the D:\text.txt file will become:

FROM RUST PROGRAM APPEND WORD

OpenOptions is a flexible way to open files. It can set open permissions. In addition to the append permission, there are also read and write permissions. If we want to open a file with read and write permissions, we can write:

Example


use std::io::prelude::*;
use std::fs::OpenOptions;

fn main() -> std::io::Result<()> {
   
    let mut file = OpenOptions::new()
            .read(true).write(true).open("D:\\text.txt")?;

    file.write(b"COVER")?;

    Ok(())
}

After running, the content of the D:\text.txt file will become:

COVERRUST PROGRAM APPEND WORD
Other Extensions