imports
Your imports should be compressed:
use std::io::{self, Read, Write};
Although it might be better to use the io
prelude:
use std::io;
use std::io::prelude::*;
I prefer the former for its explicitness.
match
vs if let
match io::stdin().read_line(&mut s) {
Ok(_) => {},
Err(err) => {
println_stderr!("{}", err.to_string());
continue;
}
};
is better as just
if let Err(err) = io::stdin().read_line(&mut s) {
println_stderr!("{}", err);
continue;
};
The same occurs later, and in the macro.
prefer late initialization
You should only initialize s
just before use in the second branch.
copy-paste error?
Your continue
when reading from stdin
is a bit optimistic. I'd suggest just exiting at that point - if reading one line fails then reading the rest probably will too.
plz no Unicode
You read and write strings holding the whole file (or line) - cat
should work instead on raw bytes, and preferrably in large chunks too. The most obvious way is something like
fn redirect_stream<R, W>(reader: &mut R, writer: &mut W, buffer: &mut Vec<u8>) -> io::Result<()>
where R: Read, W: Write
{
let mut buffer = vec![0; 64 * 1024];
loop {
let len_read = try!(reader.read(&mut buffer));
if len_read == 0 {
return Ok(())
}
try!(writer.write_all(&buffer[..len_read]));
}
}
plz no allocate
Using the above means we're stuck allocating 64k for each stream, even if there are many streams or the stream is line-buffered.
The first can be solved by passing the buffer into the function. The later can be solved by resizing the buffer up to some hard limit from a small size.
DRY
Note that there's still duplication between the args.len() < 2
and else
branches. One could solve this by writing a wrapping function to extract this functionality.
fn handle_arg<R, W>(reader: &mut R, writer: &mut W, buffer: &mut Vec<u8>)
where R: Read, W: Write
{
if let Err(err) = redirect_stream(reader, writer, buffer) {
println_stderr!("{}", err.to_string());
}
}
Making handle_arg
a closure would be prettier but require dynamic dispatch. That's probably fine, but maybe less idiomatic:
let stdout = &mut io::stdout();
let buffer = &mut vec![0; SMALL_BUFFER_SIZE];
let mut handle_arg = move |mut reader: &mut Read| {
if let Err(err) = redirect_stream(&mut reader, stdout, buffer) {
println_stderr!("{}", err.to_string());
}
};
-
means stdin?
This is more reasonable if we want to allow the code to accept -
to mean standard input, as we can do
let mut args: Vec<_> = env::args().skip(1).collect();
if args.is_empty() {
args.push("-".into());
}
let stdout = &mut io::stdout();
let buffer = &mut vec![0; SMALL_BUFFER_SIZE];
for arg in args {
if arg == "-" {
handle_arg(&mut io::stdin(), stdout, buffer);
continue;
}
match File::open(arg) {
Ok(ref mut file) => {
handle_arg(file, stdout, buffer)
},
Err(err) => {
println_stderr!("{}", err);
continue;
}
}
}
Result
use std::env;
use std::io::{self, Read, Write};
use std::iter;
use std::fs::File;
const SMALL_BUFFER_SIZE: usize = 256;
const LARGE_BUFFER_SIZE: usize = 64 * 1024;
macro_rules! println_stderr(
($($arg:tt)*) => (
if let Err(err) = writeln!(&mut io::stderr(), $($arg)* ) {
panic!("Unable to write to stderr: {}", err);
}
)
);
fn redirect_stream<R, W>(reader: &mut R, writer: &mut W, buffer: &mut Vec<u8>) -> io::Result<()>
where R: Read, W: Write
{
loop {
let len_read = try!(reader.read(buffer));
if len_read == 0 {
return Ok(())
}
try!(writer.write_all(&buffer[..len_read]));
if len_read == buffer.len() && len_read < LARGE_BUFFER_SIZE {
buffer.extend(iter::repeat(0).take(len_read));
}
}
}
fn main() {
let mut args: Vec<_> = env::args().skip(1).collect();
if args.is_empty() {
args.push("-".into());
}
fn handle_arg<R, W>(reader: &mut R, writer: &mut W, buffer: &mut Vec<u8>)
where R: Read, W: Write
{
if let Err(err) = redirect_stream(reader, writer, buffer) {
println_stderr!("{}", err.to_string());
}
}
let stdout = &mut io::stdout();
let buffer = &mut vec![0; SMALL_BUFFER_SIZE];
for arg in args {
if arg == "-" {
handle_arg(&mut io::stdin(), stdout, buffer);
continue;
}
match File::open(arg) {
Ok(ref mut file) => {
handle_arg(file, stdout, buffer)
},
Err(err) => {
println_stderr!("{}", err);
continue;
}
}
}
}