use std::env;
use std::fs::File;
use std::io::{self, Read};
use std::path::Path;
fn read_utf8_file<P: AsRef<Path>>(path: P) -> io::Result<String> {
let mut f = try!(File::open(path));
let mut s = String::new();
try!(f.read_to_string(&mut s));
Ok(s)
}
fn main() {
let file_name = match env::args_os().nth(1) {
Some(n) => {
match n.into_string() {
Ok(n) => n,
Err(_) => {
println!("File name is not valid UTF-8.");
return;
}
}
}
None => {
let exe_opt = env::current_exe().ok();
let exe = exe_opt.and_then(|p| p.file_name().map(|p| p.to_string_lossy().into_owned()));
println!("Usage: {} <file>", exe.unwrap_or("<binary>".into()));
return;
}
};
let content = match read_utf8_file(&file_name) {
Ok(c) => c,
Err(e) => {
println!("Error: {}", e);
return;
}
};
println!("{}", content);
}
I tried what you suggested and it seems easy enough, though that’s likely because there’s a read_to_string method on Read.
The unfortunate thing is how much code is required to have the main function not panic, while providing fairly standard error messages.
If you don’t use read_to_string, then you get this without doing conversions between errors (which is how read_to_string is implemented:
fn read_utf8_file<P: AsRef<Path>>(path: P) -> io::Result<String> {
let mut f = try!(File::open(path));
let mut v = Vec::new();
try!(f.read_to_end(&mut v));
match String::from_utf8(v) {
Ok(s) => Ok(s),
Err(_) => Err(io::Error::new(io::ErrorKind::InvalidData, "file did not contain valid UTF-8")),
}
}
If you want to use try! instead of a match, then you get:
use std::env;
use std::error;
use std::fmt;
use std::fs::File;
use std::io::{self, Read};
use std::path::Path;
use std::string::FromUtf8Error;
#[derive(Debug)]
enum Utf8FileError {
IoError(io::Error),
Utf8Error(FromUtf8Error),
}
impl fmt::Display for Utf8FileError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
&Utf8FileError::IoError(ref e) => e.fmt(f),
&Utf8FileError::Utf8Error(ref e) => e.fmt(f),
}
}
}
impl error::Error for Utf8FileError {
fn description(&self) -> &str {
match self {
&Utf8FileError::IoError(ref e) => e.description(),
&Utf8FileError::Utf8Error(ref e) => e.description(),
}
}
fn cause(&self) -> Option<&error::Error> {
match self {
&Utf8FileError::IoError(ref e) => e.cause(),
&Utf8FileError::Utf8Error(ref e) => e.cause(),
}
}
}
impl From<io::Error> for Utf8FileError {
fn from(e: io::Error) -> Self {
Utf8FileError::IoError(e)
}
}
impl From<FromUtf8Error> for Utf8FileError {
fn from(e: FromUtf8Error) -> Self {
Utf8FileError::Utf8Error(e)
}
}
fn read_utf8_file<P: AsRef<Path>>(path: P) -> Result<String, Utf8FileError> {
let mut f = try!(File::open(path));
let mut v = Vec::new();
try!(f.read_to_end(&mut v));
Ok(try!(String::from_utf8(v)))
}
Which, I agree is a bit much for a newbie. You don’t have to impl Error in this case, but if you’re going to go this far with the From conversions, might as well do Error too.