programmer Rust

Rust言語で簡単なcatコマンドを実装してみた

2015年には1.0が公開される予定のRust言語の勉強のために、Unixコマンドを簡略化してRustで実装してみた。

すでに、uutils/coreutilsプロジェクトでcatコマンドは実装されていますが、今回はふつうのlinuxプログラミングという本のサンプルコードを参考にしました。

※ コードはここのレポジトリに公開するので、「もっとこう書けるよ」みたいなのがあれば、プルリクお願いします。

C言語でのcat

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

static void do_cat(const char *path);
static void die(const char *s);

int
main(int argc, char *argv[])
{
  int i;
  if (argc < 2) {
    fprintf(stderr, "%s: file name not given\n", argv[0]);
    exit(1);
  }
  for (i = 1; i < argc; i++) {
    do_cat(argv[i]);
  }
  exit(0);
}

#define BUFFER_SIZE 2048

static void
do_cat(const char *path)
{
  int fd;
  unsigned char buf[BUFFER_SIZE];
  int n;

  fd = open(path, O_RDONLY);
  if (fd < 0) die(path);
  for (;;) {
    n = read(fd, buf, sizeof buf);
    if (n < 0) die(path);
    if (n == 0) break;
    if (write(STDOUT_FILENO, buf, n) < 0) die(path);
  }
  if (close(fd) < 0) die(path);
}

static void die(const char *s)
{
  perror(s);
  exit(1);
}

オプションとかは実装せず、ファイルの内容を読み取り標準出力する。

Rust言語でのcat

実行環境

$ rustc --version
rustc 0.13.0-nightly (34d680009 2014-12-22 00:12:47 +0000)
#![allow(unused_must_use)]
use std::os;
use std::io::{File, IoResult, IoError, EndOfFile};
use std::io::stdio::{stdout_raw, stderr};

fn main() {
    let paths = os::args().slice_from_or_fail(&1).to_vec();
    let mut stderr = stderr();

    if paths.len() < 1 {
        stderr.write_str("file name not given\n");
    }
    for path in paths.iter() {
        let res = do_cat(path);
        if res.is_err() {
            panic!("{}: {}", path, res.unwrap_err());
        }
    }
}

const BUFFER_SIZE: uint = 2048;

fn do_cat(path: &String) -> IoResult<()> {
    let mut writer = stdout_raw();
    let mut in_buf = [0, .. BUFFER_SIZE];
    let mut reader = File::open(&std::path::Path::new(path));

    loop {
        let n = match reader.read(&mut in_buf) {
            Ok(n) if n == 0 => return Ok(()),
            Ok(n) => n,
            Err(IoError{ kind: EndOfFile, ..}) => return Ok(()),
            Err(e) => return Err(e)
        };
        try!(writer.write(in_buf.slice_to(n)));
    }
}

実際に書いてみたら、やっぱりfor文のカウント用変数とかを使う必要がなくなったので、コンパクトに書けるし可読性は増している。

配列をeachっぽく使える iter() とか、 match 使ってif文が減らせるから便利。

もっとシンプルに書けているプルリクを頂きました

2015/06/26更新 tos-kamiyaさんからもっと、シンプルに書けているプルリクを頂きました。上のコードはCを元にしてしまっているので、Rustの恩恵があまり受けられていませんが、こちらのコードはとてもRustっぽいですね。ありがとうございます。

use std::fs::File;
use std::io::{BufReader, Read, Write};
use std::io::stdout;
use std::env::args;

const BUFFER_SIZE: usize = 2048;

fn main() {
    let paths: Vec<String> = args().skip(1).collect();
    if paths.is_empty() {
        panic!("file name not given");
    }

    let mut writer = stdout();
    for path in paths {
        do_cat(&mut writer, &path);
    }
}

fn do_cat(writer: &mut Write, path: &str) {
    let file = File::open(path).unwrap();
    let mut reader = BufReader::new(&file);
    let mut buf = [0; BUFFER_SIZE];
    loop {
        let n = reader.read(&mut buf).unwrap();
        if n == 0 { break; }
        writer.write_all(&buf[..n]).unwrap();
    }
}

元のコードはこちらで確認できます

追記

2015/01/03 kui さんにプルリクもらいました。配列の最初だけ取り除く方法も

let paths = os::args().slice_from_or_fail(&1).to_vec();

って書けばよかったんですね。 ありがとうございます。

追記

2017/03/01 RustのLT会! Rust入門者の集い #2で発表した時 1.15.1 で動くようにしました。

#![allow(unused_must_use)]
use std::env;
use std::io::{stdout, stderr, Write, Read, Result};
use std::fs::File;

fn main() {
    let paths = env::args().skip(1);
    if paths.len() < 1 {
        writeln!(&mut stderr(), "file name not given\n");
    }
    for path in paths {
        let res = do_cat(path);
        if res.is_err() {
            panic!("{}", res.unwrap_err());
        }
    }
}

const BUFFER_SIZE: usize = 2048;

fn do_cat(path: String) -> Result<()> {
    let stdout = stdout();
    let mut handle = stdout.lock();
    let mut in_buf = [0; BUFFER_SIZE];
    let mut reader = try!(File::open(&std::path::Path::new(&path)));

    loop {
        let n = match reader.read(&mut in_buf[..]) {
            Ok(n) if n == 0 => return Ok(()),
            Ok(n) => n,
            Err(e) => return Err(e)
        };
        try!(handle.write(&in_buf[0..n]));
    }
}