Rusty Weather: My first Rust App
Rusty Weather: My first Rust App
I have wanted to learn a new language and framework, as well as I, know C# and .NET for many years. I have dabbled in several different ones, but the only one that I seemed to come back to continually was Python. After trying all kinds of languages over the last couple years, I decided I would give Rust a try. I have only been a consumer of garbage collected runtimes, so I was a bit hesitant to give it a try. But I am finding that I have enough knowledge now that I am not intimidated by it. The safety of the language prevents me from getting myself in trouble, and I like the features that are from a more functional paradigm. It is also cross-platform and works equally well on Windows as it does Linux/macOS which is impressive. I will also go out on the limb and say that Cargo is one of the best package management/build tools that I have used.
When I am learning, I like to build applications that do things that I have found fun, and making this app was no exception. I adopted the idea from this Python app that I did as part of the Python Jumpstart by building 10 Apps course from TalkPython. You can check out my entire repo here, but I will provide my first pass and my final pass.
Here is my first pass at the app. I used unwrap pretty extensively, which is skipping out on some of the safety and a tuple for my return type. However, it functions just like the Python version.
use std::io;
use std::format;
use scraper::{Html, Selector};
fn main() {
print_header();
println!("What zipcode do you want the weather for (97201)?");
let mut code = String::new();
io::stdin().read_line(&mut code).expect("Cannot read the input.");
let html = get_html_from_web(&code);
let report = get_weather_from_html(&html);
println!("The temp in {} is {} {} and {}.", report.0, report.1, report.2, report.3)
}
fn print_header() {
println!("---------------------------------");
println!(" WEATHER APP");
println!("---------------------------------");
}
fn get_html_from_web(zipcode: &str) -> String {
let url = format!("http://www.wunderground.com/weather-forecast/{}", zipcode);
let mut resp = reqwest::get(&url).unwrap();
resp.text().unwrap()
}
fn get_weather_from_html(html: &str) -> (String, String, String, String) {
let document = Html::parse_document(html);
let loc_selector = Selector::parse(".city-header > h1:nth-child(2) > span:nth-child(1)").unwrap();
let condition_selector = Selector::parse(".condition-icon > p:nth-child(2)").unwrap();
let temp_selector = Selector::parse(".current-temp > lib-display-unit:nth-child(1) > span:nth-child(1) > span:nth-child(1)").unwrap();
let scale_selector = Selector::parse(".current-temp > lib-display-unit:nth-child(1) > span:nth-child(1) > span:nth-child(2) > span:nth-child(1)").unwrap();
let loc = document.select(&loc_selector).last().unwrap().inner_html();
let condition = document.select(&condition_selector).last().unwrap().inner_html();
let temp = document.select(&temp_selector).last().unwrap().inner_html();
let scale = document.select(&scale_selector).last().unwrap().inner_html();
println!("{}", loc);
println!("{}", condition);
println!("{}", temp);
println!("{}", scale);
(loc, condition, temp, scale)
}
I decided this wasn’t good enough and didn’t see to be what a Rustacean would have created, so I went back to coding. I think this example is a little better. I still have some usage of unwrap, however in this situation if the CSS selectors error then I want it to panic.
use std::io;
use std::format;
use reqwest::Error;
use scraper::{Html, Selector};
struct Report {
loc: String,
temp: String,
scale: String,
condition: String
}
fn main() {
print_header();
println!("What zipcode do you want the weather for (97201)?");
let mut code = String::new();
io::stdin().read_line(&mut code).expect("Cannot read the input.");
let weather = match process_zipcode(&code) {
Ok(report) => report,
_ => String::from("An error occured while processing.")
};
println!("{}", weather);
}
fn process_zipcode(input: &str) -> Result<String, Error> {
let html = get_html_from_web(&input)?;
let formatted = match get_weather_from_html(&html) {
Some(report) => format!("The temp in {} is {} {} and {}.", report.loc, report.temp, report.scale, report.condition),
None => String::from("An error occured while parsing the weather report.")
};
Ok(formatted)
}
fn print_header() {
println!("---------------------------------");
println!(" WEATHER APP");
println!("---------------------------------");
}
fn get_html_from_web(zipcode: &str) -> Result<String, Error> {
let url = format!("http://www.wunderground.com/weather-forecast/{}", zipcode);
let mut resp = reqwest::get(&url)?;
resp.text()
}
fn get_weather_from_html(html: &str) -> Option<Report> {
let document = Html::parse_document(html);
let loc_selector = Selector::parse(".city-header > h1:nth-child(2) > span:nth-child(1)").unwrap();
let condition_selector = Selector::parse(".condition-icon > p:nth-child(2)").unwrap();
let temp_selector = Selector::parse(".current-temp > lib-display-unit:nth-child(1) > span:nth-child(1) > span:nth-child(1)").unwrap();
let scale_selector = Selector::parse(".current-temp > lib-display-unit:nth-child(1) > span:nth-child(1) > span:nth-child(2) > span:nth-child(1)").unwrap();
let loc = document.select(&loc_selector).last()?.inner_html();
let condition = document.select(&condition_selector).last()?.inner_html();
let temp = document.select(&temp_selector).last()?.inner_html();
let scale = document.select(&scale_selector).last()?.inner_html();
Some(Report { loc: loc, temp: temp, scale: scale, condition: condition})
}
I have already started writing a few more apps in Rust; two of these apps are personal projects that I have started in other languages. I plan to port this to Rust and get these released. After completion, I think I will have a good feel for the language and ecosystem. So far I can report that for me, it is everything you hear people say about it. It’s easy, powerful, and has a great ecosystem.
Thanks for reading,
Jamie
If you enjoy the content, then consider buying me a coffee.