diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 1b7b340..c3822f9 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -2,6 +2,9 @@ - [Home](index.md) - [Résumé](resume.md) -- [Rust](rust.md) + +# Rust Stuff + + - [Generics Guide Part 1](rust-generics-guide-part-1.md) - [An Auto-Increment Crate for Rust](an_auto_increment_crate_for_rust.md) - [Rust Packages vs Crates](rust_packages_vs_crates.md) diff --git a/src/rust-generics-guide-part-1.md b/src/rust-generics-guide-part-1.md new file mode 100644 index 0000000..08bbe98 --- /dev/null +++ b/src/rust-generics-guide-part-1.md @@ -0,0 +1,207 @@ +# Rust Generics Guide Part 1 + + +- [Introduction](#introduction) +- [The Goal](#the-goal) +- [The Basics](#the-basics) + - [Type Definitions](#type-definitions) + - [Functions and Closures](#functions-and-closures) +- [Conclusion](#conclusion) + + + +## Introduction + +Rustaceans appreciate generics for three reasons: + +- Generics are **compile-time abstractions**. The `dyn` keyword, which generics can often replace, + has runtime overhead. +- Generics make code cleaner and more reusable. Thanks to trait bounds, code can describe *what* it + can do *when* those bounds are met. **Your code becomes exactly as flexible as you want it to + be.** +- Generics will help you understand lifetimes. [The Book][TheBook] explains lifetimes with *imperative* + code (i.e. code in a function body). But **generic lifetimes have *declarative* context**. This + gives them clear meaning when used in types, traits and implementations. + +Let's learn everything there is to know about generics. We'll do this by learning incrementally so +that each piece of the lesson is digested before we go back for more. + +Scroll to the bottom for a runnable example of the code we'll be writing. + +## The Goal + +Walking through a few simple examples can prepare anyone who has worked through [The +Book][TheBook] to use generics with confidence. Generics allow us to code with **selective +flexibility**. Once the tools are understood, the concept of plugging in types where appropriate +will come naturally. + +Starting with a dead-simple example, we'll do exactly what generic (or *parametized*) types are +supposed to do by introducing **type flexibility**. Then we can gently layer on the complexity and +learn why generics permeate into every aspect of the type system, why they need **optional +restrictions** and how we can use this powerful tooling without turning our code into cryptic goop. +By the end, we will have full control of Rust's type system. + +This guide will walk you through the process of writing a small library with plenty of code +examples. Each part can be completed in less than thirty minutes. If you follow along, the code +will compile after each section. Part one will cover the basics of types, functions and closures. +Part two will dig into implementations and traits. Finally, part three will add `const` generics +and parametized lifetimes. The resulting library will be simple but fun and easy to understand. + +**Part one mostly reviews information covered in The Book, but with some heplful observations.** If +you have questions, criticism or feedback, you can find contact information on the [homepage](/). + +## The Basics + +### Type Definitions + +Before getting into elaborate implementations that use multiple generics at every possible level, +let's focus on brass tacks. + +```rust,noplayground +struct Point { + id: u64 +} +``` + +This is familiar territory, `u64` is a hard-coded (non-generic) type. It offers no flexibility but +is simple and selective. + +```rust,noplayground +struct Point { + id: Id +} +``` + +This `Point` can use *any* other type as the `Id`. The `Id` could even be `Point` or a vector of +`Point`s, which would look pretty absurd: `Point>`. + +This flexibility can be excessive, which is how the `where` clause will help later on when we use an +implementation. For now, we can keep the `Point` type private from the user while we expose +public types with an `Id` that makes sense. We *could* use a trait bound in this type definition. +But you will rarely see this done. The reasons for that will be covered in detail in part two when +we write implementations and traits of our own. + +```rust,noplayground +pub type Point = inner::Point; +pub type BigPoint = inner::Point; + +mod inner { + struct Point { + id: Id + } +} +``` + +If the `Id` value is going to be randomly generated, larger types like `u128` will allow for lower +chances of collision when generating `BigPoint` instances in huge quantities. But for fewer +`Point`s, the `u32` type will use just four bytes instead of the sixteen bytes used by each `u128`. +This is a simplified example of how generics are useful when defining types. + +We have now seen three levels of flexibility: + +- A hard-coded (non-generic) type. +- A highly flexible open-ended generic type. +- A generic type used privately with certain types plugged in for the public interface. + +### Functions and Closures + +A function consists of a type definition (the function signature) and an execution context (the +imperative code block). It is helpful to start with a simple function before moving on to +implementations. + +```rust,noplayground +struct Line

{ + points: (P, P), +} + +fn connect

(left: P, right: P) -> Line

{ + Line { + points: (left, right) + } +} +``` + +Here the type used in the `points` tuple is decided **wherever `connect` is called, because that is +where `Line` is invoked**. If tight coupling is acceptable, we can specify that `connect` always +deals with `Line`s and `Point`s, while still allowing a generic type in the `Point`'s `id` field. + +```rust,noplayground +fn connect(left: Point, right: Point) -> Line> { + Line { + points: (left, right) + } +} +``` + +We still have a type parameter, but now the `Point` and `Line` types are tightly coupled in this +function. + +As with any function, the arguments could be written as a `struct` and the function body could be +moved to a method. Instead of weighing in on the eternal struggle of functional vs object-oriented +code aesthetics, we will simply observe that function signatures are type definitions. This is not +a trivial observation. For now, it's enough to simply point it out. Let's move on to using a +generic type for a closure. + +```rust +/// This is the same Point we used to first demonstrate a generic type. +#[derive(Debug)] +struct Point { + id: Id +} + +/// This Line is different. We've restricted it to use the Point type but left +/// the Id flexible. Before, this tight coupling was done in the `connect` +// method. +#[derive(Debug)] +struct Line { + points: (Point, Point), +} + +/// This new type contains a closure. While Box is a type, this involves Fn, a +/// trait that uses generic types. We will write our own such trait later. +struct Connector { + pub op: Box, Point) -> Line>, +} + +fn main() { + let connector: Connector = Connector { + op: Box::new(|left, right| Line { + points: (left, right), + }), + }; + + let point_a = Point { + id: "A".to_string(), + }; + let point_b = Point { + id: "B".to_string(), + }; + let line = (connector.op)(point_a, point_b); + + println!("{:?}", line); +} +``` + +Here we have built a tiny library that, with just a little more work, might do something +interesting. If we iterate over pairs of `Point`s we could allow the user to connect them while +also recording, modifying or filtering them. Meanwhile, our library could handle how the iterating +works. Or instead of iterating over pairs and returning `Lines`, we could select them in groups of +whatever size the user wants and combine them into types that we provide. We could also let the +user write their own `Line`, `Square`, `Graph` or whatever they want by having them plug it into a +*type parameter* (i.e. generic type). + +It is with this cliffhanger that we'll stop with part one. The good stuff comes next. + +## Conclusion + +It might seem that we have all we need with the tools we've demonstrated so far. It's true that +**you can do a lot with these few simple tools**. But Rust needs more syntax in order to feature +the power of generics in all their glory. While part two will be more complex than this lesson, the +language as a whole will become easier to understand. + +If you have any questions or comments about this guide, contact information is on the +[homepage](../). I appreciate any and all feedback. + +[Go to part two.](../rust_guide_generics_demystified_part_2) + +[TheBook]:https://doc.rust-lang.org/stable/book/title-page.html diff --git a/src/rust.md b/src/rust.md deleted file mode 100644 index 3222b7b..0000000 --- a/src/rust.md +++ /dev/null @@ -1,41 +0,0 @@ -# Rust - -Rust is a systems programming language that I've been using in my projects since 2020. Here's an example from The Rust Programming Language (a.k.a. The Book). - -```rust,noplayground -use rand::Rng; -use std::cmp::Ordering; -use std::io; - -fn main() { - println!("Guess the number!"); - - let secret_number = rand::thread_rng().gen_range(1..=100); - - loop { - println!("Please input your guess."); - - let mut guess = String::new(); - - io::stdin() - .read_line(&mut guess) - .expect("Failed to read line"); - - let guess: u32 = match guess.trim().parse() { - Ok(num) => num, - Err(_) => continue, - }; - - println!("You guessed: {guess}"); - - match guess.cmp(&secret_number) { - Ordering::Less => println!("Too small!"), - Ordering::Greater => println!("Too big!"), - Ordering::Equal => { - println!("You win!"); - break; - } - } - } -} -``` diff --git a/theme/css/general.css b/theme/css/general.css index 7670b08..9b33a0a 100644 --- a/theme/css/general.css +++ b/theme/css/general.css @@ -37,18 +37,39 @@ main { } /* Don't change font size in headers. */ -h1 code, h2 code, h3 code, h4 code, h5 code, h6 code { +h1 code, +h2 code, +h3 code, +h4 code, +h5 code, +h6 code { font-size: unset; } -.left { float: left; } -.right { float: right; } -.boring { opacity: 0.6; } -.hide-boring .boring { display: none; } -.hidden { display: none !important; } +.left { + float: left; +} +.right { + float: right; +} +.boring { + opacity: 0.6; +} +.hide-boring .boring { + display: none; +} +.hidden { + display: none !important; +} -h2, h3 { margin-block-start: 2.5em; } -h4, h5 { margin-block-start: 2em; } +h2, +h3 { + margin-block-start: 2.5em; +} +h4, +h5 { + margin-block-start: 2em; +} .header + .header h3, .header + .header h4, @@ -80,7 +101,9 @@ h6:target::before { .page { outline: 0; padding: 0 var(--page-padding); - margin-block-start: calc(0px - var(--menu-bar-height)); /* Compensate for the #menu-bar-hover-placeholder */ + margin-block-start: calc( + 0px - var(--menu-bar-height) + ); /* Compensate for the #menu-bar-hover-placeholder */ } .page-wrapper { box-sizing: border-box; @@ -88,10 +111,14 @@ h6:target::before { } .no-js .page-wrapper, .js:not(.sidebar-resizing) .page-wrapper { - transition: margin-left 0.3s ease, transform 0.3s ease; /* Animation: slide away */ + transition: + margin-left 0.3s ease, + transform 0.3s ease; /* Animation: slide away */ } -[dir=rtl] .js:not(.sidebar-resizing) .page-wrapper { - transition: margin-right 0.3s ease, transform 0.3s ease; /* Animation: slide away */ +[dir="rtl"] .js:not(.sidebar-resizing) .page-wrapper { + transition: + margin-right 0.3s ease, + transform 0.3s ease; /* Animation: slide away */ } .content { @@ -103,12 +130,25 @@ h6:target::before { margin-inline-end: auto; max-width: var(--content-max-width); } -.content p { line-height: 1.45em; } -.content ol { line-height: 1.45em; } -.content ul { line-height: 1.45em; } -.content a { text-decoration: none; } -.content a:hover { text-decoration: underline; } -.content img, .content video { max-width: 100%; } +.content p { + line-height: 1.45em; +} +.content ol { + line-height: 1.45em; +} +.content ul { + line-height: 1.45em; +} +.content a { + text-decoration: none; +} +.content a:hover { + text-decoration: underline; +} +.content img, +.content video { + max-width: 100%; +} .content .header:link, .content .header:visited { color: var(--fg); @@ -117,6 +157,14 @@ h6:target::before { .content .header:visited:hover { text-decoration: none; } +.content hr { + max-width: var(--content-max-width); +} +.content footer { + margin-inline-start: auto; + margin-inline-end: auto; + max-width: var(--content-max-width); +} table { margin: 0 auto; @@ -144,14 +192,13 @@ table tbody tr:nth-child(2n) { background: var(--table-alternate-bg); } - blockquote { margin: 20px 0; padding: 0 20px; color: var(--fg); background-color: var(--quote-bg); - border-block-start: .1em solid var(--quote-border); - border-block-end: .1em solid var(--quote-border); + border-block-start: 0.1em solid var(--quote-border); + border-block-end: 0.1em solid var(--quote-border); } .warning { diff --git a/theme/index.hbs b/theme/index.hbs index 080b785..ccb5eb8 100644 --- a/theme/index.hbs +++ b/theme/index.hbs @@ -166,7 +166,7 @@ {{/if}} -

{{ book_title }}

+

{{ book_title }}

{{#if print_enable}} @@ -215,6 +215,13 @@ {{{ content }}} +
+ + +
+ +