Rust recursion pattern in simple cat program
Where clauses go on separate lines; easier to see them as they change the functionality so much.
Move the actual argument skipping near to the comment.
There's no need to call eq; use == instead.
String::as_str can often be avoided, just take a reference via &amp;.
The code always builds the failure string, even when it doesn't fail! To avoid this, use unwrap_or_else, which allows access to the specific error as a closure argument.
Similarly, the code always gets Path::display, even when there is no failure.
There's no need to get the display anyway, the code already has the user-provided arg.
There's no need for Path at all; strings are convertable to OsStr, which is what File::open needs.
The name argument and the Method enum are always correlated one-to-one and they should be combined.
The error text in cat_recursive is incorrect; the file / stdin are already open, this error denotes failure to read from it.
Remove useless comments ("recurse"). As an aside, it was pointed out to me many years ago that the verb form is "recur", and an algorithm is "recursive"; "recurse" isn't really a word.
If you are going to document a function, use Rustdoc formatting.
If you are going to document an argument that is a standalone type, consider documenting the type instead. This is one reason to have types at all - to has a package of related meaning.


use std::{env, io};
use std::fs::File;
use std::io::BufReader;
use std::io::prelude::*;

const STDIN: &amp;'static str = "-";
const BUFFER_SIZE: usize = 4096;

/// Read line-by-line or to read the entire input
enum Method&lt;'a&gt; {
    File(&amp;'a str),
    Line,
}

impl&lt;'a&gt; Method&lt;'a&gt; {
    fn name(&amp;self) -&gt; &amp;str {
        match *self {
            Method::File(name) =&gt; name,
            Method::Line =&gt; "stdin",
        }
    }
}

fn main() {
    // skip the first element as it is the program name
    let args = env::args().skip(1);
    if args.len() == 0 {
        cat(io::stdin(), Method::Line);
    } else {
        for arg in args {
            if arg == STDIN {
                cat(io::stdin(), Method::Line);
            } else {
                let file = File::open(&amp;arg)
                    .unwrap_or_else(|e| panic!("cat: failed to open {}: {}", &amp;arg, e));
                cat(file, Method::File(&amp;arg));
            }
        }
    }
    io::stdout().flush().unwrap();
}

/// Read from `input` and print to stdout.
fn cat&lt;R&gt;(input: R, m: Method)
    where R: Read,
{
    let mut reader = BufReader::with_capacity(BUFFER_SIZE, input);
    cat_recursive(&amp;mut reader, m);
}

fn cat_recursive&lt;R&gt;(reader: &amp;mut BufReader&lt;R&gt;, m: Method)
    where R: Read,
{
    let mut s = String::new();
    let n = match m {
        Method::Line =&gt; reader.read_line(&amp;mut s),
        Method::File(..) =&gt; reader.read_to_string(&amp;mut s),
    };
    let n = n.unwrap_or_else(|e| panic!("cat: failed to read {}: {}", m.name(), e));
    if n &gt; 0 {
        print!("{}", s);
        cat_recursive(reader, m);
    }
}


The big points


The algorithm isn't really recursive for files. The entire file is read in via read_to_string. This means that memory is allocated to hold the entire file. Then we recur once to read nothing and end.
When reading line-by-line, the String is reallocated for every call. This will increase memory usage with each call, up to the last call, at which point we will have allocated the entire input size again.
Which leads to the important point: Rust doesn't have tail recursion! Writing an unbounded recursive function is eventually going to end in tears for someone.
Additionally, this cat doesn't support binary files, as it crams everything into a String.
I'm not sure why there are two methods of reading, one for files and one for stdin. Why not always read line-by-line or as one big blob?