IO

IO #

std::io adalah fondasi semua operasi input/output di Rust. Dua trait inti — Read dan Write — mendefinisikan abstraksi universal yang diimplementasikan oleh file, jaringan, buffer memori, stdin/stdout, dan banyak tipe lainnya. Kode yang menerima impl Read bisa bekerja dengan file, stdin, atau Vec<u8> tanpa perubahan. Artikel ini membahas std::io secara menyeluruh sebagai bagian dari standard library — mulai dari stdin/stdout hingga operasi file, manajemen direktori, environment variable, dan menjalankan proses anak. Untuk topik yang lebih spesifik seperti socket dan async I/O, lihat artikel I/O di section Lanjutan.

Trait Read dan Write #

Sebelum membahas implementasi konkret, penting memahami dua trait yang menjadi fondasi seluruh ekosistem I/O:

use std::io::{self, Read, Write};

// Fungsi generik — bekerja dengan SEMUA tipe yang impl Read
fn baca_semua<R: Read>(mut reader: R) -> io::Result<Vec<u8>> {
    let mut buffer = Vec::new();
    reader.read_to_end(&mut buffer)?;
    Ok(buffer)
}

// Fungsi generik — bekerja dengan SEMUA tipe yang impl Write
fn tulis_semua<W: Write>(mut writer: W, data: &[u8]) -> io::Result<()> {
    writer.write_all(data)?;
    writer.flush()?;
    Ok(())
}

fn main() -> io::Result<()> {
    // Fungsi yang sama bekerja untuk file, stdin, Vec<u8>
    let isi_file = baca_semua(std::fs::File::open("Cargo.toml")?)?;
    let isi_vec = baca_semua(b"data dari byte slice" as &[u8])?;

    tulis_semua(std::fs::File::create("output.txt")?, b"konten")?;
    tulis_semua(io::stdout(), b"ke layar\n")?;

    let mut buffer: Vec<u8> = Vec::new();
    tulis_semua(&mut buffer, b"ke memori")?;

    Ok(())
}

Stdin — Membaca Input #

use std::io::{self, BufRead, Write};

fn main() -> io::Result<()> {
    // Baca satu baris dari stdin
    print!("Masukkan nama: ");
    io::stdout().flush()?;  // wajib flush sebelum read untuk tampilkan prompt

    let mut nama = String::new();
    io::stdin().read_line(&mut nama)?;
    let nama = nama.trim();  // hapus '\n' di akhir
    println!("Halo, {}!", nama);

    // Baca banyak baris sampai EOF
    println!("Masukkan teks (Ctrl+D untuk selesai):");
    let stdin = io::stdin();
    let mut baris_list = Vec::new();

    for baris in stdin.lock().lines() {
        let baris = baris?;
        if baris.is_empty() {
            break;  // atau gunakan EOF (Ctrl+D)
        }
        baris_list.push(baris);
    }

    println!("Kamu memasukkan {} baris:", baris_list.len());
    for (i, b) in baris_list.iter().enumerate() {
        println!("  {}: {}", i + 1, b);
    }

    Ok(())
}

Membaca Input dan Parsing #

use std::io::{self, Write};

fn baca_angka(prompt: &str) -> i64 {
    loop {
        print!("{}", prompt);
        io::stdout().flush().unwrap();

        let mut input = String::new();
        io::stdin().read_line(&mut input).unwrap();

        match input.trim().parse::<i64>() {
            Ok(n) => return n,
            Err(_) => println!("Input tidak valid, coba lagi."),
        }
    }
}

fn baca_pilihan(prompt: &str, opsi: &[&str]) -> usize {
    loop {
        println!("{}", prompt);
        for (i, opsi) in opsi.iter().enumerate() {
            println!("  {}. {}", i + 1, opsi);
        }
        print!("Pilihan (1-{}): ", opsi.len());
        io::stdout().flush().unwrap();

        let mut input = String::new();
        io::stdin().read_line(&mut input).unwrap();

        if let Ok(n) = input.trim().parse::<usize>() {
            if n >= 1 && n <= opsi.len() {
                return n - 1;  // 0-indexed
            }
        }
        println!("Pilihan tidak valid.");
    }
}

fn main() {
    let a = baca_angka("Masukkan angka pertama: ");
    let b = baca_angka("Masukkan angka kedua: ");
    println!("{} + {} = {}", a, b, a + b);

    let pilihan = baca_pilihan("Pilih operasi:", &["Tambah", "Kurang", "Kali", "Bagi"]);
    println!("Kamu memilih operasi ke-{}", pilihan + 1);
}

Stdout dan Stderr #

use std::io::{self, Write};

fn main() {
    // println! dan print! — ke stdout
    println!("Output normal ke stdout");
    print!("Tanpa newline ");
    println!("lanjut di sini");

    // eprintln! dan eprint! — ke stderr (untuk error dan diagnostik)
    eprintln!("Error: koneksi gagal");
    eprint!("Warning: ");
    eprintln!("memori hampir penuh");

    // Tulis langsung ke stdout/stderr (lebih kontrol)
    let stdout = io::stdout();
    let mut handle = stdout.lock();  // lock untuk performa di loop
    for i in 0..5 {
        writeln!(handle, "Baris {}", i).unwrap();
    }

    // Redirect stdout ke file (biasanya dari shell, tapi bisa juga dari kode)
    // Program: cargo run > output.txt
    // Semua println! akan masuk ke output.txt
    // eprintln! tetap ke terminal

    // Buffer stdout secara manual
    use std::io::BufWriter;
    let stdout = io::stdout();
    let mut writer = BufWriter::new(stdout.lock());
    for i in 0..10_000 {
        writeln!(writer, "Baris {}", i).unwrap();
    }
    writer.flush().unwrap();
}

BufReader dan BufWriter #

Setiap panggilan read() atau write() adalah syscall — mahal. Buffering mengurangi jumlah syscall secara drastis:

use std::fs::File;
use std::io::{self, BufRead, BufReader, BufWriter, Write};

fn proses_file_besar(path_masuk: &str, path_keluar: &str) -> io::Result<u64> {
    let file_masuk = File::open(path_masuk)?;
    let file_keluar = File::create(path_keluar)?;

    // Default buffer 8KB — cukup untuk kebanyakan kasus
    let reader = BufReader::new(file_masuk);
    let mut writer = BufWriter::new(file_keluar);

    let mut baris_count = 0u64;

    for (nomor, baris_result) in reader.lines().enumerate() {
        let baris = baris_result?;
        baris_count += 1;

        // Transformasi: nomor baris + uppercase
        writeln!(writer, "{:6}: {}", nomor + 1, baris.to_uppercase())?;
    }

    writer.flush()?;  // wajib!
    Ok(baris_count)
}

// BufReader dengan ukuran buffer kustom
fn baca_binary_besar(path: &str) -> io::Result<Vec<u8>> {
    use std::io::Read;

    let file = File::open(path)?;
    let mut reader = BufReader::with_capacity(64 * 1024, file);  // 64KB buffer

    let mut konten = Vec::new();
    reader.read_to_end(&mut konten)?;
    Ok(konten)
}

Operasi File #

Baca dan Tulis Sederhana #

use std::fs;
use std::io;

fn main() -> io::Result<()> {
    // Tulis string ke file (buat baru atau timpa)
    fs::write("halo.txt", "Baris pertama\nBaris kedua\n")?;

    // Baca seluruh file sebagai String
    let isi = fs::read_to_string("halo.txt")?;
    println!("Isi:\n{}", isi);

    // Baca sebagai bytes
    let bytes = fs::read("halo.txt")?;
    println!("Ukuran: {} byte", bytes.len());

    // Salin file
    fs::copy("halo.txt", "salinan.txt")?;

    // Rename / pindahkan
    fs::rename("salinan.txt", "dipindahkan.txt")?;

    // Metadata file
    let meta = fs::metadata("halo.txt")?;
    println!("Ukuran: {} byte", meta.len());
    println!("Readonly: {}", meta.permissions().readonly());
    println!("Modifikasi: {:?}", meta.modified()?);

    // Hapus file
    fs::remove_file("halo.txt")?;
    fs::remove_file("dipindahkan.txt")?;

    Ok(())
}

OpenOptions — Kontrol Mode Buka File #

use std::fs::OpenOptions;
use std::io::{self, Write};

fn main() -> io::Result<()> {
    // Append — tambah di akhir tanpa menimpa
    {
        let mut file = OpenOptions::new()
            .append(true)
            .create(true)       // buat jika belum ada
            .open("log.txt")?;
        writeln!(file, "[{}] Event terjadi", chrono_now())?;
    }

    // Buat baru, error jika sudah ada
    {
        let _ = OpenOptions::new()
            .write(true)
            .create_new(true)   // error jika file sudah ada
            .open("baru.txt");
        // Abaikan error untuk demo
    }

    // Baca dan tulis sekaligus
    {
        let _ = OpenOptions::new()
            .read(true)
            .write(true)
            .open("log.txt");
    }

    // Bersihkan file (truncate ke 0 byte)
    {
        let _ = OpenOptions::new()
            .write(true)
            .truncate(true)
            .open("log.txt");
    }

    std::fs::remove_file("log.txt").ok();
    std::fs::remove_file("baru.txt").ok();
    Ok(())
}

fn chrono_now() -> String {
    // Placeholder untuk timestamp
    String::from("2024-08-24 10:30:00")
}

Seek — Posisi Kursor File #

use std::fs::File;
use std::io::{self, Read, Seek, SeekFrom, Write};

fn main() -> io::Result<()> {
    // Tulis file dengan konten terstruktur
    {
        let mut file = File::create("terstruktur.bin")?;
        file.write_all(&100u32.to_le_bytes())?;  // header: 4 byte
        file.write_all(b"DATA1234")?;              // data: 8 byte
        file.write_all(&200u32.to_le_bytes())?;   // footer: 4 byte
    }

    // Baca hanya bagian tertentu
    {
        let mut file = File::open("terstruktur.bin")?;

        // Baca header
        let mut header = [0u8; 4];
        file.read_exact(&mut header)?;
        let nilai_header = u32::from_le_bytes(header);
        println!("Header: {}", nilai_header);  // 100

        // Loncat ke footer (skip 8 byte data)
        file.seek(SeekFrom::Current(8))?;
        let mut footer = [0u8; 4];
        file.read_exact(&mut footer)?;
        println!("Footer: {}", u32::from_le_bytes(footer));  // 200

        // Kembali ke awal
        file.seek(SeekFrom::Start(0))?;

        // Posisi saat ini
        let posisi = file.seek(SeekFrom::Current(0))?;
        println!("Posisi: {}", posisi);  // 0

        // Dari akhir file
        file.seek(SeekFrom::End(-4))?;
        let posisi = file.seek(SeekFrom::Current(0))?;
        println!("Posisi dari akhir -4: {}", posisi);  // 12
    }

    std::fs::remove_file("terstruktur.bin")?;
    Ok(())
}

Manajemen Direktori #

use std::fs;
use std::io;

fn main() -> io::Result<()> {
    // Buat direktori
    fs::create_dir("direktori-baru")?;

    // Buat nested direktori (parent yang belum ada dibuat otomatis)
    fs::create_dir_all("a/b/c/d")?;

    // Buat beberapa file
    fs::write("direktori-baru/file1.txt", "isi 1")?;
    fs::write("direktori-baru/file2.rs", "fn main() {}")?;
    fs::write("direktori-baru/data.json", "{}")?;

    // Baca isi direktori
    println!("Isi direktori:");
    for entri in fs::read_dir("direktori-baru")? {
        let entri = entri?;
        let path = entri.path();
        let meta = entri.metadata()?;
        let tipe = if meta.is_dir() { "DIR" } else { "FILE" };
        println!("  [{:4}] {} ({} byte)",
            tipe,
            path.file_name().unwrap().to_string_lossy(),
            meta.len()
        );
    }

    // Urutkan entri
    let mut entri_list: Vec<_> = fs::read_dir("direktori-baru")?
        .filter_map(|e| e.ok())
        .collect();
    entri_list.sort_by_key(|e| e.file_name());

    // Cek keberadaan path
    println!("Ada direktori: {}", fs::metadata("direktori-baru").is_ok());
    println!("Ada file: {}", fs::metadata("tidak-ada.txt").is_ok());

    // Hapus direktori (harus kosong)
    fs::remove_file("direktori-baru/file1.txt")?;
    fs::remove_file("direktori-baru/file2.rs")?;
    fs::remove_file("direktori-baru/data.json")?;
    fs::remove_dir("direktori-baru")?;

    // Hapus direktori beserta isinya (rekursif)
    fs::remove_dir_all("a")?;

    Ok(())
}

std::path — Manajemen Path #

use std::path::{Path, PathBuf};

fn main() {
    // PathBuf: owned, bisa dimodifikasi (seperti String)
    let mut path = PathBuf::from("/home/budi");
    path.push("dokumen");
    path.push("laporan.pdf");
    println!("{}", path.display());  // /home/budi/dokumen/laporan.pdf

    // Path: borrowed, tidak bisa dimodifikasi (seperti &str)
    let ref_path: &Path = Path::new("/etc/hosts");

    // Komponen path
    println!("file_name: {:?}", path.file_name());    // Some("laporan.pdf")
    println!("extension: {:?}", path.extension());     // Some("pdf")
    println!("file_stem: {:?}", path.file_stem());     // Some("laporan")
    println!("parent:    {:?}", path.parent());        // Some("/home/budi/dokumen")

    // join — gabungkan path
    let base = Path::new("/var/log");
    let log = base.join("app").join("error.log");
    println!("{}", log.display());  // /var/log/app/error.log

    // Cek tipe
    println!("exists: {}", path.exists());
    println!("is_file: {}", path.is_file());
    println!("is_dir: {}", path.is_dir());
    println!("is_absolute: {}", path.is_absolute());

    // Konversi ke String
    if let Some(s) = path.to_str() {
        println!("Sebagai &str: {}", s);
    }
    let s = path.to_string_lossy();  // tidak gagal, ganti karakter tidak valid
    println!("Lossy: {}", s);

    // Path relatif
    let rel = Path::new("src/main.rs");
    println!("Relatif: {}", rel.display());

    // Canonical path (resolve symlink, bersihkan ../)
    if let Ok(abs) = Path::new(".").canonicalize() {
        println!("CWD absolut: {}", abs.display());
    }
}

Environment Variables #

use std::env;

fn main() {
    // Ambil satu env var
    match env::var("HOME") {
        Ok(val) => println!("HOME = {}", val),
        Err(e) => println!("HOME tidak ada: {}", e),
    }

    // Dengan default value
    let port = env::var("PORT").unwrap_or_else(|_| "8080".to_string());
    let debug = env::var("DEBUG").map(|v| v == "true").unwrap_or(false);
    println!("Port: {}, Debug: {}", port, debug);

    // Cek semua env var
    println!("\nSemua env var (pertama 5):");
    for (kunci, nilai) in env::vars().take(5) {
        println!("  {} = {}", kunci, &nilai[..nilai.len().min(30)]);
    }

    // Set env var (hanya untuk proses saat ini dan child processes)
    env::set_var("APP_MODE", "production");
    println!("APP_MODE: {}", env::var("APP_MODE").unwrap());

    // Hapus env var
    env::remove_var("APP_MODE");

    // Argument baris perintah
    let args: Vec<String> = env::args().collect();
    println!("\nArgumen: {:?}", args);
    println!("Program: {}", args[0]);

    // Current working directory
    let cwd = env::current_dir().unwrap();
    println!("CWD: {}", cwd.display());

    // Direktori executable
    if let Ok(exe) = env::current_exe() {
        println!("Executable: {}", exe.display());
    }
}

std::process — Menjalankan Proses Anak #

use std::process::{Command, Stdio};
use std::io::{self, Write};

fn main() -> io::Result<()> {
    // Jalankan perintah sederhana
    let status = Command::new("ls")
        .arg("-la")
        .status()?;
    println!("Exit code: {}", status.code().unwrap_or(-1));

    // Tangkap output
    let output = Command::new("echo")
        .arg("Halo dari child process!")
        .output()?;

    println!("stdout: {}", String::from_utf8_lossy(&output.stdout));
    println!("stderr: {}", String::from_utf8_lossy(&output.stderr));
    println!("success: {}", output.status.success());

    // Dengan environment variable
    let output = Command::new("printenv")
        .arg("MY_VAR")
        .env("MY_VAR", "nilai-kustom")
        .output()?;
    println!("MY_VAR: {}", String::from_utf8_lossy(&output.stdout).trim());

    // Stdin ke child process
    let mut child = Command::new("cat")
        .stdin(Stdio::piped())
        .stdout(Stdio::piped())
        .spawn()?;

    if let Some(stdin) = child.stdin.take() {
        let mut stdin = stdin;
        stdin.write_all(b"Halo dari Rust!\n")?;
    }

    let output = child.wait_with_output()?;
    println!("cat output: {}", String::from_utf8_lossy(&output.stdout));

    // Keluar dengan kode tertentu
    // process::exit(0);  // keluar dengan kode 0 (sukses)

    Ok(())
}

Error Handling I/O #

use std::io::{self, ErrorKind};
use std::fs;

fn baca_atau_default(path: &str, default: &str) -> String {
    match fs::read_to_string(path) {
        Ok(isi) => isi,
        Err(e) if e.kind() == ErrorKind::NotFound => {
            // File tidak ada — gunakan default
            default.to_string()
        }
        Err(e) => {
            eprintln!("Error membaca {}: {}", path, e);
            default.to_string()
        }
    }
}

fn pastikan_direktori_ada(path: &str) -> io::Result<()> {
    match fs::create_dir(path) {
        Ok(_) => Ok(()),
        Err(e) if e.kind() == ErrorKind::AlreadyExists => Ok(()),  // sudah ada — OK
        Err(e) => Err(e),
    }
}

fn main() -> io::Result<()> {
    // ErrorKind untuk menangani jenis error berbeda
    let jenis_error = [
        ErrorKind::NotFound,
        ErrorKind::PermissionDenied,
        ErrorKind::AlreadyExists,
        ErrorKind::ConnectionRefused,
        ErrorKind::TimedOut,
        ErrorKind::UnexpectedEof,
        ErrorKind::WouldBlock,
    ];

    // Baca konfigurasi dengan fallback
    let config = baca_atau_default("config.toml", "[default]\nport = 8080\n");
    println!("Config:\n{}", config);

    // Buat direktori dengan idempoten
    pastikan_direktori_ada("logs")?;
    pastikan_direktori_ada("logs")?;  // tidak error meski sudah ada

    fs::remove_dir("logs").ok();

    Ok(())
}

Ringkasan #

  • Trait Read dan Write sebagai abstraksi universal — tulis fungsi yang menerima impl Read atau impl Write agar bisa bekerja dengan file, stdin, buffer memori, dan tipe lain tanpa modifikasi.
  • Selalu flush() setelah BufWriter — data yang belum di-flush bisa hilang jika program crash. Drop BufWriter memang flush otomatis tapi error-nya tidak bisa ditangkap.
  • fs::write dan fs::read_to_string untuk kasus sederhana — shortcut yang nyaman untuk file kecil yang tidak butuh streaming.
  • OpenOptions untuk kontrol penuh — kombinasi read, write, append, create, create_new, truncate untuk semua skenario pembukaan file.
  • SeekFrom::Start, SeekFrom::Current, SeekFrom::End — tiga mode posisi kursor; SeekFrom::Current(0) untuk mendapat posisi saat ini tanpa bergerak.
  • Gunakan PathBuf dan Path, bukan string untuk path — menangani separator / vs \ lintas platform otomatis, dan menyediakan API seperti parent(), extension(), join().
  • ErrorKind untuk penanganan error I/O yang granular — bedakan NotFound, PermissionDenied, AlreadyExists, TimedOut untuk respons yang tepat.
  • env::var selalu mengembalikan Result — gunakan .unwrap_or_else(|_| "default".into()) untuk nilai fallback atau ? untuk propagasi error.
  • Command::output() untuk tangkap outputCommand::status() hanya dapat kode keluar, Command::output() tangkap stdout dan stderr sekaligus.

← Sebelumnya: Strings   Berikutnya: Math →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact