Skip to content

Using File IO and Configs

Reading contents from is a fundamental operation in any language, here are a few ways to do it in Rust.

std::fs

From my experience, this method works great with .txt, .yaml, and .json, and can be further passed to Serde for easy deserialization.

rust
use std::fs;

fn main() {
	// Example 1: Text file
    let s = fs::read_to_string("path/to/file.txt").unwrap();

    // Example 2: Yaml with serde reading a vector
    let s = fs::read_to_string("path/to/file.yaml").unwrap();
    let s_vec = serde_yaml::from_str::<Vec<String>>(&s).unwrap();
}
use std::fs;

fn main() {
	// Example 1: Text file
    let s = fs::read_to_string("path/to/file.txt").unwrap();

    // Example 2: Yaml with serde reading a vector
    let s = fs::read_to_string("path/to/file.yaml").unwrap();
    let s_vec = serde_yaml::from_str::<Vec<String>>(&s).unwrap();
}

The .yaml file from this example has only a list would look something like

yaml
---
- option_a
- option_b
- option_c
---
- option_a
- option_b
- option_c

For more complex usage you should create your own type and to deserialize into it.

config-rs

Reference https://github.com/mehcode/config-rs

This method is most suitable for reading configuration files into your program. A good practice is to couple this method with clap to allow user-defined configuration paths.

rust
use clap::Parser;
use config::{Config, File};
use serde::Deserialize;

#[derive(Deserialize, Debug)]
struct ProgramConfig {
    option_a: u64,
    option_b: u64,
    option_c: String,
}

#[derive(Parser, Debug)]
#[command(author = "Braden Stefanuk", version, about)]
struct Cli {
    /// path to config file
    #[arg(short, long)]
    config: String,
}

fn main() {
    let cli = Cli::parse();
    let cfg: ProgramConfig = Config::builder()
        .add_source(File::with_name(&cli.config))
        .build()
        .unwrap()
        .try_deserialize()
        .unwrap();
}
use clap::Parser;
use config::{Config, File};
use serde::Deserialize;

#[derive(Deserialize, Debug)]
struct ProgramConfig {
    option_a: u64,
    option_b: u64,
    option_c: String,
}

#[derive(Parser, Debug)]
#[command(author = "Braden Stefanuk", version, about)]
struct Cli {
    /// path to config file
    #[arg(short, long)]
    config: String,
}

fn main() {
    let cli = Cli::parse();
    let cfg: ProgramConfig = Config::builder()
        .add_source(File::with_name(&cli.config))
        .build()
        .unwrap()
        .try_deserialize()
        .unwrap();
}

When writing raw bytes to a file use the following

rust
struct Controller;

impl Controller {
    fn new() -> Self {
        Self {}
    }

    fn write_to(&self, p: &str, c: &[u8]) -> Result<()> {
        fs::write(p, c)?;
        Ok(())
    }
    fn read_from(&self, p: &str) -> Result<Vec<u8>> {
        let res = fs::read(p)?;
        Ok(res)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn write_to_file_from() -> Result<()> {
        let filename = "test_lsw_ctrl";
        let fs = Controller::new();
        fs.write_to(filename, b"1234")?;

        let contents = fs.read_from(filename)?;
        assert_eq!(contents, b"1234");
        Ok(())
    }
}
struct Controller;

impl Controller {
    fn new() -> Self {
        Self {}
    }

    fn write_to(&self, p: &str, c: &[u8]) -> Result<()> {
        fs::write(p, c)?;
        Ok(())
    }
    fn read_from(&self, p: &str) -> Result<Vec<u8>> {
        let res = fs::read(p)?;
        Ok(res)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn write_to_file_from() -> Result<()> {
        let filename = "test_lsw_ctrl";
        let fs = Controller::new();
        fs.write_to(filename, b"1234")?;

        let contents = fs.read_from(filename)?;
        assert_eq!(contents, b"1234");
        Ok(())
    }
}