├── static ├── css │ ├── input.css │ └── output.css ├── img │ ├── me.jpg │ ├── me2.jpg │ ├── posts.webp │ ├── post │ │ ├── 0.webp │ │ ├── 1.webp │ │ ├── 10.webp │ │ ├── 2.webp │ │ ├── 3.webp │ │ ├── 4.webp │ │ ├── 5.webp │ │ ├── 6.webp │ │ ├── 7.webp │ │ ├── 8.webp │ │ └── 9.webp │ ├── DALL·E 2024-12-27 05.31.52 - A visually striking thumbnail for a YouTube video titled 'The Distinction Between Faith and Knowledge.' The image features a split design_ one side sh.webp:Zone.Identifier │ ├── DALL·E 2024-12-27 05.31.52 - A visually striking thumbnail for a YouTube video titled 'The Distinction Between Faith and Knowledge.' The image features a split design_ one side sh.webp │ ├── logo-dark.svg │ └── logo.svg └── js │ └── index.js ├── templates ├── overlay.html ├── footer.html ├── nav.html ├── header.html ├── root.html └── social.html ├── Makefile ├── go.mod ├── flint.json ├── tailwind.config.js ├── content ├── posts.md ├── post │ ├── 2.formatting-strings-in-rust.md │ ├── 3.random-numbers-in-rust.md │ ├── 4.how-i-generate-this-site.md │ ├── 10.rust-blanket-implementations.md │ ├── 1.async-rust.md │ ├── 5.structs-traits-and-enums-in-rust.md │ ├── 8.rust-lifetimes.md │ ├── 9.rust-generics.md │ ├── 6.rust-hyper.md │ ├── 7.iterators-in-rust.md │ └── 0.trying-neovim.md └── index.md ├── main.go └── go.sum /static/css/input.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; -------------------------------------------------------------------------------- /static/img/me.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Phillip-England/portfolio-site/main/static/img/me.jpg -------------------------------------------------------------------------------- /static/img/me2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Phillip-England/portfolio-site/main/static/img/me2.jpg -------------------------------------------------------------------------------- /static/img/posts.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Phillip-England/portfolio-site/main/static/img/posts.webp -------------------------------------------------------------------------------- /static/img/post/0.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Phillip-England/portfolio-site/main/static/img/post/0.webp -------------------------------------------------------------------------------- /static/img/post/1.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Phillip-England/portfolio-site/main/static/img/post/1.webp -------------------------------------------------------------------------------- /static/img/post/10.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Phillip-England/portfolio-site/main/static/img/post/10.webp -------------------------------------------------------------------------------- /static/img/post/2.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Phillip-England/portfolio-site/main/static/img/post/2.webp -------------------------------------------------------------------------------- /static/img/post/3.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Phillip-England/portfolio-site/main/static/img/post/3.webp -------------------------------------------------------------------------------- /static/img/post/4.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Phillip-England/portfolio-site/main/static/img/post/4.webp -------------------------------------------------------------------------------- /static/img/post/5.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Phillip-England/portfolio-site/main/static/img/post/5.webp -------------------------------------------------------------------------------- /static/img/post/6.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Phillip-England/portfolio-site/main/static/img/post/6.webp -------------------------------------------------------------------------------- /static/img/post/7.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Phillip-England/portfolio-site/main/static/img/post/7.webp -------------------------------------------------------------------------------- /static/img/post/8.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Phillip-England/portfolio-site/main/static/img/post/8.webp -------------------------------------------------------------------------------- /static/img/post/9.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Phillip-England/portfolio-site/main/static/img/post/9.webp -------------------------------------------------------------------------------- /templates/overlay.html: -------------------------------------------------------------------------------- 1 | {{ define "overlay" }} 2 | 3 | {{ end }} -------------------------------------------------------------------------------- /static/img/DALL·E 2024-12-27 05.31.52 - A visually striking thumbnail for a YouTube video titled 'The Distinction Between Faith and Knowledge.' The image features a split design_ one side sh.webp:Zone.Identifier: -------------------------------------------------------------------------------- 1 | [ZoneTransfer] 2 | ZoneId=3 3 | HostUrl=https://chatgpt.com/ 4 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | tw: 2 | tailwindcss -i ./static/css/input.css -o ./static/css/output.css --watch 3 | 4 | install: 5 | flint spark; mv out ..; cd ..; mv out www.phillip-england.com; cd www.phillip-england.com; rm -r index.html; rm -r post; rm -r posts.html; rm -r static; cd out; mv * ..; cd ..; rm -r out; 6 | -------------------------------------------------------------------------------- /templates/footer.html: -------------------------------------------------------------------------------- 1 | {{ define "footer" }} 2 | 5 | {{ end }} 6 | -------------------------------------------------------------------------------- /static/img/DALL·E 2024-12-27 05.31.52 - A visually striking thumbnail for a YouTube video titled 'The Distinction Between Faith and Knowledge.' The image features a split design_ one side sh.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Phillip-England/portfolio-site/main/static/img/DALL·E 2024-12-27 05.31.52 - A visually striking thumbnail for a YouTube video titled 'The Distinction Between Faith and Knowledge.' The image features a split design_ one side sh.webp -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module phillip-england 2 | 3 | go 1.23.3 4 | 5 | require github.com/phillip-england/vbf v0.0.28 6 | 7 | require ( 8 | github.com/PuerkitoBio/goquery v1.8.1 // indirect 9 | github.com/a-h/templ v0.2.771 // indirect 10 | github.com/alecthomas/chroma/v2 v2.14.0 // indirect 11 | github.com/andybalholm/cascadia v1.3.1 // indirect 12 | github.com/dlclark/regexp2 v1.11.0 // indirect 13 | github.com/yuin/goldmark v1.7.4 // indirect 14 | github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc // indirect 15 | golang.org/x/net v0.28.0 // indirect 16 | ) 17 | -------------------------------------------------------------------------------- /flint.json: -------------------------------------------------------------------------------- 1 | { 2 | "host": "http://localhost:8080", 3 | "static": "./static", 4 | "favicon": "./favicon.ico", 5 | "out": "./out", 6 | "target": "https://phillip-england.com", 7 | "routes": [ 8 | "/", 9 | "/posts", 10 | "/post/trying-neovim", 11 | "/post/async-rust", 12 | "/post/formatting-strings-in-rust", 13 | "/post/random-numbers-in-rust", 14 | "/post/how-i-generate-this-site", 15 | "/post/structs-traits-and-enums-in-rust", 16 | "/post/rust-hyper", 17 | "/post/iterators-in-rust", 18 | "/post/rust-lifetimes", 19 | "/post/rust-generics", 20 | "/post/rust-blanket-implementations" 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | darkMode: 'selector', 4 | content: ["./**/*.{html, go, js}", "./static/**/*.js", "./content/**/*.md"], 5 | theme: { 6 | extend: { 7 | colors: { 8 | dracula: { 9 | background: "#282a36", 10 | current: "#44475a", 11 | foreground: "#f8f8f2", 12 | comment: "#6272a4", 13 | cyan: "#8be9fd", 14 | green: "#50fa7b", 15 | orange: "#ffb86c", 16 | pink: "#ff79c6", 17 | purple: "#bd93f9", 18 | red: "#ff5555", 19 | yellow: "#f1fa8c", 20 | }, 21 | }, 22 | }, 23 | }, 24 | plugins: [], 25 | }; -------------------------------------------------------------------------------- /content/posts.md: -------------------------------------------------------------------------------- 1 | 0. [Trying Neovim](/post/trying-neovim) *12/13/2024* 2 | 1. [Async Rust](/post/async-rust) *12/14/2024* 3 | 2. [Formatting Strings in Rust](/post/formatting-strings-in-rust) *12/15/2024* 4 | 3. [Random Numbers in Rust](/post/random-numbers-in-rust) *12/15/2024* 5 | 4. [How I Generate This Site](/post/how-i-generate-this-site) *12/15/2024* 6 | 5. [Structs, Traits, and Enums in Rust](/post/structs-traits-and-enums-in-rust) *12/15/2024* 7 | 6. [Rust Hyper](/post/rust-hyper) *12/16/2024* 8 | 7. [Iterators in Rust](/post/iterators-in-rust) *12/17/2024* 9 | 8. [Rust Lifetimes](/post/rust-lifetimes) *12/18/2024* 10 | 9. [Rust Generics](/post/rust-generics) *12/19/2024* 11 | 10. [Rust Blanket Implementations](/post/rust-blanket-implementations) *12/20/2024* 12 | -------------------------------------------------------------------------------- /templates/nav.html: -------------------------------------------------------------------------------- 1 | {{ define "nav" }} 2 | 24 | {{ end }} -------------------------------------------------------------------------------- /templates/header.html: -------------------------------------------------------------------------------- 1 | {{ define "header" }} 2 | 3 |
4 |
5 | 6 | 7 | 8 | 11 |
12 | 15 |
16 | 21 |
22 |
23 | 24 | {{ end }} 25 | -------------------------------------------------------------------------------- /content/post/2.formatting-strings-in-rust.md: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 | 6 | ## Formatting Strings 7 | I asked [Claude](https://claude.ai) how to format strings in Rust. It introduced me to the `format!` macro. 8 | 9 | Here is what Claude had to say. 10 | 11 | In Rust, there are several ways to format strings. I'll show you the most common methods: 12 | 13 | 1. Using `format!` Macro 14 | ```rs 15 | let name = "Alice"; 16 | let age = 30; 17 | let formatted = format!("My name is {} and I am {} years old", name, age); 18 | println!("{}", formatted); 19 | ``` 20 | 21 | 2. Using Positional Arguments 22 | ```rs 23 | let formatted = format!("First: {0}, Second: {1}, First again: {0}", "hello", "world"); 24 | ``` 25 | 26 | 3. Named Parameters 27 | ```rs 28 | let formatted = format!("Name: {name}, Age: {age}", name="Bob", age=25); 29 | ``` 30 | 31 | 4. Specifying Formatting Traits 32 | ```rs 33 | // Debugging format 34 | let debug_str = format!("Debug: {:?}", vec![1, 2, 3]); 35 | 36 | // Precision for floating-point numbers 37 | let pi = 3.14159; 38 | let precise = format!("Pi to two decimal places: {:.2}", pi); 39 | ``` 40 | 41 | 5. Padding and Alignment 42 | ```rs 43 | let padded = format!("{:10}", "hi"); // Right-aligned, width 10 44 | let left_aligned = format!("{:<10}", "hi"); // Left-aligned, width 10 45 | let zero_padded = format!("{:0>5}", 42); // Zero-padded to width 5 46 | ``` 47 | 48 | The `format!` macro is versatile and allows you to create formatted strings without printing them, while `println!` directly prints to the console. The syntax is consistent across these methods, making string formatting in Rust quite flexible and powerful. 49 | -------------------------------------------------------------------------------- /content/index.md: -------------------------------------------------------------------------------- 1 | ## Recent Posts 2 | 0. [Trying Neovim](/post/trying-neovim) *12/13/2024* 3 | 1. [Async Rust](/post/async-rust) *12/14/2024* 4 | 2. [Formatting Strings in Rust](/post/formatting-strings-in-rust) *12/15/2024* 5 | 3. [Random Numbers in Rust](/post/random-numbers-in-rust) *12/15/2024* 6 | 4. [How I Generate This Site](/post/how-i-generate-this-site) *12/15/2024* 7 | 5. [Structs, Traits, and Enums in Rust](/post/structs-traits-and-enums-in-rust) *12/15/2024* 8 | 6. [Rust Hyper](/post/rust-hyper) *12/16/2024* 9 | 7. [Iterators in Rust](/post/iterators-in-rust) *12/17/2024* 10 | 8. [Rust Lifetimes](/post/rust-lifetimes) *12/18/2024* 11 | 9. [Rust Generics](/post/rust-generics) *12/19/2024* 12 | 10. [Rust Blanket Implementations](/post/rust-blanket-implementations) *12/20/2024* 13 | 14 | ## Welcome, 15 | 16 | My name is Phillip England, and I am passionate about programming. This website is my notebook. Here, I will be documenting my software development educational journey. 17 | 18 | I began coding in 2019 when I became interested in making a website. I still remember when I ran my first for loop in the browser console. Since then, I've been programming as a hobby. Around 3 years ago, I ramped up my learning and have been waking up at 4AM to study. 19 | 20 | A while back, I thought to myself, "I would probably keep coding even if it was never a career for me." That's when I knew I needed to start documenting my learning. There is a chance this is a craft I keep with me for a lifetime regardless of my professional endeavours. 21 | 22 | That in and of itself might be worth something. 23 | 24 | ## Learning Wishlist 25 | When I think to myself, "I need to learn that!", I will document the thought here. 26 | 27 | - Learn about blanket implementations in Rust. They basically allow you to create methods which apply to a multitude of types. I discovered this idea when starting my string parsing lib, `stur`. 28 | -------------------------------------------------------------------------------- /static/img/logo-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 11 | 13 | 18 | 22 | 26 | 30 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /templates/root.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 24 | 25 | {{ .Title }} 26 | 27 | 28 |
29 | {{ template "header" . }} 30 | {{ template "social" . }} 31 | {{ template "nav" . }} 32 | {{ template "overlay" . }} 33 |
34 |
35 |
36 |

{{ .PostName }}

37 |

{{ .DateWritten }}

38 |
39 |

{{ .SubText }}

40 | 41 | 42 |
43 | 44 | 45 | {{ .Content }} 46 | 47 |
48 | {{ template "footer" . }} 49 | 50 |
51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /content/post/3.random-numbers-in-rust.md: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 | 6 | ## Generating Random Numbers 7 | I asked [Calude](https://claude.ai) how to generate random numbers in Rust. Here is the response: 8 | 9 | I'll show you how to generate random numbers in Rust using the rand crate, which is the most common way to generate random numbers in Rust. 10 | First, you'll need to add the rand crate to your Cargo.toml file: 11 | ```toml 12 | [dependencies] 13 | rand = "0.8.5" 14 | ``` 15 | 16 | Here are different ways to generate random numbers: 17 | 18 | 1. Generating a Random Integer 19 | ```rs 20 | use rand::Rng; 21 | 22 | fn main() { 23 | let mut rng = rand::thread_rng(); 24 | 25 | // Random integer between 1 and 10 26 | let random_number = rng.gen_range(1..=10); 27 | println!("Random number: {}", random_number); 28 | } 29 | ``` 30 | 31 | 2. Generating a Random Float 32 | ```rs 33 | use rand::Rng; 34 | 35 | fn main() { 36 | let mut rng = rand::thread_rng(); 37 | 38 | // Random float between 0.0 and 1.0 39 | let random_float = rng.gen::(); 40 | println!("Random float: {}", random_float); 41 | 42 | // Random float in a specific range 43 | let random_range_float = rng.gen_range(0.0..10.0); 44 | println!("Random float in range: {}", random_range_float); 45 | } 46 | ``` 47 | 48 | 3. Random Boolean 49 | ```rs 50 | use rand::Rng; 51 | 52 | fn main() { 53 | let mut rng = rand::thread_rng(); 54 | 55 | let random_bool = rng.gen_bool(0.5); // 50% true, 50% false 56 | println!("Random boolean: {}", random_bool); 57 | } 58 | ``` 59 | 60 | 4. Random Choice from a Collection 61 | ```rs 62 | use rand::seq::SliceRandom; 63 | 64 | fn main() { 65 | let mut rng = rand::thread_rng(); 66 | 67 | let choices = vec![1, 2, 3, 4, 5]; 68 | 69 | // Random element 70 | if let Some(chosen) = choices.choose(&mut rng) { 71 | println!("Randomly chosen: {}", chosen); 72 | } 73 | 74 | // Shuffling a vector 75 | let mut nums = vec![1, 2, 3, 4, 5]; 76 | nums.shuffle(&mut rng); 77 | println!("Shuffled vector: {:?}", nums); 78 | } 79 | ``` 80 | 81 | 5. Random Distribution (Noraml/Gaussian) 82 | ```rs 83 | use rand::distributions::{Normal, Distribution}; 84 | 85 | fn main() { 86 | let mut rng = rand::thread_rng(); 87 | 88 | // Normal distribution with mean 0.0 and standard deviation 1.0 89 | let normal = Normal::new(0.0, 1.0).unwrap(); 90 | let random_gaussian = normal.sample(&mut rng); 91 | println!("Gaussian distributed number: {}", random_gaussian); 92 | } 93 | ``` 94 | 95 | Key points to remember: 96 | 97 | - rand::thread_rng() creates a random number generator local to the current thread 98 | - gen_range() lets you specify a range (inclusive or exclusive) 99 | - gen() generates a random number of the specified type 100 | - Always import rand::Rng to use these methods 101 | 102 | Each time you run the program, you'll get different random numbers. The rand crate provides a robust and secure way to generate random numbers in Rust. 103 | -------------------------------------------------------------------------------- /static/img/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 14 | 16 | 26 | 31 | 35 | 38 | 41 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /templates/social.html: -------------------------------------------------------------------------------- 1 | {{ define "social" }} 2 |
3 | 15 |
16 | 19 |
20 | 25 |
26 | {{ end }} 27 | -------------------------------------------------------------------------------- /content/post/4.how-i-generate-this-site.md: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 | 6 | ## Easy Peasy 7 | It really is. Using [flint](https://github.com/phillip-england/flint), I run `flint spark`. The following is the resulting output, which walks you through each step flint takes as it bundles up your static assets. 8 | 9 | ```bash 10 | 11 | ########## ## ## ### ## ######## 12 | ## ## #### ## ## 13 | ####### ## ## ## ## ## ## 14 | ## ## ## ## ## ## ## 15 | ## ## ## ## ## ## ## 16 | ## ## ## ## ## ## 17 | ## ######### ## #### ## 18 | ------------------------------------------------- 19 | Language-Agnostic Static Sites 20 | ------------------------------------------------- 21 | 🔥 sparking flint 22 | 🗃️ searching for flint.json 23 | 🔎 parsing routes from flint.json 24 | 🏹 making an http request to each route 25 | 🔨 generating static html assests 26 | 🗑️ removing ./out 27 | ✍️ writing minified, static html to ./out 28 | 🖌️ copying over minified, static assests from ./static 29 | ✏️ copying over the favicon from ./favicon.ico to ./out 30 | ⚠️ Favicon does not exist at: ./favicon.ico 31 | 📚 your assets have been bundled at ./out 32 | 🙏 thank you for using flint 33 | ⭐ dont forget to give me star at: https://github.com/phillip-england/flint 34 | ``` 35 | 36 | ## Why Flint? 37 | You see, I found myself repeating a common pattern in my web applications. I consider myself fairly effecient with Go, and I can get a web server up and running with some html pages quickly. 38 | 39 | I don't want to be locked into a certain way of doing things. 40 | 41 | That's why I built flint, a minimal static site generator for people who can already get their static site running on a localhost server. 42 | 43 | Why use some big static site generator when you might not have to? 44 | 45 | ## Follow Along 46 | I can explain how `flint` works, but it's probably easier if you just follow along. Below, I will walk you through how to install this website, install flint, and use flint to generate a minified, static version of this website. 47 | 48 | 49 | ## Install This Website 50 | Clone the repo: 51 | ```bash 52 | git clone https://github.com/phillip-england/portfolio-site 53 | ``` 54 | 55 | You'll need `go` version `1.23.3` or later. 56 | 57 | Once installed, `cd` into the project and serve it: 58 | ```bash 59 | cd portfolio-site 60 | go run main.go 61 | ``` 62 | 63 | Okay, now we can leave this application running in the background. 64 | 65 | ## Installing Flint 66 | In a new terminal, clone the repo: 67 | ```bash 68 | git clone https://github.com/phillip-england/flint 69 | ``` 70 | 71 | Then install: 72 | ```bash 73 | cd flint 74 | go install 75 | ``` 76 | 77 | You'll need to verify `go` properly installed the binary on your `PATH`. After, reload your terminal to ensure the changes take effect. 78 | 79 | To verify installation, run `flint help`. 80 | 81 | ## flint.json 82 | Inside of `portfolio-site` you should see a `flint.json`. Go ahead and take a look: 83 | 84 | ```json 85 | { 86 | "host": "http://localhost:8080", 87 | "static": "./static", 88 | "favicon": "./favicon.ico", 89 | "out": "./out", 90 | "target": "https://phillip-england.com", 91 | "routes": [ 92 | "/", 93 | "/posts", 94 | 95 | ... more routes 96 | ] 97 | } 98 | ``` 99 | 100 | This config tells flint where to ping, which routes to ping, and where all the static assets are located. It also tells flint where the website will be deployed so all `href` values can be changed to absolute paths pointing towards the deployment location. 101 | 102 | ## Generating The Site 103 | Navigate back to the `portfolio-site` and make sure it is still running in the background on `localhost:8080`. 104 | 105 | Once you've ensured the website is still running locally, run the following command from within `portfolio-site`: 106 | ```bash 107 | flint spark 108 | ``` 109 | 110 | Afterward, you should see a new directory named `./out` at `/portfolio-site/out`. 111 | 112 | This directory contains all the static assets for the site and can easily be dropped into any static deployment environment. 113 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "html/template" 6 | "io/fs" 7 | "net/http" 8 | "path/filepath" 9 | "strings" 10 | 11 | "github.com/PuerkitoBio/goquery" 12 | "github.com/phillip-england/vbf" 13 | ) 14 | 15 | const KeyTemplates = "KEYTEMPLATES" 16 | 17 | func main() { 18 | 19 | mux, gCtx := vbf.VeryBestFramework() 20 | 21 | strEquals := func(input string, value string) bool { 22 | return input == value 23 | } 24 | 25 | funcMap := template.FuncMap{ 26 | "strEquals": strEquals, 27 | } 28 | 29 | templates, err := vbf.ParseTemplates("./templates", funcMap) 30 | if err != nil { 31 | panic(err) 32 | } 33 | 34 | vbf.SetGlobalContext(gCtx, KeyTemplates, templates) 35 | vbf.HandleStaticFiles(mux) 36 | vbf.HandleFavicon(mux) 37 | 38 | vbf.AddRoute("GET /", mux, gCtx, func(w http.ResponseWriter, r *http.Request) { 39 | if r.URL.Path == "/" { 40 | templates, _ := vbf.GetContext(KeyTemplates, r).(*template.Template) 41 | mdContent, err := vbf.LoadMarkdown("./content/index.md", "dracula") 42 | if err != nil { 43 | fmt.Println(err.Error()) 44 | w.WriteHeader(500) 45 | return 46 | } 47 | vbf.ExecuteTemplate(w, templates, "root.html", map[string]interface{}{ 48 | "Title": "phillip england", 49 | "Content": template.HTML(mdContent), 50 | "ReqPath": r.URL.Path, 51 | "PostName": "[phillip england]", 52 | "SubText": "learning in public", 53 | "ImagePath": "/static/img/me2.jpg", 54 | "DateWritten": "🎂 born on 12/9/2024", 55 | }) 56 | } else { 57 | vbf.WriteString(w, "404 not found") 58 | } 59 | }, vbf.MwLogger) 60 | 61 | vbf.AddRoute("GET /posts", mux, gCtx, func(w http.ResponseWriter, r *http.Request) { 62 | templates, _ := vbf.GetContext(KeyTemplates, r).(*template.Template) 63 | mdContent, err := vbf.LoadMarkdown("./content/posts.md", "dracula") 64 | if err != nil { 65 | fmt.Println(err.Error()) 66 | w.WriteHeader(500) 67 | return 68 | } 69 | vbf.ExecuteTemplate(w, templates, "root.html", map[string]interface{}{ 70 | "Title": "phillip england", 71 | "Content": template.HTML(mdContent), 72 | "ReqPath": r.URL.Path, 73 | "PostName": "[posts]", 74 | "SubText": "see what i've been learning about", 75 | "ImagePath": "/static/img/posts.webp", 76 | "DateWritten": "🎂 born on 12/9/2024", 77 | }) 78 | }, vbf.MwLogger) 79 | 80 | filepath.Walk("./content", func(path string, info fs.FileInfo, err error) error { 81 | if !strings.Contains(path, "/post/") { 82 | return nil 83 | } 84 | parts := strings.Split(path, "/") 85 | if len(parts) == 0 { 86 | return nil 87 | } 88 | lastPart := parts[len(parts)-1] 89 | moreParts := strings.Split(lastPart, ".") 90 | if len(moreParts) < 3 { 91 | return nil 92 | } 93 | index := moreParts[0] 94 | title := moreParts[1] 95 | extension := moreParts[2] 96 | if extension != "md" { 97 | return nil 98 | } 99 | imagePath := "/static/img/post/"+index+".webp" 100 | appPath := "/post/"+title 101 | vbf.AddRoute("GET "+appPath, mux, gCtx, func(w http.ResponseWriter, r *http.Request) { 102 | templates, _ := vbf.GetContext(KeyTemplates, r).(*template.Template) 103 | mdContent, err := vbf.LoadMarkdown("/"+path, "dracula") 104 | if err != nil { 105 | fmt.Println(err.Error()) 106 | w.WriteHeader(500) 107 | return 108 | } 109 | doc, err := goquery.NewDocumentFromReader(strings.NewReader(mdContent)) 110 | if err != nil { 111 | fmt.Println(err.Error()) 112 | w.WriteHeader(500) 113 | return 114 | } 115 | metaDataElm := doc.Find("#meta-data") 116 | var subText string 117 | var dob string 118 | metaDataElm.Find("*").Each(func(i int, sel *goquery.Selection) { 119 | key, _ := sel.Attr("key") 120 | if key == "subtext" { 121 | val, _ := sel.Attr("value") 122 | subText = val 123 | } 124 | if key == "dob" { 125 | val, _ := sel.Attr("value") 126 | dob = val 127 | } 128 | }) 129 | vbf.ExecuteTemplate(w, templates, "root.html", map[string]interface{}{ 130 | "Title": "phillip england", 131 | "Content": template.HTML(mdContent), 132 | "ReqPath": r.URL.Path, 133 | "PostName": "["+ strings.ReplaceAll(title, "-", " ") +"]", 134 | "SubText": subText, 135 | "ImagePath": imagePath, 136 | "DateWritten": "written " +dob, 137 | }) 138 | }, vbf.MwLogger) 139 | return nil 140 | }) 141 | 142 | err = vbf.Serve(mux, "8080") 143 | if err != nil { 144 | panic(err) 145 | } 146 | 147 | } 148 | -------------------------------------------------------------------------------- /content/post/10.rust-blanket-implementations.md: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 | 6 | ## Sandbox 7 | The sandbox for this article can be found [here](https://github.com/Phillip-England/sandbox-rust-blanket-implementations). 8 | 9 | My conversation with ChatGPT regarding the topics can be found [here](https://chatgpt.com/share/6766d509-bdb8-8005-a6eb-28fd226dd9e2). 10 | 11 | 12 | ## Discovery 13 | I wanted to get started working on a simple string parsing library in Rust to get my feet wet, and to build a low-level primitive tool I can bring with me into future projects. 14 | 15 | I basically want something that will allow me to do quick string operations I find myself performing frequently. 16 | 17 | [Here](https://github.com/phillip-england/purse) is a tiny Go implementation of the sort of thing I am discussing. 18 | 19 | Just some quick and dirty functions. 20 | 21 | ## Rust is Different 22 | Upon researching how to lay out the library and the best methods to ensure my users can work with both `String` and `&str`, I came across the idea of [blanket implementations](https://users.rust-lang.org/t/what-are-blanket-implementations/49904). 23 | 24 | In Go, it made sense to use functions because Go only has one string type. But since Rust has multiple string types, we need to think about how to incorporate multiple types in our library. 25 | 26 | I thought to myself, "Can I just create a trait and apply it to both `String` and `&str`?" 27 | 28 | The answer is, yes, using blanket implementations. 29 | 30 | ## Compare to Javascript 31 | I think looking at how Javascript and Rust handle this idea is important as the Javascript example is a bit more easy to digest, but it has the same underlying idea. 32 | 33 | In Javascript, let's imagine we want to take some object and extend it. Well, we could do something like this in the browser: 34 | 35 | ```js 36 | window.hello = () => { 37 | console.log('hello from the window'); 38 | } 39 | ``` 40 | 41 | Then, later on in our program, we may call the method like: 42 | ```js 43 | window.hello(); 44 | // hello from the window 45 | ``` 46 | 47 | Javascript is prototype-based and allows you to directly mutate core objects in the language. 48 | 49 | Rust does not provide us the ability to do this. So, in Rust, instead of directly altering core types, we can *extend* them using traits. 50 | 51 | ## Traits as Extensions 52 | Traits can be implemented on types in a unique variety of ways. 53 | 54 | NOTE TO SELF: Here I am again, discussing traits in Rust. Of all the features I've come across, traits seem to be one of the most fundamental building blocks of Rust. 55 | 56 | Let's first start by defining a trait we wish to implement on `&str` and `String`: 57 | 58 | ```rs 59 | pub trait SomeExtension { 60 | fn shout(&self) -> String; 61 | } 62 | ``` 63 | 64 | ## The AsRef Trait 65 | Rust has a trait named, `AsRef` which can be used to implement other traits on multiple core types at once. 66 | 67 | Before we dive into how to use `AsRef` to implement `SomeExtension` on both `&str` and `String`, we should take a closer look at how `AsRef` actually works. 68 | 69 | You can find the [docs](https://doc.rust-lang.org/std/convert/trait.AsRef.html) for `AsRef` here along with a [page](https://web.mit.edu/rust-lang_v1.25/arch/amd64_ubuntu1404/share/doc/rust/html/book/first-edition/borrow-and-asref.html) that works through the `AsRef` trait versus the `Borrow` trait. 70 | 71 | One of the first things I've noticed is how the Rust standard library has a whole module called `convert` which might be useful to take a deeper dive into later. It might be a good idea to just study the standard library and see how the core Rust team codes their own data types. 72 | 73 | NOTE TO SELF: Did you take the time to study the Rust standard library? 74 | 75 | ## Implementing Our Extension 76 | Now that we know `AsRef` can be used to implement traits on multiple types in a generic way, we can use it to implement `SomeExtension` on both `&str` and `String` types. 77 | 78 | To implement `SomeExtension`, write: 79 | ```rs 80 | impl SomeExtension for T 81 | where 82 | T: AsRef, 83 | { 84 | fn shout(&self) -> String { 85 | let s = self.as_ref(); 86 | format!("{}!", s.to_uppercase()) 87 | } 88 | } 89 | ``` 90 | 91 | Then, we can use our extension like so: 92 | ```rs 93 | fn main() { 94 | let some_str = "i am going to shout"; 95 | let shout = some_str.shout(); 96 | let some_string = String::from("i also shout"); 97 | let shout2 = some_string.shout(); 98 | println!("{} - {}", shout, shout2); 99 | } 100 | ``` 101 | 102 | As you can see, `&str` and `String` can now use the `shout` method from the `MyExtension` trait. 103 | 104 | ## Conclusion 105 | Again, I am finding myself in a place where I feel the need to really dive deep into traits in Rust. They keep coming up and their generic syntax is one of the most challenging things about reading the language and getting a good mental model of it. 106 | 107 | I am thinking I skim through [The Book](https://doc.rust-lang.org/book/) over the next few days and just get some more general exposure to Rust concepts and to get better at looking at the code. 108 | -------------------------------------------------------------------------------- /content/post/1.async-rust.md: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 | 6 | ## Article Sandbox 7 | I will be running experiments in my own editor and will be saving the repo [here](https://github.com/phillip-england/sandbox-async-rust). 8 | 9 | ## My Experience With Rust 10 | Rust is going to be my language for 2025. I've tinkered with a low-level networking framework called [Zeke](https://github.com/Phillip-England/zeke) and I would like to continue working on that project. 11 | 12 | Since working on Zeke, I've learned a bit more about programming on an interface level, and I think those skills will help a lot. 13 | 14 | Rust has a few concepts that are challenging to me such as lifetimes and the borrow checker. But the number one thing which caused me challenges when writing zeke was multi-threaded rust. 15 | 16 | If I had a good grapse on those concepts I would feel way more comfortable in the language. I think before I start diving into all the different multi-threaded types rust provides, I need to understand it's async model better. 17 | 18 | ## Let's Get Rusty 19 | Any time I think about rust, I think about Bodgen from [Let's Get Rusty](https://www.youtube.com/@letsgetrusty) on youtube. 20 | 21 | This [video](https://www.youtube.com/watch?v=K8LNPYNvT-U) is where I am going to start. 22 | 23 | ## Futures 24 | The first big takeaway is that `async` functions in rust are just a facade over a function which returns a `Future`. 25 | 26 | ```rs 27 | async fn my_function() { 28 | println!("I'm an async function!") 29 | } 30 | ``` 31 | 32 | is really just a fasade over: 33 | ```rs 34 | fn my_function() -> impl Future { 35 | println!("I'm an async function too!") 36 | } 37 | ``` 38 | 39 | I visited [the docs](https://doc.rust-lang.org/std/future/trait.Future.html) and it looks like `Future` is a trait which can be `polled` to completetion. 40 | 41 | When we use the `await` keyword, we are using a fasade over the `poll` method which is associated with the `Future` trait. 42 | 43 | ## Tokio 44 | Bodgen explains that `Futures` must be manually `polled` to completetion, which is cumbersom. But, that is why a runtime like [tokio](https://docs.rs/tokio/latest/tokio/) exists. 45 | 46 | In a language like Javascript, `Promises` are handled by the language underneath the hood. But in Rust, the `async` runtime is not included in the `std lib`, so options like `tokio` have emerged. 47 | 48 | I went ahead and added `tokio` to my `cargo.toml`: 49 | 50 | ```toml 51 | [package] 52 | name = "sandbox-async-rust" 53 | version = "0.1.0" 54 | edition = "2021" 55 | 56 | [dependencies] 57 | tokio = { version = "1", features = ["full"] } 58 | ``` 59 | 60 | ## Tokio Tasks 61 | Tasks are used to make our code run concurrently. Tasks a green threads and are non-blocking similar to gorountines. 62 | 63 | I discovered that `tokio` attempts to mimic the api provided by the Rust `std lib` for traditional threads. This makes it easy to swap between using tasks and traditional threads without a paradigm shift. 64 | 65 | ## Futures Are Lazy 66 | This is something to be noted. `Futures` are lazy in Rust which means we can collect our tasks and then call `await` on them later. If we do not `await` a task, then we do not experience any runtime cost for the task. 67 | 68 | This is different than other languages that use the `async/await` syntax to handle asyncronous code. 69 | 70 | ## Morphing Data Across Threads 71 | After a bit of playing around, I found myself wondering how to make changes to data across multiple threads. 72 | 73 | I ended up with something like this: 74 | ```rs 75 | async fn morph_data_across_threads() { 76 | let str = String::from("I will be morphed!"); 77 | let str_arc = Arc::new(Mutex::new(str)); 78 | let mut handles = vec![]; 79 | for i in 0..10 { 80 | let str_clone = Arc::clone(&str_arc); 81 | let task = tokio::spawn(async move { 82 | // lock the mutex to modify it 83 | let mut val = str_clone.lock().await; 84 | val.push_str(&i.to_string()); 85 | }); 86 | handles.push(task); 87 | } 88 | for task in handles { 89 | task.await.unwrap(); 90 | } 91 | // retrieve the final value as str is expended 92 | let final_str = str_arc.lock().await; 93 | println!("{}", final_str); 94 | } 95 | ``` 96 | 97 | And with this we see the introduction of a few core types I think I'll need to study. I see `Arc` and `Mutex`. We are calling `lock()` and these are concepts I think I'll need to get a better grasp on. 98 | 99 | The code runs, and I have a general understanding as to what is going on under the hood, but from what I understand about Rust, the way these types of constructs impact memory is important to get right. 100 | 101 | ## Smart Pointers 102 | After I started researching, I came across this [video](https://www.youtube.com/watch?v=CTTiaOo4cbY&t=548s) on smart pointers. 103 | 104 | ChatGPT says: "A smart pointer is an object that acts like a pointer but provides additional features to manage the ownership, lifecycle, and memory of dynamically allocated resources. It is typically used in programming languages like C++ and Rust to handle memory safely and efficiently." 105 | 106 | So, it looks like these constructs are called smart pointers. I am going to dig through them and do my best to get a surface level understanding of them. 107 | 108 | ## Box 109 | `Box` enables us to dictate that some data should be stored on the heap instead of the stack. 110 | 111 | We store things on the heap when we have no way of knowing the size of the data at compile time. You want to try and avoid storing things on the heap, but in certain situations it cannot be avoided. 112 | 113 | The video points out `Box` has 2 use-cases: 114 | 1. When we have a variable with a trait type which cannot be computer at compile time. 115 | 2. When we have a recursive data type whos fields include the struct it is derived from. 116 | 117 | NOTE TO SELF: Go back and finish "async rust" when you have a better understanding of traits and lifetimes. 118 | 119 | -------------------------------------------------------------------------------- /content/post/5.structs-traits-and-enums-in-rust.md: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 | 6 | ## Sandbox 7 | [Here](https://github.com/Phillip-England/sandbox-gpt-session-rust-0) is the repo where I coded around to get a bit familiar with these concepts. 8 | 9 | ## Overview 10 | The concepts discussed in this article have to do with modeling data in Rust. We will explore Structs, Enums, and Traits, all of which give us ways to define data and group behaviour amongst types. 11 | 12 | I used chatGPT to walk me through these topics. 13 | 14 | ## Structs 15 | Structs are a way to group related data, not much different than any other language which uses structs. One thing I noticed while reading [the book](https://doc.rust-lang.org/book/ch05-01-defining-structs.html) is that if you try to make their field of a struct a reference, then a lifetime will be required. This does not mean we cannot use shared data within a struct, but if we do use shared data within a struct, we will need to annotate the lifetime. 16 | 17 | You have multiple types of structs: 18 | - classic structs 19 | - unit structs 20 | - tuple structs 21 | 22 | Each other these have their own use-case. 23 | 24 | ## Classic Structs 25 | The name pretty much defines them. They are structs in the way you would normally think about in other languages. Like so: 26 | ```rs 27 | #[derive(Debug)] 28 | struct User { 29 | username: String, 30 | email: String, 31 | age: u32, 32 | active: bool, 33 | } 34 | ``` 35 | 36 | I include the `#[derive(Debug)]` derive macro, which is a construct I need to learn more about. However, I do know it allows us to print out our type like so: 37 | ```rs 38 | fn using_classic_structs() { 39 | let user = User { 40 | username: String::from("alice"), 41 | email: String::from("alice@gmail.com"), 42 | age: 30, 43 | active: true, 44 | }; 45 | println!("{:?}", user); 46 | } 47 | ``` 48 | 49 | ## Unit Structs 50 | Unit struct are struct with a specified type, but no real underlying data. Here is an example of two unit structs which are used as options: 51 | ```rs 52 | #[derive(Debug)] 53 | struct ReadOnly; 54 | #[derive(Debug)] 55 | struct WriteOnly; 56 | 57 | fn using_file_modes(_mode: T) { 58 | println!("{:?}", _mode); 59 | } 60 | ``` 61 | 62 | We can then make use of the options like: 63 | ```rs 64 | using_file_modes(ReadOnly{}); 65 | using_file_modes(WriteOnly{}); 66 | ``` 67 | 68 | Take note: the above function makes use of a trait bound `` which ensures any type passed into the function as `_mode: T` has the `Debug` derive macro enabled on itself. 69 | 70 | I also learned the unit struct can be used as a singleton. For example: 71 | 72 | ```rs 73 | struct Logger; 74 | impl Logger { 75 | fn log(&self, message: &str) { 76 | println!("Log: {}", message); 77 | } 78 | } 79 | fn using_singletons() { 80 | let logger = Logger{}; 81 | logger.log("I am using a singleton!"); 82 | } 83 | ``` 84 | 85 | ## Tuple Structs 86 | Tuple structs are when you have a set of unnamed data whose relationships are obvious, for example: 87 | ```rs 88 | #[derive(Debug)] 89 | struct Color(u8, u8, u8); 90 | ``` 91 | 92 | In this instance, we know the values are `r, b, g`, so there is no need to name them explicitly. 93 | 94 | We make use of the tuple struct like so: 95 | ```rs 96 | fn using_tuple_structs() { 97 | let color = Color(255, 0, 0); 98 | println!("{:?}", color); 99 | } 100 | ``` 101 | 102 | ## Traits 103 | Traits are behaviours that you can associate with a type. For example, here, we have a trait called "Describe" which has it's own default implementation: 104 | ```rs 105 | trait Describe { 106 | fn describe(&self) -> String { 107 | String::from("this is an object with no specific description") 108 | } 109 | } 110 | ``` 111 | 112 | We can then implement this trait on different types: 113 | ```rs 114 | struct Animal { 115 | name: String, 116 | } 117 | 118 | struct Vehicle { 119 | model: String, 120 | } 121 | 122 | struct Unknown; 123 | 124 | impl Describe for Animal { 125 | fn describe(&self) -> String { 126 | format!("This is an animal named: {}", self.name) 127 | } 128 | } 129 | 130 | impl Describe for Vehicle { 131 | fn describe(&self) -> String { 132 | format!("This is a vehicle with the model: {}", self.model) 133 | } 134 | } 135 | 136 | impl Describe for Unknown {} // uses the default implementation 137 | ``` 138 | 139 | To make use of these traits in a function: 140 | ```rs 141 | fn using_traits_to_describe() { 142 | let animal = Animal{ 143 | name: String::from("Tiger John"), 144 | }; 145 | println!("{}", animal.describe()); 146 | let car = Vehicle{ 147 | model: String::from("Honda"), 148 | }; 149 | println!("{}", car.describe()); 150 | let unknown = Unknown{}; 151 | println!("{}", unknown.describe()); 152 | } 153 | ``` 154 | 155 | ## Enums 156 | Enums allow us to define a type which may be one of many different variants. For example, here is an enum named, `Message`: 157 | ```rs 158 | enum Message { 159 | Quit, 160 | Move { x: i32, y: i32 }, 161 | Write(String), 162 | ChangeColor(u8, u8, u8), 163 | } 164 | ``` 165 | 166 | When we use an enum, we match it against all of it's possible variants. Then depending on which variant we get, we perform some actions. For example, here is a function which take sin a `Message` and prints out a statement depending on which variant we get: 167 | ```rs 168 | fn using_enum_with_match_arm(message: Message) { 169 | match message { 170 | Message::Quit => println!("quitting!"), 171 | Message::Move { x, y } => println!("Move to ({}, {})", x, y), 172 | Message::Write(text) => println!("Write message: {}", text), 173 | Message::ChangeColor(r, b, g) => println!("Change color to RBG({}, {}, {})", r, b, g), 174 | } 175 | } 176 | ``` 177 | 178 | Notice, each variant can have it's own unique set of data. The variants do not have to share data between each other. This is what makes enums unique. Also, the match statements must cover possible outcome. 179 | 180 | When we want to call the above function, we can do so like: 181 | ```rs 182 | using_enum_with_match_arm(Message::ChangeColor(222, 222, 201)); 183 | ``` 184 | 185 | 186 | 187 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/PuerkitoBio/goquery v1.8.1 h1:uQxhNlArOIdbrH1tr0UXwdVFgDcZDrZVdcpygAcwmWM= 2 | github.com/PuerkitoBio/goquery v1.8.1/go.mod h1:Q8ICL1kNUJ2sXGoAhPGUdYDJvgQgHzJsnnd3H7Ho5jQ= 3 | github.com/a-h/templ v0.2.771 h1:4KH5ykNigYGGpCe0fRJ7/hzwz72k3qFqIiiLLJskbSo= 4 | github.com/a-h/templ v0.2.771/go.mod h1:lq48JXoUvuQrU0VThrK31yFwdRjTCnIE5bcPCM9IP1w= 5 | github.com/alecthomas/assert/v2 v2.7.0 h1:QtqSACNS3tF7oasA8CU6A6sXZSBDqnm7RfpLl9bZqbE= 6 | github.com/alecthomas/assert/v2 v2.7.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= 7 | github.com/alecthomas/chroma/v2 v2.2.0/go.mod h1:vf4zrexSH54oEjJ7EdB65tGNHmH3pGZmVkgTP5RHvAs= 8 | github.com/alecthomas/chroma/v2 v2.14.0 h1:R3+wzpnUArGcQz7fCETQBzO5n9IMNi13iIs46aU4V9E= 9 | github.com/alecthomas/chroma/v2 v2.14.0/go.mod h1:QolEbTfmUHIMVpBqxeDnNBj2uoeI4EbYP4i6n68SG4I= 10 | github.com/alecthomas/repr v0.0.0-20220113201626-b1b626ac65ae/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8= 11 | github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc= 12 | github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= 13 | github.com/andybalholm/cascadia v1.3.1 h1:nhxRkql1kdYCc8Snf7D5/D3spOX+dBgjA6u8x004T2c= 14 | github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA= 15 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 16 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 17 | github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= 18 | github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= 19 | github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI= 20 | github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= 21 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 22 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 23 | github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= 24 | github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= 25 | github.com/phillip-england/vbf v0.0.27 h1:/1HCzLpawTSFu9JO+CXazGf/B7ObrXmK9oC7xmytY/k= 26 | github.com/phillip-england/vbf v0.0.27/go.mod h1:u+U6GX5Vax1vZX79UNuRwEvvuKQO93cZlxIK7KYDLbQ= 27 | github.com/phillip-england/vbf v0.0.28 h1:T+8KdN18VY+13SkZM1OQ/lB43YQi26xaO7nDBJhK+Yg= 28 | github.com/phillip-england/vbf v0.0.28/go.mod h1:u+U6GX5Vax1vZX79UNuRwEvvuKQO93cZlxIK7KYDLbQ= 29 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 30 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 31 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 32 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 33 | github.com/yuin/goldmark v1.4.15/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 34 | github.com/yuin/goldmark v1.7.4 h1:BDXOHExt+A7gwPCJgPIIq7ENvceR7we7rOS9TNoLZeg= 35 | github.com/yuin/goldmark v1.7.4/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= 36 | github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc h1:+IAOyRda+RLrxa1WC7umKOZRsGq4QrFFMYApOeHzQwQ= 37 | github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc/go.mod h1:ovIvrum6DQJA4QsJSovrkC4saKHQVs7TvcaeO8AIl5I= 38 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 39 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 40 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 41 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 42 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 43 | golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 44 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 45 | golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 46 | golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= 47 | golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= 48 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 49 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 50 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 51 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 52 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 53 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 54 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 55 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 56 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 57 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 58 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 59 | golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= 60 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 61 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 62 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 63 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 64 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 65 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 66 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 67 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 68 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 69 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 70 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 71 | -------------------------------------------------------------------------------- /content/post/8.rust-lifetimes.md: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 | 6 | ## Sandbox 7 | The repo where I tested out these ideas is [here](https://github.com/Phillip-England/sandbox-rust-lifetimes). 8 | 9 | The conversation I had with ChatGPT about the topic is [here](https://chatgpt.com/share/6762cc56-3184-8005-87ec-841eaf4826ba). I do a lot of my personal study with ChatGPT, so the conversation is worth a peek as the majority of this content is derived from my studies there. 10 | 11 | ## Afterthoughts 12 | As I was writing this article, I noticed generics are really important regarding lifetimes. I have a pretty solid understanding of generics already, but I think going back and doing a post about them would be good after this. 13 | 14 | NOTE TO SELF: Did you write a post about generics in Rust? 15 | 16 | ## It's About Time 17 | I've been avoiding this topic for a bit because of all the concepts in Rust, lifetimes are the one I am most likely to just rely on compile-time errors to help me manage and correct. 18 | 19 | In short, when I write lifetimes, I have no idea what I am doing. 20 | 21 | The only thing I can remember is a quote from [Tristram Oaten](https://github.com/0atman/noboilerplate) on his YouTube channel, [No Boilerplate](https://www.youtube.com/c/NoBoilerplate). 22 | 23 | He said, "..lifetimes let us know **when** our data is." 24 | 25 | So, let's checkout [The Book](https://doc.rust-lang.org/rust-by-example/scope/lifetime.html) and see what it has to say on lifetimes. 26 | 27 | ## The Book on Lifetimes 28 | Right off the bat, we read a lifetime is, "..a construct the compiler (or more specifically, its borrow checker) uses to ensure all borrows are valid." 29 | 30 | Despite not feeling confident about lifetimes, I *am* pretty familiar with the borrow-checker. I used Rust pretty early on in my programming journey (which is why I've revisted the language 3 times now). Because of this, I've actually inherited some good coding practices regarding how I think about handling data within a program. 31 | 32 | The book also points out that it can be easy to confuse lifetimes and scopes. This is because scopes and lifetimes are closely related. 33 | 34 | The real thing I notice here is that it is just important to know when your data is valid in your Rust programs. 35 | 36 | I think a pattern I came across when I first started Rust is the idea that you can pass a variable into a function, us it within, and then return it if you need it back. 37 | 38 | The Book has a page on how to explicitly annotate lifetimes using `some_var<'a>` 39 | 40 | Let me see what ChatGPT has to say about the topic. 41 | 42 | ## ChatGPT on Lifetimes 43 | ChatGPT makes it clear lifetimes are used to ensure references are valid for the shortest amount of time possible. They enable us to tell the compiler when a reference is no longer accessible. 44 | 45 | Here is an example where we try to make use of a `&x` when it is no longer available: 46 | ```rs 47 | fn dangling_reference() { 48 | let r; 49 | { 50 | let x = 5; 51 | r = &x; 52 | } 53 | // println!("{}", r); 💥 Dangling reference! 54 | } 55 | ``` 56 | 57 | ## Why Does This Matter? 58 | Lifetimes become a practical problem in your code when you start passing references around. 59 | 60 | Take the following function for example: 61 | ```rs 62 | fn longest<'a>(s1: &'a str, s2: &'a str) -> &'a str { 63 | if s1.len() > s2.len() { 64 | s1 65 | } else { 66 | s2 67 | } 68 | } 69 | ``` 70 | 71 | In this function, we use `-> &'a str` to state: "The returned reference will be valid for the duration of lifetime 'a" 72 | 73 | What is the duration of lifetime `'a` you might ask? 74 | 75 | ## The Shortest Lifetime 76 | If a more than one reference is passed into a function, and the function returns a new reference, the new reference will mirror the shortest lifetime of the input references. 77 | 78 | So, when we say state, `(s1: &'a str, s2: &'a str) -> &'a str`, what we are really saying is: 79 | 80 | "The lifetime of the return value will match s1 if s1 has the shortest lifetime, or it will match s2 if s2 has the shortest lifetime." 81 | 82 | This means when we are in a scenario where a reference is being passed from one location to another, we need to be mindful of all the data points it comes into contact with. 83 | 84 | If we pipe a reference into a function, and that function outputs another reference, our data is now "linked" from the compiler's perspective. 85 | 86 | ## Every Reference Has a Lifetime 87 | One thing to note is that all the references in a Rust program have a lifetime, even if it is not explicitly annotated. Sometimes, Rust can even infer the lifetime of a return value. These are called the [Elision Rules](https://doc.rust-lang.org/nomicon/lifetime-elision.html). 88 | 89 | In short, elision rules allow us to forgo explicitly writing out our lifetime annotations. 90 | 91 | A good rule of thumb is if a function only has one reference passed into it, then the annotation can be excluded because the return value will always match that of the input reference. 92 | 93 | I would take a peak at the Elision Rules though because I am not diving into all the minute details here. 94 | 95 | ## Common Scenarios 96 | Here are a few common scenarios where lifetimes are found: 97 | 98 | ### Structs 99 | You may end up with a struct which has lifetime annotations like so: 100 | ```rs 101 | struct Car<'a> { 102 | model: &'a str 103 | } 104 | ``` 105 | 106 | ### Functions 107 | As already discussed, functions may have lifetime annotations: 108 | ```rs 109 | fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { 110 | if x.len() > y.len() { 111 | x 112 | } else { 113 | y 114 | } 115 | } 116 | ``` 117 | 118 | ### In Trait Bounds 119 | We can also use lifetimes when using trait bounds: 120 | ```rs 121 | fn print_with_lifetime<'a, T>(item: &'a T) 122 | where 123 | T: std::fmt::Display + 'a, 124 | { 125 | println!("{}", item); 126 | } 127 | ``` 128 | 129 | Of all the things I've listed so far, ^ that one looks the most archaic to me. I understand traits and their purpose, however, I need to brush up on actually using traits in real world scenarios. 130 | 131 | NOTE TO SELF: Did you do a deep dive on trait bounds in Rust? 132 | 133 | ## Lifetimes as Timelines 134 | ChatGPT suggested thinking of lifetimes in terms of timelines. When approach a Rust program, we need to take special care to ensure we are being proactive about how we are thinking about our references. 135 | 136 | Remember, each reference has it's own timeline, and if you borrow a reference, you need to make sure it lives long enough to be borrowed in the first place. 137 | 138 | ## Conclusion 139 | I think this first look at lifetimes is good, but I will definitely be back on this topic. All in all, lifetimes are really all about making sure that references are available for only the time they are needed and no more. 140 | 141 | Lifetimes are Rusts way of avoiding common a pitfall like dangling references. 142 | 143 | I have heard some complain about lifetimes "coloring" functions. Kind of similar to how when you mark a function `async` in typescript, your whole codebase ends up being marked with `async`. 144 | 145 | I will be back on this one. 146 | -------------------------------------------------------------------------------- /content/post/9.rust-generics.md: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 | 6 | ## Sandbox 7 | The sandbox where I tested out these ideas is [here](https://github.com/phillip-england/sandbox-rust-generics). 8 | 9 | The conversation I had with ChatGPT regarding the topic is [here](https://chatgpt.com/share/67641768-87cc-8005-b3ab-8a17f19a5c62). 10 | 11 | ## Generics Are Used Everywhere 12 | Yesterday, I was learning about lifetimes and I kept coming across generic notations. I understood most of them but wanted to do a deep dive to make sure I do not miss anything important or obscure. 13 | 14 | One thing is for sure, generics are used extensivly in Rust code. I see the notations all over the place and they have a tendancy to make the code feel "full" and a bit harder to navigate. 15 | 16 | I think having a solid understanding of generics will help my brain feel more at ease when scanning Rust code. 17 | 18 | ## Avoiding Code Duplication 19 | Generics are all about avoiding code duplication. Since Rust is a statically typed language, you end up in scenarios where you might have a similar function for multiple types. 20 | 21 | Take these two functions: 22 | ```rs 23 | fn add_strs(s1: String, s2: String) -> String { 24 | s1 + &s2 25 | } 26 | 27 | fn add_nums(x: i32, y: i32) -> i32 { 28 | x + y 29 | } 30 | ``` 31 | 32 | They can be converted into this generic function: 33 | ```rs 34 | fn add_generic>(x: T, y: T) -> T { 35 | x + y 36 | } 37 | ``` 38 | 39 | I learned a few things while drafting out these above examples. Let's take note of this tidbit right here `>`. 40 | 41 | ## Rust Constrains 42 | OK, this is an idea I like and think is a core theme found throughout Rust. Rust is a constraining language. It is trying to place tons of caps and limits on what you can do and how you can manage the flow of data throughout your program. 43 | 44 | When we say `>`, we are limiting the passed in types to types which implement the Add trait from the standard library. 45 | 46 | ChatGPT made a good point on this. If we do not limit the passed in types, we may end up in a situation where the function does not return a value of type `T`. 47 | 48 | In short, these type constraints help us to be confident that the function is going to return the type of data that it says it will return. 49 | 50 | Learning more about traits and trait bounds is a sure way to get a better grip on generics as they are related closely together in this way. 51 | 52 | ## Constraints Are Contextual 53 | Rust may require a function or method be constrained depending on how the inputs are used. For example, in the above function `add_generic`, we see that we are operating on the inputs using the addition symbol `+`. Because of this context, Rust now requires a constraint. 54 | 55 | Had we never added the values together, the constraint would no longer be needed. 56 | 57 | Rust provides a multitude of traits to constrain your generic types with depending on how they are used within your code. 58 | 59 | This is all possible because the Rust compiler is so thorough. When we complain about Rust having a slow compiler, these are the types of checks which have the potential to slow things down. 60 | 61 | All in the name of safety, baby. 62 | 63 | ## Implementing Add on a Custom Type 64 | So, what if we want to use a custom type with our `add_generic` function? Well, we can implement the `Add` trait and tell Rust how they type ought to be used when used in conjunction with addition operator. 65 | 66 | First, our custom type: 67 | ```rs 68 | #[derive(Debug)] 69 | struct Point { 70 | x: i32, 71 | y: i32, 72 | } 73 | ``` 74 | 75 | Then, we implement the `Add` trait: 76 | ```rs 77 | impl std::ops::Add for Point { 78 | type Output = Point; 79 | 80 | fn add(self, other: Point) -> Point { 81 | Point { 82 | x: self.x + other.x, 83 | y: self.y + other.y, 84 | } 85 | } 86 | } 87 | ``` 88 | 89 | Finally, we can add our points: 90 | ```rs 91 | let p1 = Point {x: 10, y: 10}; 92 | let p2 = Point {x: 10, y: 10}; 93 | let p3 = add_generic(p1, p2); 94 | dbg!(p3); 95 | ``` 96 | 97 | ## Other Generic Contexts 98 | As I was looking into this, I found it useful to learn about other contexts where our generic types might need to be constrained. After asking ChatGPT, here is what I came up with: 99 | 100 | ### Arithmetic and Mathematical Traits 101 | - `std::ops::Add`: For + operator. 102 | - `std::ops::Sub`: For - operator. 103 | - `std::ops::Mul`: For * operator. 104 | - `std::ops::Div`: For / operator. 105 | - `std::ops::Rem`: For % operator. 106 | - `std::ops::Neg`: For unary - operator. 107 | - `std::ops::Shl` / `std::ops::Shr`: For bitwise left (<<) and right (>>) shifts. 108 | 109 | ### Comparison Traits 110 | - `std::cmp::PartialEq`: For == and !=. 111 | - `std::cmp::Eq`: For strict equality (used in HashMap keys, requires PartialEq). 112 | - `std::cmp::PartialOrd`: For <, <=, >, and >=. 113 | - `std::cmp::Ord`: For total ordering (requires PartialOrd and Eq). 114 | 115 | ### Iteration and Collection Traits 116 | - `std::iter::Iterator`: For types that can produce a sequence of values. 117 | - `std::iter::IntoIterator`: For types that can be converted into an iterator. 118 | - `std::iter::Extend`: For extending a collection with an iterator. 119 | - `std::iter::FromIterator`: For constructing a collection from an iterator. 120 | 121 | ### Borrowing and Ownership Traits 122 | - `std::borrow::Borrow`: For generic borrowing. 123 | - `std::borrow::ToOwned`: For creating an owned version of a borrowed value (e.g., String from &str). 124 | - `std::convert::AsRef`: For converting a value to a reference of another type. 125 | - `std::convert::AsMut`: For converting a value to a mutable reference of another type. 126 | 127 | ### Default and Debug Traits 128 | - `std::default::Default`: For providing a default value. 129 | - `std::fmt::Debug`: For formatting with {:?}. 130 | - `std::fmt::Display`: For formatting with {}. 131 | 132 | ### Trait Object-Specific Traits 133 | - `std::any::Any`: For working with types at runtime. 134 | - `std::marker::Send`: For types that are safe to transfer between threads. 135 | - `std::marker::Sync`: For types that are safe to reference from multiple threads. 136 | 137 | ### Functional Programming Traits 138 | - `Fn`: For closures that do not mutate state. 139 | - `FnMut`: For closures that mutate state. 140 | - `FnOnce`: For closures that consume their environment. 141 | 142 | ### I/O Traits 143 | - `std::io::Read`: For types that can read data. 144 | - `std::io::Write`: For types that can write data. 145 | - `std::io::Seek`: For types that can seek within a stream. 146 | - `std::io::BufRead`: For buffered readers. 147 | 148 | ### Common Combinations 149 | - `Clone + PartialEq`: For types that can be cloned and compared. 150 | - `Iterator + Debug`: For iterators that can also be debugged. 151 | - `Send + Sync`: For thread-safe types. 152 | 153 | ## Conclusion 154 | Generics in Rust are extremely important and a lack of understanding of them can result in code feeling full and archaic. Generics are tied directly to traits and are used in conjunction with them to ensure that return values are of the intended type. 155 | 156 | All in all, when we are writing generic functions, we need to be mindful and aware that the context and way in which we use the input parameters will dictate the way in which we need to constrain them. 157 | -------------------------------------------------------------------------------- /content/post/6.rust-hyper.md: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 | 6 | ## Sandbox 7 | The sandbox for this article is in this [repo](https://github.com/phillip-england/sandbox-rust-hyper). 8 | 9 | ## Overview 10 | My goal is to learn about how to use [hyper](https://hyper.rs/), but more importantly, I want to take a close look at how to set up middleware. 11 | 12 | In this article, I will be working my way through hyper and trying to understand how to use it to build a web application. 13 | 14 | ## The Basics 15 | Hyper offers some pretty good docs to get going. I ended up with something like this off the bat: 16 | 17 | ```rs 18 | #[tokio::main] 19 | async fn main() -> Result<(), Box> { 20 | let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); 21 | let listener = TcpListener::bind(addr).await?; 22 | loop { 23 | let (stream, _) = listener.accept().await?; 24 | let io = TokioIo::new(stream); 25 | tokio::task::spawn(async move { 26 | if let Err(err) = http1::Builder::new() 27 | .serve_connection(io, service_fn(hello)) 28 | .await 29 | { 30 | eprintln!("Error serving connection: {:?}", err); 31 | } 32 | }); 33 | } 34 | } 35 | 36 | async fn hello(_: Request) -> Result>, Infallible> { 37 | Ok(Response::new(Full::new(Bytes::from("Hello, World!")))) 38 | } 39 | ``` 40 | 41 | In the above example, the function `hello` is basically acting as a "catch-all" for all incoming requests. So, from within `hello`, we can get the request, determine the path, and perform some action depending on the path. 42 | 43 | This is exactly what we want. 44 | 45 | ## What About These Types 46 | I think it is worth it to take some time to see how `hyper` implements the `Request` type. 47 | 48 | I found its documentation [here](https://docs.rs/http/1.1.0/http/request/struct.Request.html). 49 | 50 | We can do this to get the request method: 51 | ```rs 52 | async fn hello(req: Request) -> Result>, Infallible> { 53 | 54 | let method = req.method(); 55 | println!("{}", method); 56 | 57 | Ok(Response::new(Full::new(Bytes::from("Hello, World!")))) 58 | } 59 | ``` 60 | 61 | and then I ended up here: 62 | ```rs 63 | async fn hello(req: Request) -> Result>, Infallible> { 64 | 65 | let method = req.method(); 66 | let uri = req.uri(); 67 | 68 | let req_data = format!("URI: {} 69 | METHOD: {}", uri, method); 70 | println!("{}", req_data); 71 | 72 | Ok(Response::new(Full::new(Bytes::from(req_data)))) 73 | } 74 | ``` 75 | 76 | OK, so we can set up basic routing pretty easily. 77 | 78 | ## Asking ChatGPT 79 | I asked ChatGPT to help me understand these types a bit more. I said, "what are we saying when we put `` next to `Request`", here is the response: 80 | 81 | ### What is hyper::body::Incoming? 82 | `hyper::body::Incoming` is the specific type used for the body of the request when it's received by the server. 83 | 84 | It represents a streaming body of data that is incoming from a client. This body type supports asynchronous operations, allowing the server to handle large or streamed payloads efficiently. 85 | 86 | By specifying Incoming as the type parameter, we're saying: 87 | 88 | "The Request object contains a body that can be streamed or processed incrementally as it arrives." 89 | 90 | ## What Other Types Can The Request Be? 91 | I dug a bit deeper and learned a bunch of interesting stuff about how the `Request` type can be used. 92 | 93 | Here is a [link](https://chatgpt.com/share/67600631-71ac-8005-b4d8-a257c5116ed0) to the full conversation. 94 | 95 | One tid-bit I'd like to place here is: 96 | ```bash 97 | 5. Custom Types 98 | 99 | Description: Any custom type that implements the HttpBody trait or is converted from the Request body. 100 | 101 | Use Case: For structured data like JSON or XML, you can use types like serde_json::Value or even your own deserialized structs. 102 | ``` 103 | 104 | I think this line of thinking is going to be common around Rust. 105 | 106 | ## Basic Routing 107 | The docs walk you through how to set up routing using a match table and it looks pretty nice. I wanted to go a simple path and build up. Here is what I got working for catching 404s: 108 | 109 | ```rs 110 | async fn hello(req: Request) -> Result>, Infallible> { 111 | 112 | 113 | if req.method() == "GET" && req.uri().path() == "/" { 114 | return Ok(Response::new(Full::new(Bytes::from("Hello, World!")))); 115 | } 116 | 117 | Ok(Response::new(Full::new(Bytes::from("404 not found")))) 118 | 119 | } 120 | ``` 121 | 122 | But there is a problem: we are not properly setting our status codes or headers. To fix this, we will have to start creating and modeling `Responses`. 123 | 124 | ## Crafting Responses 125 | I found myself wanting to be able to make changes to my responses prior to sending them. Hyper offers a page on how to make a routing table. I made a few changes to the example and ended up with this: 126 | 127 | ```rs 128 | // function to catch all incoming requests 129 | async fn catch_all(req: Request) -> Result>, hyper::Error> { 130 | match req.uri().path() { 131 | "/" => { 132 | match req.method() { 133 | &Method::GET => { 134 | let mut res = Response::new(box_response("

Hello, World!

")); 135 | res.headers_mut().insert("Content-Type", HeaderValue::from_static("text/html")); 136 | return Ok(res) 137 | }, 138 | _ => { 139 | let mut invalid_method = Response::new(box_response("invalid method")); 140 | *invalid_method.status_mut() = StatusCode::METHOD_NOT_ALLOWED; 141 | return Ok(invalid_method) 142 | } 143 | } 144 | 145 | }, 146 | _ => { 147 | let mut not_found = Response::new(box_response("

404 not found

")); 148 | *not_found.status_mut() = StatusCode::NOT_FOUND; 149 | not_found.headers_mut().insert("Content-Type", HeaderValue::from_static("text/html")); 150 | return Ok(not_found) 151 | } 152 | } 153 | } 154 | 155 | // utility function to box up our response body 156 | fn box_response>(chunk: T) -> BoxBody { 157 | Full::new(chunk.into()) 158 | .map_err(|never| match never {}) 159 | .boxed() 160 | } 161 | ``` 162 | 163 | That's a lot of code, so let's breakdown some things I learned. 164 | 165 | ### Setting Response headers 166 | Response headers are set using the `Response.headers_mut().insert()` method. When using this, to pass strings in as the header value, I had to use `HeaderValue::from_static()` and that allowed me to properly set the headers on my response types. Here is an example: 167 | 168 | ```rs 169 | res.headers_mut().insert("Content-Type", HeaderValue::from_static("text/html")); 170 | ``` 171 | 172 | ### Catch-All In Match Expressions 173 | I found it useful to use a catch-all for the match expressions when checking which method a request had as it came into the server. 174 | 175 | This type of pattern feels very Go-ish. I like checking the path first, and then dealing with the method. This approach gave me a clean way to do this. 176 | 177 | This is what I mean: 178 | 179 | ```rs 180 | "/" => { 181 | match req.method() { 182 | &Method::GET => { 183 | let mut res = Response::new(box_response("

Hello, World!

")); 184 | res.headers_mut().insert("Content-Type", HeaderValue::from_static("text/html")); 185 | return Ok(res) 186 | }, 187 | _ => { 188 | let mut invalid_method = Response::new(box_response("invalid method")); 189 | *invalid_method.status_mut() = StatusCode::METHOD_NOT_ALLOWED; 190 | return Ok(invalid_method) 191 | } 192 | } 193 | }, 194 | ``` 195 | 196 | ### Setting Status Codes 197 | Very similar to headers, status codes are set using a method, `Response.status_mut()` and we use it like: 198 | 199 | ```rs 200 | let mut not_found = Response::new(box_response("

404 not found

")); 201 | *not_found.status_mut() = StatusCode::NOT_FOUND; 202 | ``` 203 | 204 | ## Conclusion 205 | I think hyper is a great way to get going with building web applications in Rust, especially if you are interested in creating libraries or frameworks. It is non-invasive and extremely minimal. All it really does is provide a few core types to make handling requests and responses easier. That is really the whole deal. 206 | 207 | I dug into middleware and I think I will do a whole post dedicated to setting up middleware in hyper. It looks like they recently did an overhaul and stopped using `tower` which is how they handled middleware previously. 208 | 209 | All in all, I am going to learn this tool a bit deeper and come back with more content regarding middleware and route management. 210 | 211 | I also think I need to learn a bit more about how to think in Rust. I came across this snippet of code and it made me realize I need to brush up on iterators in Rust: 212 | 213 | ```rs 214 | let frame_stream = req.into_body().map_frame(|frame| { 215 | let frame = if let Ok(data) = frame.into_data() { 216 | // Convert every byte in every Data frame to uppercase 217 | data.iter() 218 | .map(|byte| byte.to_ascii_uppercase()) 219 | .collect::() 220 | } else { 221 | Bytes::new() 222 | }; 223 | 224 | Frame::data(frame) 225 | }); 226 | ``` 227 | 228 | NOTE TO SELF: Did you do an article about iterators in Rust? 229 | 230 | 231 | -------------------------------------------------------------------------------- /static/js/index.js: -------------------------------------------------------------------------------- 1 | class TwMarkdown extends HTMLElement { 2 | constructor() { 3 | super(); 4 | } 5 | 6 | connectedCallback() { 7 | const children = Array.from(this.children).map((child) => 8 | child.cloneNode(true) 9 | ); 10 | this.innerHTML = ""; 11 | children.forEach(this.styleElement); 12 | children.forEach((child) => this.appendChild(child)); 13 | } 14 | 15 | styleElement = (element) => { 16 | const nodeName = element.nodeName.toLowerCase(); 17 | 18 | switch (nodeName) { 19 | case "pre": 20 | element.classList.add( 21 | "custom-scroll", 22 | "p-4", 23 | "text-sm", 24 | "overflow-x-auto", 25 | "rounded", 26 | "mb-4", 27 | ); 28 | break; 29 | case "h1": 30 | element.classList.add( 31 | "font-bold", 32 | "text-3xl", 33 | "pb-4", 34 | ); 35 | break; 36 | case "h2": 37 | element.classList.add( 38 | "font-bold", 39 | "text-2xl", 40 | "pb-4", 41 | "pt-4", 42 | "border-t", 43 | "dark:border-gray-800", 44 | ); 45 | break; 46 | case "h3": 47 | element.classList.add( 48 | "font-bold", 49 | "text-xl", 50 | "mt-6", 51 | "mb-4", 52 | ); 53 | break; 54 | case "p": 55 | element.classList.add( 56 | "text-sm", 57 | "leading-6", 58 | "mb-4", 59 | ); 60 | break; 61 | case "ul": 62 | element.classList.add( 63 | "pl-6", 64 | "mb-4", 65 | "list-disc", 66 | ); 67 | break; 68 | case "ol": 69 | element.classList.add( 70 | "pl-6", 71 | "mb-4", 72 | "list-decimal", 73 | ); 74 | break; 75 | case "li": 76 | element.classList.add( 77 | "mb-2", 78 | "text-sm", 79 | ); 80 | break; 81 | case "blockquote": 82 | element.classList.add( 83 | "ml-4", 84 | "pl-4", 85 | "border-l-4", 86 | "border-gray-300", 87 | "italic", 88 | "text-gray-700", 89 | ); 90 | break; 91 | case "code": 92 | if (element.parentElement.nodeName.toLowerCase() !== "pre") { 93 | element.classList.add( 94 | "font-mono", 95 | "px-1", 96 | "rounded", 97 | "text-sm", 98 | "border", 99 | "border-gray-200", 100 | "dark:border-gray-800" 101 | ); 102 | } 103 | break; 104 | case "hr": 105 | element.classList.add( 106 | "border-t", 107 | "border-gray-300", 108 | "dark:border-gray-800", 109 | "my-4", 110 | ); 111 | break; 112 | case "a": 113 | element.classList.add( 114 | "text-blue-800", 115 | "dark:text-blue-500", 116 | "underline", 117 | ); 118 | break; 119 | case "img": 120 | element.classList.add( 121 | "max-w-full", 122 | "h-auto", 123 | "rounded", 124 | "my-4", 125 | ); 126 | break; 127 | } 128 | 129 | // Recursively style child elements 130 | Array.from(element.children).forEach(this.styleElement); 131 | }; 132 | } 133 | 134 | class RandomBeads extends HTMLElement { 135 | connectedCallback() { 136 | this.classList.add("flex", "flex-row", "gap-2"); 137 | const count = this.getAttribute("count"); 138 | const countInt = parseInt(count); 139 | if (isNaN(countInt)) { 140 | console.error( 141 | ' requires an integer in the "count" attribute', 142 | ); 143 | return; 144 | } 145 | this.beads = []; 146 | let size = 4; 147 | for (let i = 0; i < countInt; i++) { 148 | const bead = document.createElement("div"); 149 | bead.classList.add("rounded-full", "transition-colors", "duration-1000"); 150 | const initialColors = this.generateRandomColor(); 151 | bead.style.height = `${size}px`; 152 | bead.style.width = `${size}px`; 153 | bead.style.backgroundColor = 154 | `rgb(${initialColors.r}, ${initialColors.g}, ${initialColors.b})`; 155 | 156 | this.appendChild(bead); 157 | this.beads.push(bead); 158 | size += 1; 159 | } 160 | this.colorIntervalId = setInterval(() => this.transitionBeadColors(), 2000); 161 | } 162 | generateRandomColor() { 163 | return { 164 | r: Math.floor(Math.random() * 256), 165 | g: Math.floor(Math.random() * 256), 166 | b: Math.floor(Math.random() * 256), 167 | }; 168 | } 169 | transitionBeadColors() { 170 | this.beads.forEach((bead) => { 171 | const newColors = this.generateRandomColor(); 172 | bead.style.backgroundColor = 173 | `rgb(${newColors.r}, ${newColors.g}, ${newColors.b})`; 174 | }); 175 | } 176 | disconnectedCallback() { 177 | if (this.colorIntervalId) { 178 | clearInterval(this.colorIntervalId); 179 | } 180 | } 181 | } 182 | 183 | class TheBlinker extends HTMLElement { 184 | constructor() { 185 | super(); 186 | this.attachShadow({ mode: "open" }); 187 | } 188 | connectedCallback() { 189 | const rate = parseInt(this.getAttribute("rate") || "1000"); 190 | const blinkElement = document.createElement("span"); 191 | blinkElement.textContent = this.textContent || "_"; 192 | const style = document.createElement("style"); 193 | style.textContent = ` 194 | @keyframes blink { 195 | 0%, 100% { opacity: 1; } 196 | 50% { opacity: 0; } 197 | } 198 | span { 199 | animation: blink ${rate}ms step-end infinite; 200 | } 201 | `; 202 | this.shadowRoot.appendChild(style); 203 | this.shadowRoot.appendChild(blinkElement); 204 | } 205 | } 206 | 207 | class TitleLinks extends HTMLElement { 208 | constructor() { 209 | super(); 210 | } 211 | 212 | connectedCallback() { 213 | const targetSelector = this.getAttribute("target"); 214 | const linkClass = this.getAttribute("link-class"); 215 | const linkWrapperClass = this.getAttribute("link-wrapper-class"); 216 | const linkClasses = linkClass.split(" "); 217 | const linkWrapperClasses = linkWrapperClass.split(" "); 218 | const offset = parseInt(this.getAttribute("offset"), 10) || 0; 219 | 220 | const targetElement = document.querySelector(targetSelector); 221 | if (!targetElement) { 222 | console.error(`Target element "${targetSelector}" not found.`); 223 | return; 224 | } 225 | 226 | const headings = targetElement.querySelectorAll("h1, h2, h3, h4, h5, h6"); 227 | 228 | headings.forEach((heading) => { 229 | if (heading.id) { 230 | const linkItem = document.createElement("div"); 231 | linkWrapperClasses.forEach((linkWrapperClass) => { 232 | linkItem.classList.add(linkWrapperClass); 233 | }); 234 | const link = document.createElement("a"); 235 | linkClasses.forEach((linkClass) => { 236 | link.classList.add(linkClass); 237 | }); 238 | link.classList.add("title-link"); 239 | link.href = `#${heading.id}`; 240 | link.textContent = heading.textContent; 241 | linkItem.appendChild(link); 242 | this.appendChild(linkItem); 243 | } 244 | }); 245 | 246 | // Add styles 247 | const style = document.createElement("style"); 248 | this.appendChild(style); 249 | this.addEventListener("click", (e) => { 250 | if (e.target.tagName === "A") { 251 | e.preventDefault(); 252 | const targetId = e.target.getAttribute("href").substring(1); 253 | history.pushState({}, document.title, window.location.pathname+"#"+targetId) 254 | const targetElement = document.getElementById(targetId); 255 | if (targetElement) { 256 | const position = targetElement.getBoundingClientRect().top + 257 | window.pageYOffset + offset; 258 | window.scrollTo({ 259 | top: position, 260 | behavior: "smooth", 261 | }); 262 | } 263 | } 264 | }); 265 | } 266 | } 267 | 268 | class CustomScroll extends HTMLElement { 269 | constructor() { 270 | super(); 271 | } 272 | connectedCallback() { 273 | this.innerHTML = ` 274 | 296 | `; 297 | } 298 | } 299 | 300 | class HashTitleScroll extends HTMLElement { 301 | connectedCallback() { 302 | let offset = parseInt(this.getAttribute('offset'), 10) || 0 303 | let currentHref = window.location.href 304 | let parts = currentHref.split('/') 305 | let lastPart = parts[parts.length-1] 306 | if (!lastPart.includes('#')) { 307 | return 308 | } 309 | let titleId = lastPart.split('#')[1] 310 | let titleElm = document.getElementById(titleId) 311 | if (!titleElm) { 312 | return 313 | } 314 | const position = titleElm.getBoundingClientRect().top + window.pageYOffset + offset; 315 | window.scrollTo({ 316 | top: position, 317 | behavior: "smooth" 318 | }) 319 | } 320 | } 321 | 322 | 323 | window.addEventListener("DOMContentLoaded", () => { 324 | customElements.define("the-blinker", TheBlinker); 325 | customElements.define("tw-markdown", TwMarkdown); 326 | customElements.define("random-beads", RandomBeads); 327 | customElements.define("title-links", TitleLinks); 328 | customElements.define('hash-title-scroll', HashTitleScroll) 329 | customElements.define('custom-scroll', CustomScroll) 330 | 331 | }); 332 | -------------------------------------------------------------------------------- /content/post/7.iterators-in-rust.md: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 | 6 | ## Sandbox 7 | The git repo where I played around with these ideas can be found [here](https://github.com/Phillip-England/sandbox-iterators-in-rust). 8 | 9 | Also, my conversation with chatGPT regarding this topic can be found [here](https://chatgpt.com/share/67615a1d-8ce4-8005-a8f1-018d879944c7). 10 | 11 | ## Pitstop 12 | While I was learning [hyper](https://hyper.rs), I realized I needed to take a pitstop and learn a bit more about Rust iterators. From what I understand, iterators are preferred over loops and I see them used a lot around Rust. 13 | 14 | I know with the zero-cost abstraction model Rust has, it makes using iterators the same cost as running a loop. Let's see whats up. 15 | 16 | ## The Book 17 | First, I read the entry in [The Book](https://doc.rust-lang.org/stable/book/) regarding iterators to see what the Rust team has to say about them. 18 | 19 | ### Lazy 20 | First, I learned iterators are lazy and they do not do anything until you consume the iterators. 21 | 22 | This means that iterators are first created, then consumed. Can an iterator be consumed more than once? Nope. 23 | 24 | Here is an example of iterating through a vector: 25 | ```rs 26 | fn using_iter_on_vec() { 27 | let some_vec = vec!["a", "b", "c"]; 28 | let my_iterator = some_vec.iter(); 29 | for val in my_iterator { 30 | println!("Value: {}", val); 31 | } 32 | } 33 | ``` 34 | 35 | ### Many Different Methods 36 | Iterators are really Rust's take on higher order functions. It looks like many different methods can be combined to get different results with your iterator. I think the key here is really understanding what these methods do and how they work. 37 | 38 | Here is an example of transforming data with `map` and gathering the output into a vector with `collect`: 39 | ```rs 40 | fn using_map_and_collect() { 41 | let v = vec![0, 1, 2]; 42 | let incremented: Vec<_> = v.iter().map(|x| x + 1).collect(); 43 | println!("{:?}", incremented); 44 | } 45 | ``` 46 | 47 | In this example, I see two concepts I am not entirely sure of so I need to diverge and research them. 48 | 49 | ### Vec<_> 50 | I am guessing the `Vec<_>` represents some generic type. Maybe it means the lack of a type? I don't know. But I know I want to know!.. 51 | 52 | ..OK it looks like the `Vec<_>` syntax is a way of stating the variable is a vector with a type that should be inferred based on how the vector is used. This is a concept that has come up a few times but I'd like to state it here: 53 | 54 | Rust is always attempting to guess the types in your code base and it is able to do so because of how strict the compiler is. Here, when we use `Vec<_>`, we are knowingly taking advantage of the fact that the compiler can make inferences about the code base and about how types are used. 55 | 56 | ### Inspecting Closures 57 | I am taking note of this line here: 58 | 59 | ```rs 60 | let incremented: Vec<_> = v.iter().map(|x| x + 1).collect(); 61 | ``` 62 | 63 | I notice a closure is used in `map()` and I need to do a little bit of research about how closures work in Rust. 64 | 65 | I do want to address them a bit right here, though. Let me do a bit of digging to get some light context.. 66 | 67 | ..OK, the first thing I am finding is that closures are intended for short-lived computations. They are similar to `lambda` functions in other languages. Basically, a closure is an unnamed function that lives only for the time it is used. 68 | 69 | The basic syntax of a closure is: 70 | ```bash 71 | |parameters| expression 72 | ``` 73 | 74 | Another unique thing about closures versus functions is that closures capture variables from their surrounding environment while functions do not. 75 | 76 | Oh man, this might be a bigger trail than I thought.. 77 | 78 | ## Closures 79 | It seems I've made an error. Iterators are useful, but they make heavy use of closures. And if we do not understand closures, then we cannot make proper use of iterators. 80 | 81 | We need to spend some time looking into closures and getting a good grasp on how they are used. 82 | 83 | I mentioned it above, but the first thing to know is that closures capture their surrounding environment. For example: 84 | ```rs 85 | let y = 5; 86 | let add_y = |x| x + y; // `y` is captured from the environment 87 | println!("{}", add_y(3)); // 8 88 | ``` 89 | 90 | ### How Closures Capture Their Environment 91 | I am learning the majority of this from chatGPT, so be sure to checkout [the chat](https://chatgpt.com/share/67615a1d-8ce4-8005-a8f1-018d879944c7) as this content is being directly influenced from it's output. 92 | 93 | It looks like a closure can capture the surrounding environment by: 94 | 95 | borrowing: 96 | ```rs 97 | fn using_closure_with_borrow() { 98 | let x = 10; 99 | let my_closure = || println!("x is {}", x); 100 | my_closure(); 101 | } 102 | ``` 103 | 104 | mutably borrowing: 105 | ```rs 106 | fn using_closure_with_mut_borrow() { 107 | let mut x = 10; 108 | let mut increment_x = |y| x += y; 109 | increment_x(1); 110 | println!("x is {}", x); 111 | } 112 | ``` 113 | 114 | And take note in the above example, we marked the closure itself as `mut` along with `x`, the value being mutated. 115 | 116 | And finally, taking ownership: 117 | ```rs 118 | fn using_closure_with_ownership() { 119 | let x = String::from("I am owned!"); 120 | let take_x = move || println!("{}", x); 121 | take_x(); 122 | // println!("{}", x); // Error: `x` has been moved 123 | } 124 | ``` 125 | 126 | ### Where Can Closures Be Used? 127 | Anywhere a function or callback is expected. This is what makes closures so popular with iterators, the topic we are currently discussing, remember? 128 | 129 | ### Creating a Function Which Takes a Closure 130 | And I am just too curious to walk away. Here is a function which takes a closure as input: 131 | 132 | ```rs 133 | fn using_fn_with_closure_input(func: F) where F: Fn(String) { 134 | func(String::from("I am injected into the closure!")); 135 | } 136 | 137 | using_fn_with_closure_input(|str| println!("{}", str)); 138 | ``` 139 | 140 | Oh and this is very helpful. Here, I discovered the way in which we use `Fn`, `FnMut`, and `FnOnce`, will dictate the way our closure captures its environment when being utilized as input into a function. 141 | 142 | ```rs 143 | fn call_fn(f: F) { f(); } // Fn 144 | fn call_fnmut(mut f: F) { f(); } // FnMut 145 | fn call_fnonce(f: F) { f(); } // FnOnce 146 | 147 | let x = String::from("Hello"); 148 | 149 | call_fnonce(move || println!("{}", x)); // Takes ownership (FnOnce) 150 | call_fn(|| println!("This just borrows")); // Borrows (Fn) 151 | ``` 152 | 153 | Just know, if we are ever in a situation where we are using a closure as input to a function, choosing between the three above options is part of the process and influences how the closure handles memory in relation to it's environment. 154 | 155 | ## Back To Iterators 156 | We can filter a list of elements using `filter`: 157 | ```rs 158 | fn using_filter() { 159 | let nums = vec![1,2,3,4,5,6,7,8,9,10]; 160 | let even: Vec<_> = nums.iter().filter(|&&x| x % 2 == 0).collect(); 161 | println!("{:?}", even); 162 | } 163 | ``` 164 | 165 | I can see why people love iterators with how concise the syntax is. However, in the above example, I am wondering why `&&x` was used. 166 | 167 | For some reason, these two options run fine as well: 168 | ```rs 169 | fn using_filter() { 170 | let nums = vec![1,2,3,4,5,6,7,8,9,10]; 171 | let even: Vec<_> = nums.iter().filter(|x| *x % 2 == 0).collect(); 172 | println!("{:?}", even); 173 | } 174 | 175 | fn using_filter() { 176 | let nums = vec![1,2,3,4,5,6,7,8,9,10]; 177 | let even: Vec<_> = nums.iter().filter(|&x| x % 2 == 0).collect(); 178 | println!("{:?}", even); 179 | } 180 | ``` 181 | 182 | Now, it makes sense and I understand how dereferencing works in the first example, but I do not understand why both `&&x` and `&x` work in this scenario.. 183 | 184 | ..OK it looks like all three have the same outcome. I still don't fully understand why, but I do know in the above example everything resolves to `i32`. Using `&&x` is somehow communicating a double-unwrapping of a reference, but also seeing `&x` is the "correct" way to go about it. All in all, I feel confortable with both `*x` and `&x` so I will just stick to `&x` until I learn more. 185 | 186 | ## Implementing Your Own iterators 187 | I think the most important question is, "How do we make our own types iterators?". Well, `Iterator` is just a trait. We like with any trait, we can implement the trait on our custom types. 188 | 189 | Here is a `TimeBomb` type I implemented the `Iterator` trait on and then made use of in the function `using_custom_iterator`: 190 | 191 | ```rs 192 | struct TimeBomb { 193 | count: u32, 194 | limit: u32, 195 | } 196 | 197 | impl Iterator for TimeBomb { 198 | type Item = u32; 199 | fn next(&mut self) -> Option { 200 | self.count += 1; 201 | if self.count < self.limit { 202 | Some(self.count) 203 | } else { 204 | println!("💥"); 205 | None 206 | } 207 | } 208 | } 209 | 210 | fn using_custom_iterator() { 211 | let mut tb = TimeBomb{ 212 | count: 0, 213 | limit: 10, 214 | }; 215 | while let Some(count) = tb.next() { 216 | println!("{}", count); 217 | } 218 | } 219 | ``` 220 | 221 | ## Other Examples 222 | Here are some of the other examples I ended up coming across, all of which are in the repo listed above: 223 | 224 | ```rs 225 | // combining methods like map and filter 226 | fn using_map_and_filter() { 227 | let nums = vec![1,2,3,4,5,6]; 228 | let doubled_even: Vec<_> = nums 229 | .iter() 230 | .filter(|&x| x % 2 == 0) 231 | .map(|&x| x * 2) 232 | .collect(); 233 | println!("{:?}", doubled_even); 234 | } 235 | 236 | // consuming an iterator using sum 237 | fn using_sum_to_consume() { 238 | let nums = vec![1,2,3,4]; 239 | let total: i32 = nums.iter().sum(); 240 | println!("{:?}", total); 241 | } 242 | 243 | // chaining operations with fold and building accumlative values 244 | fn using_fold_to_accumulate() { 245 | let nums = vec![1,2,3,4,5]; 246 | let sum = nums.iter().fold(0, |acc, &x| acc + x); 247 | println!("{:?}", sum); 248 | } 249 | 250 | // using into_iter to consume and take ownership of a collection 251 | fn using_into_iter_to_take_ownership() { 252 | let nums = vec![1,2,3,4,5]; 253 | for num in nums.into_iter() { 254 | println!("{}", num); 255 | } 256 | // nums is no longer valid 257 | } 258 | 259 | // using iter_mut to mutate values in place 260 | fn using_iter_mut_for_mutation() { 261 | let mut nums = vec![1,2,3,4]; 262 | for num in nums.iter_mut() { 263 | *num = *num + 1; 264 | } 265 | println!("{:?}", nums); 266 | } 267 | 268 | // using enumerate to add an index to each iter_mut 269 | fn using_enumerate_to_index() { 270 | let colors = vec!["red", "green", "blue"]; 271 | for (i, color) in colors.iter().enumerate() { 272 | println!("{}: {}", i, color); 273 | } 274 | } 275 | ``` 276 | 277 | ## Conclusion 278 | I think iterators are an important concept in Rust and they seem very widespread throughout the language. Iterators are tied closely to closures, and so having a solid understanding on how closures interact with their environment is really the key to making solid use of iterators. 279 | -------------------------------------------------------------------------------- /content/post/0.trying-neovim.md: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 | 6 | ## Points of Learning 7 | The following article touches on these technical skills: 8 | 9 | - installing binaries and moving them around on your system 10 | - learn the basics of vim motions 11 | - gain exposure to find and replace methods in nvim 12 | - install a precustomized nvim version, [NvChad](https://nvchad.com/) 13 | 14 | ## Preparing the Thumbnail 15 | As I was drafting out this post, I was preparing a thumbnail for it (ai). During which I noticed the image for my home page on this website was 1mb in size. Way to large for a simple blog thumbnail. So, I resized it using [Image Resizer](https://imageresizer.com/) and I got to thinking, "I bet a command line utility for this exists." 16 | 17 | I got this image size down from 1mb to 7kb. The images in the thumbnails on this site are 150x150px and I have no intention of scaling them over time. 18 | 19 | NOTE TO SELF: Find a command line utility to resize images on the fly. 20 | 21 | ## My Experience With Nvim 22 | I know the basics. I am not an expert on the key maps or anything but I have a general idea of how it works. I know you can do all your config in lua. I don't really know lua, but I know it is loosely typed so we are good. 23 | 24 | I gave nvim a good go around a year or so ago, but I think it might be time for another stab. The main reason is I just want to run something more lightweight on my desktop. Sometimes she can't keep up 😅. 25 | 26 | That, and honestly, the amount of die-hard vim users is curious to me. Am I missing something? Plus, I get a chance to look a little bit closer at a well-known software. Here we go. 27 | 28 | ## Installation 29 | I found installing a bit of a pain as version as version `0.7.2` was installed by default on WSL when running: 30 | 31 | ```bash 32 | sudo apt-get install neovim 33 | ``` 34 | 35 | Let's uninstall, update, and reinstall: 36 | 37 | ```bash 38 | sudo apt-get remove neovim 39 | sudo apt-get update 40 | sudo apt-get upgrade 41 | sudo apt-get install neovim 42 | ``` 43 | 44 | Still getting `0.7.2`.. 45 | 46 | Found this [post](https://www.reddit.com/r/neovim/comments/yghzkb/im_running_neovim_07_but_require_08_for_a_plugin/) on reddit and tried this: 47 | 48 | ```bash 49 | sudo apt-get install software-properties-common 50 | sudo add-apt-repository ppa:neovim-ppa/stable 51 | sudo apt-get update 52 | sudo apt-get install neovim 53 | nvim -v 54 | ``` 55 | 56 | Still getting `0.7.2`.. 57 | 58 | ## Building From Source 59 | 60 | I can build from source on the [nvim release page](https://github.com/neovim/neovim/releases). 61 | 62 | I ended up doing: 63 | 64 | ```bash 65 | wget https://github.com/neovim/neovim/releases/download/v0.10.2/nvim-linux64.tar.gz 66 | tar -xvzf nvim-linux64.tar.gz 67 | mv nvim-linux64 nvim 68 | ``` 69 | 70 | I am not 100% sure where this needs to go on my system. I know neovim is config-heavy so I want to be sure I set this up right. 71 | 72 | ... 73 | 74 | Okay I see `/usr/bin` and `~/.config/nvim` [might be options](https://www.reddit.com/r/neovim/comments/10e0iaj/where_is_the_neovim_install_location/)? 75 | 76 | `/usr/bin` makes the most sense lets try it: 77 | 78 | ```bash 79 | mv nvim /usr/bin 80 | nvim 81 | ``` 82 | 83 | Now, `nvim` was an installation directory, so it has the actual binary within. I had issues getting it to work unless I kept the `nvim` directory within `/usr/bin`. 84 | 85 | `/usr/bin` normally has all of your system binaries, so having a directory in there feels a little off. 86 | 87 | I ended up having to change `/usr/bin/nvim` to `/usr/bin/nvim-dir` and adding `/usr/bin/nvim-dir/bin` to my `PATH` as a solution. 88 | 89 | Now we are on version `0.10.2` 😀 90 | 91 | ## Plugins 92 | We gotta get some plugins installed. A little research and I found that package manager [lazy.nvim](https://lazy.folke.io/). 93 | 94 | I went ahead and followed the docs to setup some basic configuration I stashed in [this repo](https://github.com/phillip-england/nvim-config). 95 | 96 | That repo might change as I find my favorite custom config. 97 | 98 | ## The LSP 99 | At this point in the post, I swapped from vscode to neovim. 100 | 101 | I think the plugin I am most concerned about getting up and running is my LSP support. I am going to do some research and see what I can find. 102 | 103 | I found [this page](https://neovim.io/doc/user/lsp.html) in the neovim docs. It looks like language servers are installed using a package manager. 104 | 105 | I checked out [this page](https://rust-analyzer.github.io/manual.html#installation) where I see we can go through the process of install language severs one-by-one, but I think a better way might be out there. 106 | 107 | I found this [repo](https://github.com/neovim/nvim-lspconfig) which is maintained and looks promising. We still manage installing servers, but this makes it easy to get them working in our projects. 108 | 109 | I already have `gopls` installed, so I'll just try to get it working with that. 110 | 111 | Adding this line to `~/.config/nvim/init.lua`: 112 | 113 | ```bash 114 | require'lspconfig'.gopls.setup{} 115 | ``` 116 | 117 | ## NvChad 118 | 119 | After digging around with LSP details and a few other plugins, I decided it would be best to use a drop in configuration with good defaults. I decided to go with [NvChad](https://nvchad.com/docs/quickstart/install) which has super easy installation instructions. 120 | 121 | It has good defaults and I think it'll work for the time being. 122 | 123 | I got it up and running and it is doing fine. I have not had any issues so far and was able to change over my theme. 124 | 125 | ## Getting Around 126 | 127 | I am going to spend a little bit of time learning how to getting around in neovim. I am already confident in things like insert mode. For selecting, I am just using the mouse. But I know select mode is a thing. Just going to dive in and look for the most useful things to know. 128 | 129 | Here is an entry from [Claude](https://claude.ai): 130 | 131 | I'll share the most crucial Vim navigation skills that will dramatically improve your text editing efficiency: 132 | 133 | Basic Movement Keys: 134 | - `h`, `j`, `k`, `l` are your fundamental movement keys 135 | - `h`: Move left 136 | - `j`: Move down 137 | - `k`: Move up 138 | - `l`: Move right 139 | 140 | Word-Based Movement: 141 | - `w`: Move forward to the start of the next word 142 | - `b`: Move backward to the start of the previous word 143 | - `e`: Move to the end of the current word 144 | - `W`, `B`, `E`: Similar to `w`, `b`, `e`, but move by WORD (ignore punctuation) 145 | 146 | Line Movement: 147 | - `0`: Jump to the start of the line 148 | - `$`: Jump to the end of the line 149 | - `^`: Jump to the first non-blank character of the line 150 | 151 | Screen Movement: 152 | - `Ctrl + f`: Page down 153 | - `Ctrl + b`: Page up 154 | - `H`: Move to the top of the screen 155 | - `M`: Move to the middle of the screen 156 | - `L`: Move to the bottom of the screen 157 | 158 | Advanced Movement: 159 | - `gg`: Go to the first line of the document 160 | - `G`: Go to the last line of the document 161 | - `{number}G`: Go to a specific line number 162 | 163 | Search Movement: 164 | - `/{pattern}`: Search forward for a pattern 165 | - `?{pattern}`: Search backward for a pattern 166 | - `n`: Repeat the last search forward 167 | - `N`: Repeat the last search backward 168 | 169 | Quick Jumps: 170 | - `(`: Move to the start of the previous sentence 171 | - `)`: Move to the start of the next sentence 172 | - `{`: Move to the previous paragraph 173 | - `}`: Move to the next paragraph 174 | 175 | Pro Tips: 176 | 1. Combine movement with numbers for faster navigation (e.g., `5j` moves 5 lines down) 177 | 2. Use these movements with operators like `d` (delete) or `c` (change) for powerful editing 178 | 3. Practice these until they become muscle memory 179 | 180 | Bonus Tip: Use `vimtutor` in your terminal for an interactive tutorial that will help you master these skills quickly! 181 | 182 | Would you like me to elaborate on any of these navigation techniques? 183 | 184 | ## Find and Replace 185 | Then, I questioned [Calude](https://claude.ai) about how to easily find and replace text within a document using nvim. Here is the response: 186 | 187 | I'll cover find and replace in Vim, which is extremely powerful: 188 | 189 | Basic Find and Replace: 190 | 1. Within a single line: 191 | - `s/old/new`: Replace first occurrence 192 | - `s/old/new/g`: Replace all occurrences on current line 193 | 194 | 2. Global Find and Replace: 195 | - `:%s/old/new`: Replace first occurrence in entire file 196 | - `:%s/old/new/g`: Replace ALL occurrences in entire file 197 | - `:%s/old/new/gc`: Replace with confirmation prompt before each replacement 198 | 199 | Advanced Options: 200 | - `:%s/old/new/gi`: Case-insensitive replacement 201 | - `:%s/old/new/gI`: Case-sensitive replacement 202 | - `:%s/\/new/g`: Whole word replacement (matches 'old' exactly) 209 | 210 | Regex Power: 211 | - Use regex for complex replacements 212 | - `:%s/\v(pattern1)(pattern2)/\2\1/g`: Swap patterns 213 | - `:%s/\v(\d+)/number: \1/g`: Add prefix to numbers 214 | 215 | Special Characters: 216 | - `\n`: Newline in replacement 217 | - `\r`: Carriage return 218 | - `\t`: Tab 219 | 220 | Scoped Replacements: 221 | - `:10,20s/old/new/g`: Replace between lines 10-20 222 | - `:'<,'>s/old/new/g`: Replace in visual selection 223 | 224 | Pro Tips: 225 | - Always use `/g` to replace all occurrences 226 | - Use confirmation mode `gc` when making significant changes 227 | - Practice regex for complex replacements 228 | 229 | Want me to elaborate on any of these techniques? 230 | 231 | ## Combining Commands 232 | I think reading [this post](https://vi.stackexchange.com/questions/9028/what-is-the-command-for-select-all-in-vim-and-vsvim) on StackExchange really helped me to 'get the point' a little bit more. I was trying to taking this post and run it through AI for a spellcheck when I realized I didn't know how to do that in nvim. 233 | 234 | So, in that post we see `ggVG` is the combination of commands to select all the text in a file. It doesn't feel intuitive, but man does it feel cool 😎. 235 | 236 | I think my favorite commands are `/` and `?` for searching forwards and backwards in a file. Instead of using my keyboard to move my cursor around the file, I notice it is way quicker to spot where you want to go, and then to search jump there using `/` or `?` depending if you are moving forwards or backwards in the file based on your current cursor position. 237 | 238 | I think this is where nvim shines. It's ability to allow you to quickly jump around a file without having to use the mouse is really the point. 239 | 240 | For example, let's say I want to grab this current block of text. Mind you, this is off the dome, I did not reaseach this bit I am just rolling here. 241 | 242 | Okay, that block right there ^, lets copy it real quick. I'm thinking we do `?For example` to jump back in the file to the start of the block. Then we enter visual mode using `v`. Then we can run `/here.` to jump to the end of the line. Now that I say that out loud I know you can also run `$` to go to the end of the line. Then you can yank the line using `y`. 243 | 244 | So, you have the ability to on the fly combine commands to avoid using the mouse and that is an example of how we can do it. I think that is the point of nvim. 245 | 246 | ## Conclusion 247 | I do think getting around the document can be more effcient with nvim. I think it really boils down to taking the time to learn the mappings and then just forcing yourself to use the editor for a period of time. I think that is probably what I will commit to to give it a fair shake. 248 | 249 | Setting things up can be time-consuming, but it is a good process if you are wanting to learn more about computers in general. For me, I don't think taking the time to get the most pristine customization is really where my time is best suited, so I opted in for a precustomized-install. 250 | 251 | I did learn more about LSP's and it did make me interested in what it would look like to design my own LSP. 252 | 253 | I was recently working on [GTML](https://github.com/phillip-england/gtml) and it has been the closest thing to make getting into compilers and interpreters. My lack of understanding of LSP's did limit my creative potential in that project. 254 | 255 | NOTE TO SELF: Did you learn how LSPs work? 256 | 257 | ## After 258 | 259 | All my notes I take on this topic will go here. 260 | 261 | ### Dropping Buffers 262 | I found it was annoying when I was using telescope to open up new files in vim because tons of them would collect. You can run `:bd` to drop the current buffer. This is useful. 263 | 264 | ### Shifting Blocks of Text 265 | I noticed it you could not indent blocks of text by using `tab`. So, I did a little research and learned you can use `SELECT` mode to highlight a block of text and then use `<` or `>` to shift things. 266 | 267 | ### Changing the Current Word 268 | I always find myself doing `CRTL+BACKSPACE` to delete words in vscode. I learned you can run `Bciw` to change the inner word which is a vim command which more or less does the same thing. 269 | 270 | ### Rust Analyzer 271 | I found it was a little bit difficult to get `rust-analyzer` working with `NvChad` as it did not come preinstalled. However, I was able to get it up and running and here is the [page](https://github.com/neovim/nvim-lspconfig/blob/master/doc/configs.md#rust_analyzer) where I found my solution. 272 | 273 | ### Commenting Out Text 274 | I discovered I could not use `CTRL+/` to comment out a highlighted section of text. `NvChad` comes with the ability to use `gc` to comment out blocks of text instead. 275 | 276 | ### Jumping to End of Line in Insert Mode 277 | Bruh, `A` brings you to the end of the line in insert mode. I've been doing `$i` my gosh. 278 | -------------------------------------------------------------------------------- /static/css/output.css: -------------------------------------------------------------------------------- 1 | /* 2 | ! tailwindcss v3.4.3 | MIT License | https://tailwindcss.com 3 | */ 4 | 5 | /* 6 | 1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4) 7 | 2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116) 8 | */ 9 | 10 | *, 11 | ::before, 12 | ::after { 13 | box-sizing: border-box; 14 | /* 1 */ 15 | border-width: 0; 16 | /* 2 */ 17 | border-style: solid; 18 | /* 2 */ 19 | border-color: #e5e7eb; 20 | /* 2 */ 21 | } 22 | 23 | ::before, 24 | ::after { 25 | --tw-content: ''; 26 | } 27 | 28 | /* 29 | 1. Use a consistent sensible line-height in all browsers. 30 | 2. Prevent adjustments of font size after orientation changes in iOS. 31 | 3. Use a more readable tab size. 32 | 4. Use the user's configured `sans` font-family by default. 33 | 5. Use the user's configured `sans` font-feature-settings by default. 34 | 6. Use the user's configured `sans` font-variation-settings by default. 35 | 7. Disable tap highlights on iOS 36 | */ 37 | 38 | html, 39 | :host { 40 | line-height: 1.5; 41 | /* 1 */ 42 | -webkit-text-size-adjust: 100%; 43 | /* 2 */ 44 | -moz-tab-size: 4; 45 | /* 3 */ 46 | -o-tab-size: 4; 47 | tab-size: 4; 48 | /* 3 */ 49 | font-family: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; 50 | /* 4 */ 51 | font-feature-settings: normal; 52 | /* 5 */ 53 | font-variation-settings: normal; 54 | /* 6 */ 55 | -webkit-tap-highlight-color: transparent; 56 | /* 7 */ 57 | } 58 | 59 | /* 60 | 1. Remove the margin in all browsers. 61 | 2. Inherit line-height from `html` so users can set them as a class directly on the `html` element. 62 | */ 63 | 64 | body { 65 | margin: 0; 66 | /* 1 */ 67 | line-height: inherit; 68 | /* 2 */ 69 | } 70 | 71 | /* 72 | 1. Add the correct height in Firefox. 73 | 2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655) 74 | 3. Ensure horizontal rules are visible by default. 75 | */ 76 | 77 | hr { 78 | height: 0; 79 | /* 1 */ 80 | color: inherit; 81 | /* 2 */ 82 | border-top-width: 1px; 83 | /* 3 */ 84 | } 85 | 86 | /* 87 | Add the correct text decoration in Chrome, Edge, and Safari. 88 | */ 89 | 90 | abbr:where([title]) { 91 | -webkit-text-decoration: underline dotted; 92 | text-decoration: underline dotted; 93 | } 94 | 95 | /* 96 | Remove the default font size and weight for headings. 97 | */ 98 | 99 | h1, 100 | h2, 101 | h3, 102 | h4, 103 | h5, 104 | h6 { 105 | font-size: inherit; 106 | font-weight: inherit; 107 | } 108 | 109 | /* 110 | Reset links to optimize for opt-in styling instead of opt-out. 111 | */ 112 | 113 | a { 114 | color: inherit; 115 | text-decoration: inherit; 116 | } 117 | 118 | /* 119 | Add the correct font weight in Edge and Safari. 120 | */ 121 | 122 | b, 123 | strong { 124 | font-weight: bolder; 125 | } 126 | 127 | /* 128 | 1. Use the user's configured `mono` font-family by default. 129 | 2. Use the user's configured `mono` font-feature-settings by default. 130 | 3. Use the user's configured `mono` font-variation-settings by default. 131 | 4. Correct the odd `em` font sizing in all browsers. 132 | */ 133 | 134 | code, 135 | kbd, 136 | samp, 137 | pre { 138 | font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; 139 | /* 1 */ 140 | font-feature-settings: normal; 141 | /* 2 */ 142 | font-variation-settings: normal; 143 | /* 3 */ 144 | font-size: 1em; 145 | /* 4 */ 146 | } 147 | 148 | /* 149 | Add the correct font size in all browsers. 150 | */ 151 | 152 | small { 153 | font-size: 80%; 154 | } 155 | 156 | /* 157 | Prevent `sub` and `sup` elements from affecting the line height in all browsers. 158 | */ 159 | 160 | sub, 161 | sup { 162 | font-size: 75%; 163 | line-height: 0; 164 | position: relative; 165 | vertical-align: baseline; 166 | } 167 | 168 | sub { 169 | bottom: -0.25em; 170 | } 171 | 172 | sup { 173 | top: -0.5em; 174 | } 175 | 176 | /* 177 | 1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297) 178 | 2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016) 179 | 3. Remove gaps between table borders by default. 180 | */ 181 | 182 | table { 183 | text-indent: 0; 184 | /* 1 */ 185 | border-color: inherit; 186 | /* 2 */ 187 | border-collapse: collapse; 188 | /* 3 */ 189 | } 190 | 191 | /* 192 | 1. Change the font styles in all browsers. 193 | 2. Remove the margin in Firefox and Safari. 194 | 3. Remove default padding in all browsers. 195 | */ 196 | 197 | button, 198 | input, 199 | optgroup, 200 | select, 201 | textarea { 202 | font-family: inherit; 203 | /* 1 */ 204 | font-feature-settings: inherit; 205 | /* 1 */ 206 | font-variation-settings: inherit; 207 | /* 1 */ 208 | font-size: 100%; 209 | /* 1 */ 210 | font-weight: inherit; 211 | /* 1 */ 212 | line-height: inherit; 213 | /* 1 */ 214 | letter-spacing: inherit; 215 | /* 1 */ 216 | color: inherit; 217 | /* 1 */ 218 | margin: 0; 219 | /* 2 */ 220 | padding: 0; 221 | /* 3 */ 222 | } 223 | 224 | /* 225 | Remove the inheritance of text transform in Edge and Firefox. 226 | */ 227 | 228 | button, 229 | select { 230 | text-transform: none; 231 | } 232 | 233 | /* 234 | 1. Correct the inability to style clickable types in iOS and Safari. 235 | 2. Remove default button styles. 236 | */ 237 | 238 | button, 239 | input:where([type='button']), 240 | input:where([type='reset']), 241 | input:where([type='submit']) { 242 | -webkit-appearance: button; 243 | /* 1 */ 244 | background-color: transparent; 245 | /* 2 */ 246 | background-image: none; 247 | /* 2 */ 248 | } 249 | 250 | /* 251 | Use the modern Firefox focus style for all focusable elements. 252 | */ 253 | 254 | :-moz-focusring { 255 | outline: auto; 256 | } 257 | 258 | /* 259 | Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737) 260 | */ 261 | 262 | :-moz-ui-invalid { 263 | box-shadow: none; 264 | } 265 | 266 | /* 267 | Add the correct vertical alignment in Chrome and Firefox. 268 | */ 269 | 270 | progress { 271 | vertical-align: baseline; 272 | } 273 | 274 | /* 275 | Correct the cursor style of increment and decrement buttons in Safari. 276 | */ 277 | 278 | ::-webkit-inner-spin-button, 279 | ::-webkit-outer-spin-button { 280 | height: auto; 281 | } 282 | 283 | /* 284 | 1. Correct the odd appearance in Chrome and Safari. 285 | 2. Correct the outline style in Safari. 286 | */ 287 | 288 | [type='search'] { 289 | -webkit-appearance: textfield; 290 | /* 1 */ 291 | outline-offset: -2px; 292 | /* 2 */ 293 | } 294 | 295 | /* 296 | Remove the inner padding in Chrome and Safari on macOS. 297 | */ 298 | 299 | ::-webkit-search-decoration { 300 | -webkit-appearance: none; 301 | } 302 | 303 | /* 304 | 1. Correct the inability to style clickable types in iOS and Safari. 305 | 2. Change font properties to `inherit` in Safari. 306 | */ 307 | 308 | ::-webkit-file-upload-button { 309 | -webkit-appearance: button; 310 | /* 1 */ 311 | font: inherit; 312 | /* 2 */ 313 | } 314 | 315 | /* 316 | Add the correct display in Chrome and Safari. 317 | */ 318 | 319 | summary { 320 | display: list-item; 321 | } 322 | 323 | /* 324 | Removes the default spacing and border for appropriate elements. 325 | */ 326 | 327 | blockquote, 328 | dl, 329 | dd, 330 | h1, 331 | h2, 332 | h3, 333 | h4, 334 | h5, 335 | h6, 336 | hr, 337 | figure, 338 | p, 339 | pre { 340 | margin: 0; 341 | } 342 | 343 | fieldset { 344 | margin: 0; 345 | padding: 0; 346 | } 347 | 348 | legend { 349 | padding: 0; 350 | } 351 | 352 | ol, 353 | ul, 354 | menu { 355 | list-style: none; 356 | margin: 0; 357 | padding: 0; 358 | } 359 | 360 | /* 361 | Reset default styling for dialogs. 362 | */ 363 | 364 | dialog { 365 | padding: 0; 366 | } 367 | 368 | /* 369 | Prevent resizing textareas horizontally by default. 370 | */ 371 | 372 | textarea { 373 | resize: vertical; 374 | } 375 | 376 | /* 377 | 1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300) 378 | 2. Set the default placeholder color to the user's configured gray 400 color. 379 | */ 380 | 381 | input::-moz-placeholder, textarea::-moz-placeholder { 382 | opacity: 1; 383 | /* 1 */ 384 | color: #9ca3af; 385 | /* 2 */ 386 | } 387 | 388 | input::placeholder, 389 | textarea::placeholder { 390 | opacity: 1; 391 | /* 1 */ 392 | color: #9ca3af; 393 | /* 2 */ 394 | } 395 | 396 | /* 397 | Set the default cursor for buttons. 398 | */ 399 | 400 | button, 401 | [role="button"] { 402 | cursor: pointer; 403 | } 404 | 405 | /* 406 | Make sure disabled buttons don't get the pointer cursor. 407 | */ 408 | 409 | :disabled { 410 | cursor: default; 411 | } 412 | 413 | /* 414 | 1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14) 415 | 2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210) 416 | This can trigger a poorly considered lint error in some tools but is included by design. 417 | */ 418 | 419 | img, 420 | svg, 421 | video, 422 | canvas, 423 | audio, 424 | iframe, 425 | embed, 426 | object { 427 | display: block; 428 | /* 1 */ 429 | vertical-align: middle; 430 | /* 2 */ 431 | } 432 | 433 | /* 434 | Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14) 435 | */ 436 | 437 | img, 438 | video { 439 | max-width: 100%; 440 | height: auto; 441 | } 442 | 443 | /* Make elements with the HTML hidden attribute stay hidden by default */ 444 | 445 | [hidden] { 446 | display: none; 447 | } 448 | 449 | *, ::before, ::after { 450 | --tw-border-spacing-x: 0; 451 | --tw-border-spacing-y: 0; 452 | --tw-translate-x: 0; 453 | --tw-translate-y: 0; 454 | --tw-rotate: 0; 455 | --tw-skew-x: 0; 456 | --tw-skew-y: 0; 457 | --tw-scale-x: 1; 458 | --tw-scale-y: 1; 459 | --tw-pan-x: ; 460 | --tw-pan-y: ; 461 | --tw-pinch-zoom: ; 462 | --tw-scroll-snap-strictness: proximity; 463 | --tw-gradient-from-position: ; 464 | --tw-gradient-via-position: ; 465 | --tw-gradient-to-position: ; 466 | --tw-ordinal: ; 467 | --tw-slashed-zero: ; 468 | --tw-numeric-figure: ; 469 | --tw-numeric-spacing: ; 470 | --tw-numeric-fraction: ; 471 | --tw-ring-inset: ; 472 | --tw-ring-offset-width: 0px; 473 | --tw-ring-offset-color: #fff; 474 | --tw-ring-color: rgb(59 130 246 / 0.5); 475 | --tw-ring-offset-shadow: 0 0 #0000; 476 | --tw-ring-shadow: 0 0 #0000; 477 | --tw-shadow: 0 0 #0000; 478 | --tw-shadow-colored: 0 0 #0000; 479 | --tw-blur: ; 480 | --tw-brightness: ; 481 | --tw-contrast: ; 482 | --tw-grayscale: ; 483 | --tw-hue-rotate: ; 484 | --tw-invert: ; 485 | --tw-saturate: ; 486 | --tw-sepia: ; 487 | --tw-drop-shadow: ; 488 | --tw-backdrop-blur: ; 489 | --tw-backdrop-brightness: ; 490 | --tw-backdrop-contrast: ; 491 | --tw-backdrop-grayscale: ; 492 | --tw-backdrop-hue-rotate: ; 493 | --tw-backdrop-invert: ; 494 | --tw-backdrop-opacity: ; 495 | --tw-backdrop-saturate: ; 496 | --tw-backdrop-sepia: ; 497 | --tw-contain-size: ; 498 | --tw-contain-layout: ; 499 | --tw-contain-paint: ; 500 | --tw-contain-style: ; 501 | } 502 | 503 | ::backdrop { 504 | --tw-border-spacing-x: 0; 505 | --tw-border-spacing-y: 0; 506 | --tw-translate-x: 0; 507 | --tw-translate-y: 0; 508 | --tw-rotate: 0; 509 | --tw-skew-x: 0; 510 | --tw-skew-y: 0; 511 | --tw-scale-x: 1; 512 | --tw-scale-y: 1; 513 | --tw-pan-x: ; 514 | --tw-pan-y: ; 515 | --tw-pinch-zoom: ; 516 | --tw-scroll-snap-strictness: proximity; 517 | --tw-gradient-from-position: ; 518 | --tw-gradient-via-position: ; 519 | --tw-gradient-to-position: ; 520 | --tw-ordinal: ; 521 | --tw-slashed-zero: ; 522 | --tw-numeric-figure: ; 523 | --tw-numeric-spacing: ; 524 | --tw-numeric-fraction: ; 525 | --tw-ring-inset: ; 526 | --tw-ring-offset-width: 0px; 527 | --tw-ring-offset-color: #fff; 528 | --tw-ring-color: rgb(59 130 246 / 0.5); 529 | --tw-ring-offset-shadow: 0 0 #0000; 530 | --tw-ring-shadow: 0 0 #0000; 531 | --tw-shadow: 0 0 #0000; 532 | --tw-shadow-colored: 0 0 #0000; 533 | --tw-blur: ; 534 | --tw-brightness: ; 535 | --tw-contrast: ; 536 | --tw-grayscale: ; 537 | --tw-hue-rotate: ; 538 | --tw-invert: ; 539 | --tw-saturate: ; 540 | --tw-sepia: ; 541 | --tw-drop-shadow: ; 542 | --tw-backdrop-blur: ; 543 | --tw-backdrop-brightness: ; 544 | --tw-backdrop-contrast: ; 545 | --tw-backdrop-grayscale: ; 546 | --tw-backdrop-hue-rotate: ; 547 | --tw-backdrop-invert: ; 548 | --tw-backdrop-opacity: ; 549 | --tw-backdrop-saturate: ; 550 | --tw-backdrop-sepia: ; 551 | --tw-contain-size: ; 552 | --tw-contain-layout: ; 553 | --tw-contain-paint: ; 554 | --tw-contain-style: ; 555 | } 556 | 557 | .static { 558 | position: static; 559 | } 560 | 561 | .fixed { 562 | position: fixed; 563 | } 564 | 565 | .absolute { 566 | position: absolute; 567 | } 568 | 569 | .left-0 { 570 | left: 0px; 571 | } 572 | 573 | .right-0 { 574 | right: 0px; 575 | } 576 | 577 | .top-0 { 578 | top: 0px; 579 | } 580 | 581 | .top-\[85px\] { 582 | top: 85px; 583 | } 584 | 585 | .z-30 { 586 | z-index: 30; 587 | } 588 | 589 | .z-40 { 590 | z-index: 40; 591 | } 592 | 593 | .z-50 { 594 | z-index: 50; 595 | } 596 | 597 | .my-4 { 598 | margin-top: 1rem; 599 | margin-bottom: 1rem; 600 | } 601 | 602 | .mb-2 { 603 | margin-bottom: 0.5rem; 604 | } 605 | 606 | .mb-4 { 607 | margin-bottom: 1rem; 608 | } 609 | 610 | .ml-4 { 611 | margin-left: 1rem; 612 | } 613 | 614 | .mt-6 { 615 | margin-top: 1.5rem; 616 | } 617 | 618 | .block { 619 | display: block; 620 | } 621 | 622 | .flex { 623 | display: flex; 624 | } 625 | 626 | .table { 627 | display: table; 628 | } 629 | 630 | .hidden { 631 | display: none; 632 | } 633 | 634 | .h-6 { 635 | height: 1.5rem; 636 | } 637 | 638 | .h-8 { 639 | height: 2rem; 640 | } 641 | 642 | .h-\[50px\] { 643 | height: 50px; 644 | } 645 | 646 | .h-\[85px\] { 647 | height: 85px; 648 | } 649 | 650 | .h-auto { 651 | height: auto; 652 | } 653 | 654 | .h-full { 655 | height: 100%; 656 | } 657 | 658 | .h-screen { 659 | height: 100vh; 660 | } 661 | 662 | .w-6 { 663 | width: 1.5rem; 664 | } 665 | 666 | .w-8 { 667 | width: 2rem; 668 | } 669 | 670 | .w-\[150px\] { 671 | width: 150px; 672 | } 673 | 674 | .w-\[300px\] { 675 | width: 300px; 676 | } 677 | 678 | .w-full { 679 | width: 100%; 680 | } 681 | 682 | .max-w-full { 683 | max-width: 100%; 684 | } 685 | 686 | .flex-grow { 687 | flex-grow: 1; 688 | } 689 | 690 | .resize { 691 | resize: both; 692 | } 693 | 694 | .list-decimal { 695 | list-style-type: decimal; 696 | } 697 | 698 | .list-disc { 699 | list-style-type: disc; 700 | } 701 | 702 | .flex-row { 703 | flex-direction: row; 704 | } 705 | 706 | .flex-col { 707 | flex-direction: column; 708 | } 709 | 710 | .items-center { 711 | align-items: center; 712 | } 713 | 714 | .justify-center { 715 | justify-content: center; 716 | } 717 | 718 | .justify-between { 719 | justify-content: space-between; 720 | } 721 | 722 | .gap-2 { 723 | gap: 0.5rem; 724 | } 725 | 726 | .gap-4 { 727 | gap: 1rem; 728 | } 729 | 730 | .overflow-auto { 731 | overflow: auto; 732 | } 733 | 734 | .overflow-x-auto { 735 | overflow-x: auto; 736 | } 737 | 738 | .overflow-y-scroll { 739 | overflow-y: scroll; 740 | } 741 | 742 | .rounded { 743 | border-radius: 0.25rem; 744 | } 745 | 746 | .rounded-full { 747 | border-radius: 9999px; 748 | } 749 | 750 | .border { 751 | border-width: 1px; 752 | } 753 | 754 | .border-b { 755 | border-bottom-width: 1px; 756 | } 757 | 758 | .border-l-4 { 759 | border-left-width: 4px; 760 | } 761 | 762 | .border-t { 763 | border-top-width: 1px; 764 | } 765 | 766 | .border-gray-200 { 767 | --tw-border-opacity: 1; 768 | border-color: rgb(229 231 235 / var(--tw-border-opacity)); 769 | } 770 | 771 | .border-gray-300 { 772 | --tw-border-opacity: 1; 773 | border-color: rgb(209 213 219 / var(--tw-border-opacity)); 774 | } 775 | 776 | .bg-black { 777 | --tw-bg-opacity: 1; 778 | background-color: rgb(0 0 0 / var(--tw-bg-opacity)); 779 | } 780 | 781 | .bg-gray-200 { 782 | --tw-bg-opacity: 1; 783 | background-color: rgb(229 231 235 / var(--tw-bg-opacity)); 784 | } 785 | 786 | .bg-white { 787 | --tw-bg-opacity: 1; 788 | background-color: rgb(255 255 255 / var(--tw-bg-opacity)); 789 | } 790 | 791 | .p-4 { 792 | padding: 1rem; 793 | } 794 | 795 | .p-6 { 796 | padding: 1.5rem; 797 | } 798 | 799 | .px-1 { 800 | padding-left: 0.25rem; 801 | padding-right: 0.25rem; 802 | } 803 | 804 | .px-6 { 805 | padding-left: 1.5rem; 806 | padding-right: 1.5rem; 807 | } 808 | 809 | .py-2 { 810 | padding-top: 0.5rem; 811 | padding-bottom: 0.5rem; 812 | } 813 | 814 | .pb-4 { 815 | padding-bottom: 1rem; 816 | } 817 | 818 | .pb-6 { 819 | padding-bottom: 1.5rem; 820 | } 821 | 822 | .pl-4 { 823 | padding-left: 1rem; 824 | } 825 | 826 | .pl-6 { 827 | padding-left: 1.5rem; 828 | } 829 | 830 | .pt-4 { 831 | padding-top: 1rem; 832 | } 833 | 834 | .pt-\[135px\] { 835 | padding-top: 135px; 836 | } 837 | 838 | .font-mono { 839 | font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; 840 | } 841 | 842 | .text-2xl { 843 | font-size: 1.5rem; 844 | line-height: 2rem; 845 | } 846 | 847 | .text-3xl { 848 | font-size: 1.875rem; 849 | line-height: 2.25rem; 850 | } 851 | 852 | .text-sm { 853 | font-size: 0.875rem; 854 | line-height: 1.25rem; 855 | } 856 | 857 | .text-xl { 858 | font-size: 1.25rem; 859 | line-height: 1.75rem; 860 | } 861 | 862 | .text-xs { 863 | font-size: 0.75rem; 864 | line-height: 1rem; 865 | } 866 | 867 | .font-bold { 868 | font-weight: 700; 869 | } 870 | 871 | .uppercase { 872 | text-transform: uppercase; 873 | } 874 | 875 | .italic { 876 | font-style: italic; 877 | } 878 | 879 | .leading-6 { 880 | line-height: 1.5rem; 881 | } 882 | 883 | .text-black { 884 | --tw-text-opacity: 1; 885 | color: rgb(0 0 0 / var(--tw-text-opacity)); 886 | } 887 | 888 | .text-blue-800 { 889 | --tw-text-opacity: 1; 890 | color: rgb(30 64 175 / var(--tw-text-opacity)); 891 | } 892 | 893 | .text-dracula-red { 894 | --tw-text-opacity: 1; 895 | color: rgb(255 85 85 / var(--tw-text-opacity)); 896 | } 897 | 898 | .text-gray-600 { 899 | --tw-text-opacity: 1; 900 | color: rgb(75 85 99 / var(--tw-text-opacity)); 901 | } 902 | 903 | .text-gray-700 { 904 | --tw-text-opacity: 1; 905 | color: rgb(55 65 81 / var(--tw-text-opacity)); 906 | } 907 | 908 | .text-gray-800 { 909 | --tw-text-opacity: 1; 910 | color: rgb(31 41 55 / var(--tw-text-opacity)); 911 | } 912 | 913 | .underline { 914 | text-decoration-line: underline; 915 | } 916 | 917 | .opacity-50 { 918 | opacity: 0.5; 919 | } 920 | 921 | .filter { 922 | filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow); 923 | } 924 | 925 | .transition-colors { 926 | transition-property: color, background-color, border-color, text-decoration-color, fill, stroke; 927 | transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); 928 | transition-duration: 150ms; 929 | } 930 | 931 | .duration-1000 { 932 | transition-duration: 1000ms; 933 | } 934 | 935 | .hover\:underline:hover { 936 | text-decoration-line: underline; 937 | } 938 | 939 | @media (min-width: 640px) { 940 | .sm\:left-\[5\%\] { 941 | left: 5%; 942 | } 943 | 944 | .sm\:w-\[90\%\] { 945 | width: 90%; 946 | } 947 | 948 | .sm\:pl-\[5\%\] { 949 | padding-left: 5%; 950 | } 951 | 952 | .sm\:pr-\[5\%\] { 953 | padding-right: 5%; 954 | } 955 | } 956 | 957 | @media (min-width: 768px) { 958 | .md\:left-\[10\%\] { 959 | left: 10%; 960 | } 961 | 962 | .md\:w-\[80\%\] { 963 | width: 80%; 964 | } 965 | 966 | .md\:pl-\[10\%\] { 967 | padding-left: 10%; 968 | } 969 | 970 | .md\:pr-\[10\%\] { 971 | padding-right: 10%; 972 | } 973 | } 974 | 975 | @media (min-width: 1024px) { 976 | .lg\:left-\[20\%\] { 977 | left: 20%; 978 | } 979 | 980 | .lg\:w-\[60\%\] { 981 | width: 60%; 982 | } 983 | 984 | .lg\:pl-\[20\%\] { 985 | padding-left: 20%; 986 | } 987 | 988 | .lg\:pr-\[20\%\] { 989 | padding-right: 20%; 990 | } 991 | } 992 | 993 | @media (min-width: 1280px) { 994 | .xl\:left-0 { 995 | left: 0px; 996 | } 997 | 998 | .xl\:top-0 { 999 | top: 0px; 1000 | } 1001 | 1002 | .xl\:flex { 1003 | display: flex; 1004 | } 1005 | 1006 | .xl\:hidden { 1007 | display: none; 1008 | } 1009 | 1010 | .xl\:w-\[300px\] { 1011 | width: 300px; 1012 | } 1013 | 1014 | .xl\:w-\[40\%\] { 1015 | width: 40%; 1016 | } 1017 | 1018 | .xl\:pl-\[30\%\] { 1019 | padding-left: 30%; 1020 | } 1021 | 1022 | .xl\:pr-\[30\%\] { 1023 | padding-right: 30%; 1024 | } 1025 | } 1026 | 1027 | .dark\:flex:where(.dark, .dark *) { 1028 | display: flex; 1029 | } 1030 | 1031 | .dark\:hidden:where(.dark, .dark *) { 1032 | display: none; 1033 | } 1034 | 1035 | .dark\:border-gray-800:where(.dark, .dark *) { 1036 | --tw-border-opacity: 1; 1037 | border-color: rgb(31 41 55 / var(--tw-border-opacity)); 1038 | } 1039 | 1040 | .dark\:bg-black:where(.dark, .dark *) { 1041 | --tw-bg-opacity: 1; 1042 | background-color: rgb(0 0 0 / var(--tw-bg-opacity)); 1043 | } 1044 | 1045 | .dark\:bg-dracula-background:where(.dark, .dark *) { 1046 | --tw-bg-opacity: 1; 1047 | background-color: rgb(40 42 54 / var(--tw-bg-opacity)); 1048 | } 1049 | 1050 | .dark\:text-blue-500:where(.dark, .dark *) { 1051 | --tw-text-opacity: 1; 1052 | color: rgb(59 130 246 / var(--tw-text-opacity)); 1053 | } 1054 | 1055 | .dark\:text-gray-200:where(.dark, .dark *) { 1056 | --tw-text-opacity: 1; 1057 | color: rgb(229 231 235 / var(--tw-text-opacity)); 1058 | } 1059 | 1060 | .dark\:text-gray-400:where(.dark, .dark *) { 1061 | --tw-text-opacity: 1; 1062 | color: rgb(156 163 175 / var(--tw-text-opacity)); 1063 | } 1064 | 1065 | .dark\:text-white:where(.dark, .dark *) { 1066 | --tw-text-opacity: 1; 1067 | color: rgb(255 255 255 / var(--tw-text-opacity)); 1068 | } --------------------------------------------------------------------------------