4
\$\begingroup\$

I've created a small tool that fetches and stores the newest self-posts from a specific subreddit. The posts are stored in a redis store.

I'm going to write another couple of applications at a future date that will read from the redis store. I will want to be able to pick from the stored posts at random.

This is one of my first forays into writing something that's useful to me in rust, so I'd love some feedback.

#[macro_use]
extern crate serde_derive;

extern crate reqwest;
extern crate serde;
extern crate serde_json;
extern crate redis;

use redis::PipelineCommands;

#[derive(Deserialize)]
struct RedditResponse {
    data: Data,
}

#[derive(Deserialize)]
struct Data {
    children: Vec<Post>,
}

#[derive(Deserialize)]
struct Post {
    data: PostData,
}

#[derive(Deserialize)]
struct PostData {
    id: String,
    is_self: bool,
    title: String,
}

fn main() {
    let self_posts = match get_posts(20) {
        Ok(v) => v,
        Err(e) => panic!("Failed to fetch new posts: {:?}", e),
    };

    let redis_client = match redis::Client::open("redis://127.0.0.1") {
        Ok(c) => c,
        Err(e) => panic!("Unable to create the store client: {:?}", e),
    };

    let redis_conn = match redis_client.get_connection() {
        Ok(c) => c,
        Err(e) => panic!("Unable to retrieve the connection to the store: {:?}", e),
    };

    match add_posts_to_store(redis_conn, self_posts) {
        Ok(n) => println!("Stored {} posts.", n),
        Err(e) => panic!("Could not store posts: {:?}", e),
    };
}

fn get_posts(num: usize) -> reqwest::Result<Vec<Post>> {
    let url = format!("https://www.reddit.com/r/dota2/new.json?sort=new&limit={}", num);
    let posts: RedditResponse = reqwest::get(&url)?.json()?;

    Ok(
        posts.data.children
            .into_iter()
            .filter(|p| p.data.is_self)
            .collect::<Vec<Post>>()
    )
}

fn add_posts_to_store(store: redis::Connection, posts: Vec<Post>) -> redis::RedisResult<usize> {
    let mut pipe = redis::pipe();

    for p in &posts {
        pipe.set(&p.data.id, &p.data.title).ignore();
    }

    pipe.query(&store)?;

    Ok(posts.len())
}
\$\endgroup\$
3
\$\begingroup\$

The pattern

match some_result {
    Ok(v)  => v,
    Err(e) => panic!("message: {:?}", e),
}

is exactly what expect does for you. We can get a somewhat leaner main if we use that instead:

fn main() {
    let self_posts = get_posts(20).expect("Failed to fetch new posts");

    let redis_client =
        redis::Client::open("redis://127.0.0.1").expect("Unable to create the store client");

    let redis_conn = redis_client
        .get_connection()
        .expect("Unable to retrieve the connection to the store");

    match add_posts_to_store(redis_conn, self_posts) {
        Ok(n) => println!("Stored {} posts.", n),
        Err(e) => panic!("Could not store posts: {:?}", e),
    };
}

Somewhat, because the error messages lead to long lines and rustfmt doesn't like that.

However, keep in mind that while panic is great for developers, it's a system that should be used for unrecoverable errors, bugs that you cannot handle or anticipate. When you panic!, you generate an error message that's helpful for programmers, but not for users:

thread 'main' panicked at 'Unable to retrieve the connection to the store: .......', src/main.rs:.....
note: Run with `RUST_BACKTRACE=1` for a backtrace.

Instead, stderr should get used. The Rust Book contains a section for error handling in its example I/O project which you can use for inspiration. But if you never intend to distribute your reddit/redis updater, feel free to use panic! or .expect("...").

Other than that, the turbofish syntax at collect::<Vec<Post>>() isn't necessary, since its inferred by the return type. Also, you can traverse p in posts instead of &p in &posts since you don't want to use the Vec afterwards, you just have to save the posts.len() beforehand.

However, you probably want to use store : &redis::Connection in add_posts_to_store if you want to reuse the connection. Also, you usually want to derive some of the standard traits, for example Debug. Again, that depends on whether you will use your structs in another context, but if you ever publish your package it's very recommended to derive all possible standard traits (Debug, PartialEq, Eq, PartialOrd, Ord, Clone and maybe Hash in your case).

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.