blob: fcc78590917d0513e7ca98063700c0ca525e5405 [file] [log] [blame]
//! A support crate for [_The Rust Programming Language_][trpl].
//!
//! [trpl]: https://doc.rust-lang.org/book
//!
//! This crate mostly just re-exports items from *other* crates. It exists for
//! two main reasons:
//!
//! 1. So that as you read along in _The Rust Programming Language_, you can
//! add just one dependency, rather than however many we end up with, and
//! likewise use only one set of imports.
//!
//! 2. So that we can more easily guarantee it keeps building and working. Since
//! we control the contents of this crate and when it changes, readers will
//! never be broken by upstream changes, e.g. if Tokio does a breaking 2.0
//! release at some point.
// For direct use within the `trpl` crate, *not* re-exported.
use std::{future::Future, pin::pin};
use futures::future;
// Re-exports, to be used like `trpl::join`.
pub use futures::{
future::{Either, join, join_all, join3},
join,
};
pub use tokio::{
fs::read_to_string,
runtime::Runtime,
// We use the `unbounded` variants because they most closely match the APIs
// from `std::sync::mpsc::channel`. Tokio's API choices are interesting:
//
// | `tokio::sync::mpsc` | `std::sync::mpsc` |
// | ------------------- | ----------------- |
// | `channel` | `sync_channel` |
// | `unbounded_channel` | `channel` |
//
// The book collapses these differences for pedagogical simplicity, so that
// readers are not asking why `unbounded` is now important and can focus on
// the more important differences between sync and async APIs.
sync::mpsc::{
UnboundedReceiver as Receiver, UnboundedSender as Sender,
unbounded_channel as channel,
},
task::{JoinHandle, spawn as spawn_task, yield_now},
time::{interval, sleep},
};
pub use tokio_stream::{
Stream, StreamExt, iter as stream_from_iter,
wrappers::{IntervalStream, UnboundedReceiverStream as ReceiverStream},
};
/// Run a single future to completion on a bespoke Tokio `Runtime`.
///
/// Every time you call this, a new instance of `tokio::runtime::Runtime` will
/// be created (see the implementation for details: it is trivial). This is:
///
/// - Reasonable for teaching purposes, in that you do not generally need to set
/// up more than one runtime anyway, and especially do not in basic code like
/// we are showing!
///
/// - Not *that* far off from what Tokio itself does under the hood in its own
/// `tokio::main` macro for supporting `async fn main`.
pub fn run<F: Future>(future: F) -> F::Output {
let rt = Runtime::new().unwrap();
rt.block_on(future)
}
/// Run two futures, taking whichever finishes first and canceling the other.
///
/// Notice that this is built on [`futures::future::select`], which has the
/// same overall semantics but does *not* drop the slower future. The idea there
/// is that you can work with the first result and then later *also* continue
/// waiting for the second future.
///
/// We use the `race` semantics, where the slower future is simply dropped, for
/// the sake of simplicity in the examples: no need to deal with the tuple and
/// intentionally ignore the second future this way!
///
/// Note that this only works as “simply” as it does because:
///
/// - It takes ownership of the futures.
/// - It internally *pins* the futures.
/// - It throws away (rather than returning) the unused future (which is why it
/// can get away with pinning them).
pub async fn race<A, B, F1, F2>(f1: F1, f2: F2) -> Either<A, B>
where
F1: Future<Output = A>,
F2: Future<Output = B>,
{
let f1 = pin!(f1);
let f2 = pin!(f2);
match future::select(f1, f2).await {
Either::Left((a, _f2)) => Either::Left(a),
Either::Right((b, _f1)) => Either::Right(b),
}
}
/// Fetch data from a URL. For more convenient use in _The Rust Programming
/// Language_, panics instead of returning a [`Result`] if the request fails.
pub async fn get(url: &str) -> Response {
Response(reqwest::get(url).await.unwrap())
}
/// A thin wrapper around [`reqwest::Response`] to make the demos in _The Rust
/// Programming Language_ substantially nicer to use.
pub struct Response(reqwest::Response);
impl Response {
/// Get the full response text.
///
/// If the response cannot be deserialized, this panics instead of returning
/// a [`Result`] (for convenience in the demo).
pub async fn text(self) -> String {
self.0.text().await.unwrap()
}
}
/// A thin wrapper around [`scraper::Html`] to make the demos in _The Rust
/// Programming Language_ substantially nicer to use.
pub struct Html {
inner: scraper::Html,
}
impl Html {
/// Parse an HTML document from a string.
///
/// This is just a thin wrapper around `scraper::Html::parse_document` to
/// keep the exported API surface simpler.
pub fn parse(source: &str) -> Html {
Html {
inner: scraper::Html::parse_document(source),
}
}
/// Get the first item in the document matching a string selector. Returns
/// Some()
///
/// If the selector is not a valid CSS selector, panics rather than
/// returning a [`Result`] for convenience.
pub fn select_first<'a>(
&'a self,
selector: &'a str,
) -> Option<scraper::ElementRef<'a>> {
let selector = scraper::Selector::parse(selector).unwrap();
self.inner.select(&selector).nth(0)
}
}