Add rust guide part 1; Add CCBY license to pages

This commit is contained in:
Jeff 2024-07-16 10:44:34 -04:00
parent 31956191bd
commit f06eb61b9e
5 changed files with 289 additions and 64 deletions

View File

@ -2,6 +2,9 @@
- [Home](index.md) - [Home](index.md)
- [Résumé](resume.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) - [An Auto-Increment Crate for Rust](an_auto_increment_crate_for_rust.md)
- [Rust Packages vs Crates](rust_packages_vs_crates.md) - [Rust Packages vs Crates](rust_packages_vs_crates.md)

View File

@ -0,0 +1,207 @@
# Rust Generics Guide Part 1
<!-- TOC -->
- [Introduction](#introduction)
- [The Goal](#the-goal)
- [The Basics](#the-basics)
- [Type Definitions](#type-definitions)
- [Functions and Closures](#functions-and-closures)
- [Conclusion](#conclusion)
<!-- /TOC -->
## 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: 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<Vec<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<Id>` 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<u32>;
pub type BigPoint = inner::Point<u128>;
mod inner {
struct Point<Id> {
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<P> {
points: (P, P),
}
fn connect<P>(left: P, right: P) -> Line<P> {
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<Id>(left: Point<Id>, right: Point<Id>) -> Line<Point<Id>> {
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: 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<Id> {
points: (Point<Id>, Point<Id>),
}
/// 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<Id> {
pub op: Box<dyn Fn(Point<Id>, Point<Id>) -> Line<Id>>,
}
fn main() {
let connector: Connector<String> = 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

View File

@ -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;
}
}
}
}
```

View File

@ -37,18 +37,39 @@ main {
} }
/* Don't change font size in headers. */ /* 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; font-size: unset;
} }
.left { float: left; } .left {
.right { float: right; } float: left;
.boring { opacity: 0.6; } }
.hide-boring .boring { display: none; } .right {
.hidden { display: none !important; } float: right;
}
.boring {
opacity: 0.6;
}
.hide-boring .boring {
display: none;
}
.hidden {
display: none !important;
}
h2, h3 { margin-block-start: 2.5em; } h2,
h4, h5 { margin-block-start: 2em; } h3 {
margin-block-start: 2.5em;
}
h4,
h5 {
margin-block-start: 2em;
}
.header + .header h3, .header + .header h3,
.header + .header h4, .header + .header h4,
@ -80,7 +101,9 @@ h6:target::before {
.page { .page {
outline: 0; outline: 0;
padding: 0 var(--page-padding); 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 { .page-wrapper {
box-sizing: border-box; box-sizing: border-box;
@ -88,10 +111,14 @@ h6:target::before {
} }
.no-js .page-wrapper, .no-js .page-wrapper,
.js:not(.sidebar-resizing) .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 { [dir="rtl"] .js:not(.sidebar-resizing) .page-wrapper {
transition: margin-right 0.3s ease, transform 0.3s ease; /* Animation: slide away */ transition:
margin-right 0.3s ease,
transform 0.3s ease; /* Animation: slide away */
} }
.content { .content {
@ -103,12 +130,25 @@ h6:target::before {
margin-inline-end: auto; margin-inline-end: auto;
max-width: var(--content-max-width); max-width: var(--content-max-width);
} }
.content p { line-height: 1.45em; } .content p {
.content ol { line-height: 1.45em; } line-height: 1.45em;
.content ul { line-height: 1.45em; } }
.content a { text-decoration: none; } .content ol {
.content a:hover { text-decoration: underline; } line-height: 1.45em;
.content img, .content video { max-width: 100%; } }
.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:link,
.content .header:visited { .content .header:visited {
color: var(--fg); color: var(--fg);
@ -117,6 +157,14 @@ h6:target::before {
.content .header:visited:hover { .content .header:visited:hover {
text-decoration: none; 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 { table {
margin: 0 auto; margin: 0 auto;
@ -144,14 +192,13 @@ table tbody tr:nth-child(2n) {
background: var(--table-alternate-bg); background: var(--table-alternate-bg);
} }
blockquote { blockquote {
margin: 20px 0; margin: 20px 0;
padding: 0 20px; padding: 0 20px;
color: var(--fg); color: var(--fg);
background-color: var(--quote-bg); background-color: var(--quote-bg);
border-block-start: .1em solid var(--quote-border); border-block-start: 0.1em solid var(--quote-border);
border-block-end: .1em solid var(--quote-border); border-block-end: 0.1em solid var(--quote-border);
} }
.warning { .warning {

View File

@ -166,7 +166,7 @@
{{/if}} {{/if}}
</div> </div>
<h1 class="menu-title">{{ book_title }}</h1> <h1 class="menu-title"><a href="/">{{ book_title }}</a></h1>
<div class="right-buttons"> <div class="right-buttons">
{{#if print_enable}} {{#if print_enable}}
@ -215,6 +215,13 @@
{{{ content }}} {{{ content }}}
</main> </main>
<hr/>
<footer>
<!-- License from https://creativecommons.org/ -->
<p>Licensed under <a href="https://creativecommons.org/licenses/by/4.0?ref=chooser-v1">CC BY 4.0</a></p>
</footer>
<nav class="nav-wrapper" aria-label="Page navigation"> <nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons --> <!-- Mobile navigation buttons -->
{{#previous}} {{#previous}}
@ -232,6 +239,8 @@
<div style="clear: both"></div> <div style="clear: both"></div>
</nav> </nav>
</div> </div>
</div> </div>
<nav class="nav-wide-wrapper" aria-label="Page navigation"> <nav class="nav-wide-wrapper" aria-label="Page navigation">