wtfm_rs/lib.rs
1#![doc(html_playground_url = "https://play.rust-lang.org/")]
2//! # Introduction
3//!
4//! WTFM is <a href="https://en.wikipedia.org/wiki/RTFM">RTFM</a> in Rust.
5//!
6//! To master an ecosystem as broad and deep as Rust in a short period of time,
7//! we might want to turn R into W, dive deep by writing our own
8//! [doctests](https://doc.rust-lang.org/rustdoc/write-documentation/documentation-tests.html) iteratively until we find the solution to our problem.
9//!
10//! ```
11//! assert_eq!("WTFM", "RTFM".replace("R", "W"));
12//! ```
13//! Doctests can be good "paper trails" of our thought process.
14//!
15//! We can also learn about the throught process of other crates by "reviewing"
16//! them with our own doctests.
17//!
18//! # Professional rustdoc rabbit holes
19//! Be prepared to fall into rabbit holes of rustdoc and get out
20//! where and when you need to. This is a skill to be learned.
21//! We are programmers, not librarians.
22//!
23//! <https://rust-lang.github.io/api-guidelines/documentation.html>
24//!
25//! # Doctest
26//! If `cargo test --doc` passed, we will have have output like
27//! ```text
28//! all doctests ran in 0.70s; merged doctests compilation took 0.33s
29//! ```
30//! There is no need to check each one of them in playground but we can explore
31//! further by tinkering with them in the plalyground.
32//! (That is why we call it playground.)
33//!
34//! Since doctests are merged and compiled into one binary, we want to make sure
35//! they don't interfere with each other. Since doctests are not the real part
36//! of the code, we can have cleanup code in the libray body or unit test body.
37//! We can see how the source code progress this way.
38//!
39//! # [assert!] driven development
40//! Be prepared to [RTFM](https://doc.rust-lang.org/rustdoc/write-documentation/documentation-tests.html) [std].
41//!
42//! Let's start with a trivial rust program using
43//! macro [assert!] and primitive [true].
44//! ```
45//! assert!(true);
46//! ```
47//!
48//! We can wrap it with a function `assert_true_v1` and call it in the
49//! same doctest block:
50//! ```
51//! fn assert_true_v1() {
52//! assert!(true);
53//! }
54//! assert_true_v1();
55//! ```
56//! To make it a little be more complicated we can wrap [true] with
57//! a function as well.
58//! ```
59//! fn return_true() -> bool { true }
60//! assert!(return_true());
61//! ```
62//! The version 2 of `asser_true` can be
63//!
64//! ```compile_fail
65//! fn assert_true_v2() {
66//! assert!(return_true());
67//! }
68//! ```
69//! The reason the doctest above failed is `return_true` is out of scope.
70//!
71//! Let's put function [return_true] in the crate,
72//! following works in `cargo test --doc` but
73//! would fail in playground because playground can't resolve our crate.
74//! ```
75//! use wtfm_rs::return_true;
76//! fn assert_true_v2() {
77//! assert!(return_true());
78//! }
79//! ```
80//! We can add similar test to the unit test of this crate (see the source code)
81//! `cargo test` will capture all of them.
82//! ```text
83//! cargo test
84//! Finished `test` profile [unoptimized + debuginfo] target(s) in 0.00s
85//! Running unittests src/lib.rs (target/debug/deps/wtfm_rs-ee0d1b96bcb289bd)
86//!
87//! running 1 test
88//! test assert_true_v3 ... ok
89//!
90//! test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
91//!
92//! Doc-tests wtfm_rs
93//!
94//! running 4 tests
95//! test src/lib.rs - (line 51) ... ok
96//! test src/lib.rs - (line 73) ... ok
97//! test src/lib.rs - (line 58) ... ok
98//! test src/lib.rs - (line 11) ... ok
99//!
100//! test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
101//!
102//!
103//! running 1 test
104//! test src/lib.rs - (line 64) - compile fail ... ok
105//!
106//! test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.03s
107//!
108//! all doctests ran in 0.45s; merged doctests compilation took 0.21s
109//! ```
110//!
111//! # [println!] driven development
112//! If we add [println!] to the doctest
113//! ```
114//! use wtfm_rs::return_true;
115//! fn assert_true_v3() {
116//! assert!(return_true());
117//! println!("{}", return_true());
118//! }
119//! ```
120//! `cargo test` will not have any println feedback.
121//! ```text
122//! test src/lib.rs - (line 110) ... ok
123//! ```
124//!
125//! So `println!` driven development wouldn't work with doctests.
126//!
127//! To do `println!` driven development, we can use example crates.
128//! ```text
129//! cargo doc --examples
130//! ```
131//! ```text
132//! cargo run --example example-assert-true
133//! Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.00s
134//! Running `target/debug/examples/example-assert-true`
135//! true
136//! ```
137
138//! # External crates
139//! Rustdoc is organized by crate and we can also bring external crates in
140//! via `Cargo.toml`
141//!
142//! ```toml
143//! [package]
144//! name = "wtfm-rs"
145//! version = "0.1.0"
146//! edition = "2024"
147//!
148//! [dependencies]
149//! wtfm-rs-hello-world = { git = "https://github.com/wtfm-rs/wtfm-rs.github.io", branch = "hello-world", version = "0.1.0" }
150//! ```
151//!
152//! In this case, we pull [wtfm_rs_hello_world] crate from
153//! <https://github.com/wtfm-rs/wtfm-rs.github.io/tree/hello-world>.
154//!
155//! We can use the function `hello_world()` from it
156//! ```text
157//! pub use wtfm_rs_hello_world::hello_world;
158//! ```
159//! and use it as `wtfm_rs::hello_world`.
160//!
161//! ```
162//! use wtfm_rs::hello_world;
163//! assert_eq!(hello_world(), "Hello, world!");
164//! ```
165//! This will fail in playwright as before, but will pass `cargo test`.
166//!
167//! # Get the ouput from echo Hello, world!
168//! ```
169//! use std::process::Command;
170//! let output = Command::new("echo")
171//! .arg("Hello,")
172//! .arg("world!")
173//! .output()
174//! .expect("Failed to execute command");
175//! let output_1 = String::from_utf8_lossy(&output.stdout).to_string();
176//! assert_eq!(format!("{}", output_1), "Hello, world!\n");
177//! let output_2 = String::from_utf8(output.stdout).expect("Format error");
178//! assert_eq!(format!("{}", output_2), "Hello, world!\n");
179//! ```
180//! [std::process::Command]
181//!
182//! [String::from_utf8]
183//!
184//! [String::from_utf8_lossy]
185//!
186//! # Same code in three crates
187//!
188//! 1. We started it as doctest in this crate [`wtfm_rs`](./index.html).
189//! 2. We copy and paste it to an example crate
190//! [`example_echo_hello_world`](../example_echo_hello_world/index.html).
191//!
192//! 3. We create an external crate [wtfm_rs_echo_hello_world]
193//! in a [branch](https://github.com/wtfm-rs/wtfm-rs.github.io/tree/refs/heads/echo-hello-world) and pull it in with `Cargo.toml`.:
194//! ```text
195//! wtfm-rs-echo-hello-world = { git = "https://github.com/wtfm-rs/wtfm-rs.github.io", branch = "echo-hello-world", version = "0.1.0" }
196//! ```
197//! # Why do we do this?
198//! This allows us to explore with controlled dependenices.
199//! Doctests in a crate will not becoming the dependency of any crate so we are
200//! safe to modify and test out ideas.
201//! Same with example crates that allows us to add `fn main` but can only be
202//! called via `cargto run --example ...`.
203//!
204//! Once the code is "published" in [wtfm_rs_echo_hello_world],
205//! and became part of the dependency tree, it can be called by us
206//! (Rollowing code will not run in Playground becuase of scope.)
207//!
208//! ```
209//! use wtfm_rs_echo_hello_world::echo_hello_world;
210//! assert_eq!(echo_hello_world(), "Hello, world!\n");
211//! ```
212//!
213//! ```
214//! use wtfm_rs::echo_hello_world;
215//! assert_eq!(echo_hello_world(), "Hello, world!\n");
216//! ```
217//!
218//! ```text
219//! cargo tree
220//! wtfm-rs v0.1.0 (/Users/sam/github/wtfm-rs/wtfm-rs.github.io)
221//! ├── wtfm-rs-echo-hello-world v0.1.0 (https://github.com/wtfm-rs/wtfm-rs.github.io?branch=echo-hello-world#9244775f)
222//! └── wtfm-rs-hello-world v0.1.0 (https://github.com/wtfm-rs/wtfm-rs.github.io?branch=hello-world#98fb5c7e)
223//! ```
224//! By design, none of these WTFM crates should end up in production.
225//! They are "paper trails” of thought process.
226//! The purpose of reading, writing, and testing them is to force us to
227//! think and reason the choices we made in the context of rust toolchain,
228//! i.e., `cargo doc, cargo test, cargo run ...` .
229//!
230//! We are free to change our mind.
231//!
232//! # Devil is in the details
233//! We have been distracted by the toolchain rabbit holes and it is time
234//! to get back to our main trail. Let's take a look at following code again.
235//! ```
236//! use std::process::Command;
237//! let output = Command::new("echo")
238//! .arg("Hello,")
239//! .arg("world!")
240//! .output()
241//! .expect("Failed to execute command");
242//! let output_1 = String::from_utf8_lossy(&output.stdout).to_string();
243//! assert_eq!(format!("{}", output_1), "Hello, world!\n");
244//! let output_2 = String::from_utf8(output.stdout).expect("Format error");
245//! assert_eq!(format!("{}", output_2), "Hello, world!\n");
246//! ```
247//! If we switch `output_1` and `output_2`, it won't compile
248//! ```compile_fail
249//! use std::process::Command;
250//! let output = Command::new("echo")
251//! .arg("Hello,")
252//! .arg("world!")
253//! .output()
254//! .expect("Failed to execute command");
255//! let output_2 = String::from_utf8(output.stdout).expect("Format error");
256//! assert_eq!(format!("{}", output_2), "Hello, world!\n");
257//! let output_1 = String::from_utf8_lossy(&output.stdout).to_string();
258//! assert_eq!(format!("{}", output_1), "Hello, world!\n");
259//! ```
260//! ```text
261//! failures:
262//!
263//! ---- src/lib.rs - (line 247) stdout ----
264//! error[E0382]: borrow of moved value: `output.stdout`
265//! --> src/lib.rs:256:40
266//! |
267//! 9 | let output_2 = String::from_utf8(output.stdout).expect("Format error");
268//! | ------------- value moved here
269//! 10 | assert_eq!(format!("{}", output_2), "Hello, world!\n");
270//! 11 | let output_1 = String::from_utf8_lossy(&output.stdout).to_string();
271//! | ^^^^^^^^^^^^^^ value borrowed here after move
272//! |
273//! = note: move occurs because `output.stdout` has type `Vec<u8>`, which does not implement the `Copy` trait
274//! = note: borrow occurs due to deref coercion to `[u8]`
275//! note: deref defined here
276//! --> /Users/sam/.rustup/toolchains/stable-aarch64-apple-darwin/lib/rustlib/src/rust/library/alloc/src/vec/mod.rs:3584:5
277//! |
278//! 3584 | type Target = [T];
279//! | ^^^^^^^^^^^
280//!
281//! error: aborting due to 1 previous error
282//!
283//! For more information about this error, try `rustc --explain E0382`.
284//! Couldn't compile the test.
285//!
286//! failures:
287//! src/lib.rs - (line 247)
288//! ```
289//! The error message is a great souce for RTFM so we won't
290//! get into another rabbit hole here.
291//! ( [String::from_utf8] vs [String::from_utf8_lossy])
292//!
293//! After RTFM, we might want to use [str::from_utf8]:
294//! ```
295//! use std::process::Command;
296//! let output = Command::new("echo")
297//! .arg("Hello,")
298//! .arg("world!")
299//! .output()
300//! .expect("Failed to execute command");
301//! let stdout = &output.stdout;
302//! let hello_world = str::from_utf8(stdout).expect("Format error");
303//! assert_eq!(hello_world, "Hello, world!\n");
304//! ```
305//! Much simpler and more idiomatic.
306//! We update [wtfm_rs_echo_hello_world] with a `echo_hello_world_v2`
307//!
308//! ```
309//! use wtfm_rs_echo_hello_world::echo_hello_world_v2;
310//! assert_eq!(echo_hello_world_v2(), "Hello, world!\n");
311//! ```
312//!
313//! ```
314//! use wtfm_rs::echo_hello_world_v2;
315//! assert_eq!(echo_hello_world_v2(), "Hello, world!\n");
316//! ```
317//!
318
319pub fn return_true() -> bool {
320 true
321}
322
323pub use wtfm_rs_echo_hello_world::echo_hello_world;
324pub use wtfm_rs_echo_hello_world::echo_hello_world_v2;
325pub use wtfm_rs_hello_world::hello_world;
326
327#[cfg(test)]
328#[test]
329fn test_return_true() {
330 assert!(return_true());
331}
332
333#[test]
334fn test_hello_world() {
335 assert_eq!(hello_world(), "Hello, world!");
336}
337
338#[test]
339fn test_echo_hello_world() {
340 assert_eq!(echo_hello_world(), "Hello, world!\n");
341}
342
343#[test]
344fn test_echo_hello_world_v2() {
345 assert_eq!(echo_hello_world_v2(), "Hello, world!\n");
346}