Randomly selecting an adjective and noun, combining them into a message
Everything looks pretty straight-forward, there are just some idiomatic changes to be made.


The function documentation, especially the argument and return value documentation, is mostly useless. In a statically typed language, the types provide a large amount of implicit documentation. Variable and method names tell another large chunk. Don't repeat all that compiler-checked documentation in your prose. Maybe even think about introducing new types for documentation purposes!
The type is also encoded in the name in certain places, which risks being redundant. — file_path: &amp;Path.
Instead of requiring &amp;Path, use AsRef&lt;Path&gt; to accept more types and improve the ergonomics at the call site.
It's inefficient to evaluate the path's display before it's needed. Since it's only used for error cases, only evaluate it in error cases. This is another way of saying to scope variables as tightly as possible.
There's no reason to create a variable to return it in the next statement; just return it directly — match {...} instead of let foo = match {...}; foo.
Call trait methods directly when possible, instead of using the Universal Function Call Syntax (UFCS) — foo.description() instead of Error::description(&amp;foo).
read_to_string doesn't return an Ok(content), it returns the number of bytes read. This variable name is misleading.
If only one branch of a match statement is used, it's better to use an if let statement.
Functions shouldn't take a String if they just reading from it, it's better to take a &amp;str as it is more accepting.
Functions shouldn't take a Vec&lt;T&gt; if they just reading from it, it's better to take a &amp;[T] as it is more accepting.
Use expect instead on unwrap as it will help you find logic errors much easier when they happen. They may also help end users figure out problems on their own.
There's no need to specify the item type when collecting into a collection like a vector. You can use the placeholder _ instead — Vec&lt;_&gt;.
selfcare is confusingly named.
It's better to get the random number generator just once and reuse it.
When writing the sentence to the output, the code doesn't care if it's a string, just that it's printable. Accept a generic type that is printable instead.




extern crate rand;

use rand::Rng;
use std::env;
use std::error::Error;
use std::fs::File;
use std::io::prelude::*;
use std::path::Path;

fn read_lines&lt;P&gt;(file_path: P) -&gt; Vec&lt;String&gt;
    where P: AsRef&lt;Path&gt;
{
    let content = read_file(file_path);
    split_lines(&amp;content)
}

fn open_file&lt;P&gt;(file_path: P) -&gt; File
    where P: AsRef&lt;Path&gt;
{
    let file_path = file_path.as_ref();
    match File::open(file_path) {
        Err(why) =&gt; panic!("Couldn't open file {}: {}", file_path.display(), why.description()),
        Ok(file) =&gt; file,
    }
}

fn read_file&lt;P&gt;(file_path: P) -&gt; String
    where P: AsRef&lt;Path&gt;
{
    let file_path = file_path.as_ref();
    let mut file = open_file(file_path);
    let mut content = String::new();
    if let Err(why) = file.read_to_string(&amp;mut content) {
        panic!("Couldn't read file {}: {}", file_path.display(), why.description());
    }
    content
}

fn split_lines(string: &amp;str) -&gt; Vec&lt;String&gt; {
    string
        .lines()
        .map(ToOwned::to_owned) // Nice
        .collect()
}

/// Chooses random adjective and noun from file, combines into message.
///
/// This function does not return the message, but prints it to stdout.
/// The message is in the format "You are a &lt;adjective&gt; &lt;noun&gt;."
fn selfcare&lt;S&gt;(adj: &amp;[S], nouns: &amp;[S])
    where S: std::fmt::Display
{
    let mut rng = rand::thread_rng();
    let adjective = rng.choose(adj).expect("No adjective found");
    let noun = rng.choose(nouns).expect("No noun found");
    println!("You are a {} {}", adjective, noun)
}

/// Print random inspiring message in format "You are a &lt;adjective&gt; &lt;noun&gt;".
///
/// Takes command line args `adj_file` and `noun_file`, respectively the paths
/// to the file containing the adjectives and the containing the nouns.
fn main() {
    let mut args: Vec&lt;_&gt; = env::args().collect();
    if args.len() == 1 {
        args.push("adjectives.txt".to_string());
        args.push("nouns.txt".to_string());
    }
    let adj = read_lines(&amp;args[1]);
    let nouns = read_lines(&amp;args[2]);
    selfcare(&amp;adj, &amp;nouns);
}




I'd also probably roll all the file stuff into one method:

fn read_lines&lt;P&gt;(file_path: P) -&gt; Vec&lt;String&gt;
    where P: AsRef&lt;Path&gt;
{
    let file_path = file_path.as_ref();

    let file = match File::open(file_path) {
        Err(why) =&gt; panic!("Couldn't open file {}: {}", file_path.display(), why.description()),
        Ok(file) =&gt; file,
    };

    let buf = BufReader::new(file);

    buf.lines().map(|line| {
        match line {
            Ok(l) =&gt; l,
            Err(why) =&gt; panic!("Couldn't read file {}: {}", file_path.display(), why.description()),
        }
    }).collect()
}


Note that this also allows me to use BufRead::lines, which is probably more efficient than reading the entire file into memory and then splitting it.



I'm also not the biggest fan of burying print statements in my code. You even have comments warning the caller about that! Instead, check out this exciting change:

/// Chooses a random adjective and noun.
fn selfcare&lt;A, N, F, T&gt;(adj: &amp;[A], nouns: &amp;[N], f: F) -&gt; T
    where F: FnOnce(&amp;A, &amp;N) -&gt; T
{
    let mut rng = rand::thread_rng();
    let adjective = rng.choose(adj).expect("No adjective found");
    let noun = rng.choose(nouns).expect("No noun found");
    f(adjective, noun)
}

// ...

selfcare(&amp;adj, &amp;nouns, |adjective, noun| println!("You are a {} {}", adjective, noun));


This pushes the printing to the outermost layers of your program, where it belongs :-) It also opens a new name for selfcare - random_adjective_and_noun, thus allowing you to delete the comment. Note that the requirement for being able to print the value disappears, and I took the opportunity to allow the adjective and noun to be different types (which is a bit of YAGNI).



Thinking about your main method, it seems a little strange to push into the args vector to provide default values. Instead, I might combine Vec::get and Option::map_or to provide defaults:

let args: Vec&lt;_&gt; = env::args().collect();
let adj_filename = args.get(1).map_or("adjectives.txt", |f| &amp;**f);
let noun_filename = args.get(2).map_or("nouns.txt", |f| &amp;**f);
let adj = read_lines(adj_filename);
let nouns = read_lines(noun_filename);


This has the benefit of not needing to allocate the fallback filenames, and it handles the case of providing the first filename and not the second. In fact, you don't have to even collect the arguments anymore, you could just use the args iterator directly. However, putting them into a vector is pretty standard.