diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..546304c --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,56 @@ +name: Deploy mdbook to GitHub Pages + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages +permissions: + contents: read + pages: write + id-token: write + +# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. +# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. +concurrency: + group: "pages" + cancel-in-progress: false + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install mdBook + run: | + mkdir mdbook + curl -sSL https://github.com/rust-lang/mdBook/releases/download/v0.4.40/mdbook-v0.4.40-x86_64-unknown-linux-gnu.tar.gz | tar -xz --directory=./mdbook + echo `pwd`/mdbook >> $GITHUB_PATH + + - name: Build book + run: mdbook build + + - name: Setup Pages + uses: actions/configure-pages@v4 + if: github.event_name != 'pull_request' + + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + if: github.event_name != 'pull_request' + with: + path: './book' + + deploy: + if: github.event_name != 'pull_request' + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + needs: build + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3a31070 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +# mdbook output +book/ + +# Temporary files +*.tmp +.DS_Store \ No newline at end of file diff --git a/LICENSE-APACHE b/LICENSE-APACHE new file mode 100644 index 0000000..24665b4 --- /dev/null +++ b/LICENSE-APACHE @@ -0,0 +1,162 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to source code, documentation files, and + configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (which shall not include Communications that are conspicuously + marked or otherwise designated in writing by the copyright owner + as "Not a Work"). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based upon (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and for which the separate contributions constituting Derivative + Works that do not fall under this definition may be used under the terms + of their respective licenses. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to use, reproduce, modify, distribute, sublicense, + and/or sell copies of the Work, and to permit persons to whom the + Work is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Work. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, trademark, patent, + attribution and other notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright notice to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Support. You can choose to offer, and to + charge a fee for, warranty, support, indemnity or other liability + obligations and/or rights consistent with this License. However, in + accepting such obligations, You may act only on Your own behalf and on + Your sole responsibility, not on behalf of any other Contributor, and + only if You agree to indemnify, defend, and hold each Contributor + harmless for any liability incurred by, or claims asserted against, + such Contributor by reason of your accepting any such warranty or support. + + END OF TERMS AND CONDITIONS \ No newline at end of file diff --git a/LICENSE-MIT b/LICENSE-MIT new file mode 100644 index 0000000..6802bc4 --- /dev/null +++ b/LICENSE-MIT @@ -0,0 +1,19 @@ +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/book.toml b/book.toml new file mode 100644 index 0000000..5df153d --- /dev/null +++ b/book.toml @@ -0,0 +1,17 @@ +[book] +authors = ["Rust Philosopher Contributors"] +language = "en" +multilingual = false +src = "src" +title = "Rust Philosopher" +description = "A collection of Rust best practices, intended for human and GenAI consumption" + +[build] +build-dir = "book" + +[output.html] +default-theme = "light" +preferred-dark-theme = "ayu" +git-repository-url = "https://github.com/socratic-shell/rust-philosopher" +edit-url-template = "https://github.com/socratic-shell/rust-philosopher/edit/main/{path}" +site-url = "/rust-philosopher/" diff --git a/src/SUMMARY.md b/src/SUMMARY.md new file mode 100644 index 0000000..87b92a8 --- /dev/null +++ b/src/SUMMARY.md @@ -0,0 +1,5 @@ +# Summary + +- [Welcome](./welcome.md) +- [Reference Material](./reference/README.md) + - [Alice Ryhl's "Actors with Tokio"](./reference/actors-with-tokio.md) diff --git a/src/reference/README.md b/src/reference/README.md new file mode 100644 index 0000000..a4889de --- /dev/null +++ b/src/reference/README.md @@ -0,0 +1,7 @@ +# Reference Material + +This section contains in-depth articles and reference material covering various Rust topics and patterns. + +## Contents + +- [Alice Ryhl's "Actors with Tokio"](./actors-with-tokio.md) - A comprehensive guide to building actors with Tokio directly, without using external actor libraries. \ No newline at end of file diff --git a/src/reference/actors-with-tokio.md b/src/reference/actors-with-tokio.md new file mode 100644 index 0000000..b63a84f --- /dev/null +++ b/src/reference/actors-with-tokio.md @@ -0,0 +1,385 @@ +# Alice Ryhl's "Actors with Tokio" + +> **Original Author**: [Alice Ryhl](https://github.com/Darksonn) +> **Original Publication**: 2021-02-13 (Revised: 2023-02-12) +> **Source**: [ryhl.io](https://ryhl.io/blog/actors-with-tokio/) + + + +This article is about building actors with Tokio directly, without using any +actor libraries such as Actix. This turns out to be rather easy to do, however +there are some details you should be aware of: + + 1. Where to put the `tokio::spawn` call. + 2. Struct with `run` method vs bare function. + 3. Handles to the actor. + 4. Backpressure and bounded channels. + 5. Graceful shutdown. + +The techniques outlined in this article should work with any executor, but for +simplicity we will only talk about Tokio. There is some overlap with the +[spawning] and [channel chapters] from the Tokio tutorial, and I recommend also +reading those chapters. + +[spawning]: https://tokio.rs/tokio/tutorial/spawning +[channel chapters]: https://tokio.rs/tokio/tutorial/channels + + + +Before we can talk about how to write an actor, we need to know what an actor +is. The basic idea behind an actor is to spawn a self-contained task that +performs some job independently of other parts of the program. Typically these +actors communicate with the rest of the program through the use of message +passing channels. Since each actor runs independently, programs designed using +them are naturally parallel. + +A common use-case of actors is to assign the actor exclusive ownership of some +resource you want to share, and then let other tasks access this resource +indirectly by talking to the actor. For example, if you are implementing a chat +server, you may spawn a task for each connection, and a master task that routes +chat messages between the other tasks. This is useful because the master task +can avoid having to deal with network IO, and the connection tasks can focus +exclusively on dealing with network IO. + +This article is also available as [a talk on YouTube]. + +[a talk on YouTube]: https://www.youtube.com/watch?v=fTXuGRP1ee4 + +## The Recipe + +An actor is split into two parts: the task and the handle. The task is the +independently spawned Tokio task that actually performs the duties of the actor, +and the handle is a struct that allows you to communicate with the task. + +Let's consider a simple actor. The actor internally stores a counter that is +used to obtain some sort of unique id. The basic structure of the actor would be +something like the following: +```rust +use tokio::sync::{oneshot, mpsc}; + +struct MyActor { + receiver: mpsc::Receiver, + next_id: u32, +} +enum ActorMessage { + GetUniqueId { + respond_to: oneshot::Sender, + }, +} + +impl MyActor { + fn new(receiver: mpsc::Receiver) -> Self { + MyActor { + receiver, + next_id: 0, + } + } + fn handle_message(&mut self, msg: ActorMessage) { + match msg { + ActorMessage::GetUniqueId { respond_to } => { + self.next_id += 1; + + // The `let _ =` ignores any errors when sending. + // + // This can happen if the `select!` macro is used + // to cancel waiting for the response. + let _ = respond_to.send(self.next_id); + }, + } + } +} + +async fn run_my_actor(mut actor: MyActor) { + while let Some(msg) = actor.receiver.recv().await { + actor.handle_message(msg); + } +} +``` +Now that we have the actor itself, we also need a handle to the actor. A handle +is an object that other pieces of code can use to talk to the actor, and is also +what keeps the actor alive. + +The handle will look like this: +```rust +#[derive(Clone)] +pub struct MyActorHandle { + sender: mpsc::Sender, +} + +impl MyActorHandle { + pub fn new() -> Self { + let (sender, receiver) = mpsc::channel(8); + let actor = MyActor::new(receiver); + tokio::spawn(run_my_actor(actor)); + + Self { sender } + } + + pub async fn get_unique_id(&self) -> u32 { + let (send, recv) = oneshot::channel(); + let msg = ActorMessage::GetUniqueId { + respond_to: send, + }; + + // Ignore send errors. If this send fails, so does the + // recv.await below. There's no reason to check for the + // same failure twice. + let _ = self.sender.send(msg).await; + recv.await.expect("Actor task has been killed") + } +} +``` +[full example](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=1e60fb476843fb130db9034e8ead210c) + +Let's take a closer look at the different pieces in this example. + +**`ActorMessage.`** The `ActorMessage` enum defines the kind of messages we can +send to the actor. By using an enum, we can have many different message types, +and each message type can have its own set of arguments. We return a value to +the sender by using an [`oneshot`] channel, which is a message passing channel +that allows sending exactly one message. + +In the example above, we match on the enum inside a `handle_message` method on +the actor struct, but that isn't the only way to structure this. One could also +match on the enum in the `run_my_actor` function. Each branch in this match +could then call various methods such as `get_unique_id` on the actor object. + +**Errors when sending messages.** When dealing with channels, not all errors are +fatal. Because of this, the example sometimes uses `let _ =` to ignore errors. +Generally a `send` operation on a channel fails if the receiver has been +dropped. + +The first instance of this in our example is the line in the actor where we +respond to the message we were sent. This can happen if the receiver is no +longer interested in the result of the operation, e.g. if the task that sent +the message might have been killed. + +**Shutdown of actor.** We can detect when the actor should shut down by looking +at failures to receive messages. In our example, this happens in the following +while loop: +```rust +while let Some(msg) = actor.receiver.recv().await { + actor.handle_message(msg); +} +``` +When all senders to the `receiver` have been dropped, we know that we will never +receive another message and can therefore shut down the actor. When this +happens, the call to `.recv()` returns `None`, and since it does not match the +pattern `Some(msg)`, the while loop exits and the function returns. + +**`#[derive(Clone)]`** The `MyActorHandle` struct derives the `Clone` trait. It +can do this because [`mpsc`] means that it is a multiple-producer, +single-consumer channel. Since the channel allows multiple producers, we can +freely clone our handle to the actor, allowing us to talk to it from multiple +places. + +[`oneshot`]: https://docs.rs/tokio/1/tokio/sync/oneshot/index.html +[`mpsc`]: https://docs.rs/tokio/1/tokio/sync/mpsc/index.html + +## A run method on a struct + +The example I gave above uses a top-level function that isn't defined on any +struct as the thing we spawn as a Tokio task, however many people find it more +natural to define a `run` method directly on the `MyActor` struct and spawn +that. This certainly works too, but the reason I give an example that uses a +top-level function is that it more naturally leads you towards the approach that +doesn't give you lots of lifetime issues. + +To understand why, I have prepared an example of what people unfamiliar with the +pattern often come up with. +```rust +impl MyActor { + fn run(&mut self) { + tokio::spawn(async move { + while let Some(msg) = self.receiver.recv().await { + self.handle_message(msg); + } + }); + } + + pub async fn get_unique_id(&self) -> u32 { + let (send, recv) = oneshot::channel(); + let msg = ActorMessage::GetUniqueId { + respond_to: send, + }; + + // Ignore send errors. If this send fails, so does the + // recv.await below. There's no reason to check for the + // same failure twice. + let _ = self.sender.send(msg).await; + recv.await.expect("Actor task has been killed") + } +} + +... and no separate MyActorHandle +``` +The two sources of trouble in this example are: + + 1. The `tokio::spawn` call is inside `run`. + 2. The actor and the handle are the same struct. + +The first issue causes problems because the `tokio::spawn` function requires the +argument to be `'static`. This means that the new task must own everything +inside it, which is a problem because the method borrows `self`, meaning that it +is not able to give away ownership of `self` to the new task. + +The second issue causes problems because Rust enforces the single-ownership +principle. If you combine both the actor and the handle into a single struct, +you are (at least from the compiler's perspective) giving every handle access to +the fields owned by the actor's task. E.g. the `next_id` integer should be owned +only by the actor's task, and should not be directly accessible from any of the +handles. + +That said, there is a version that works. By fixing the two above problems, you +end up with the following: +```rust +impl MyActor { + async fn run(&mut self) { + while let Some(msg) = self.receiver.recv().await { + self.handle_message(msg); + } + } +} + +impl MyActorHandle { + pub fn new() -> Self { + let (sender, receiver) = mpsc::channel(8); + let actor = MyActor::new(receiver); + tokio::spawn(async move { actor.run().await }); + + Self { sender } + } +} +``` +This works identically to the top-level function. Note that, strictly speaking, +it is possible to write a version where the `tokio::spawn` is inside `run`, but +I don't recommend that approach. + +## Variations on the theme + +The actor I used as an example in this article uses the request-response +paradigm for the messages, but you don't have to do it this way. In this section +I will give some inspiration to how you can change the idea. + +### No responses to messages + +The example I used to introduce the concept includes a response to the messages +sent over a `oneshot` channel, but you don't always need a response at all. In +these cases there's nothing wrong with just not including the `oneshot` channel +in the message enum. When there's space in the channel, this will even allow you +to return from sending before the message has been processed. + +You should still make sure to use a bounded channel so that the number of +messages waiting in the channel don't grow without bound. In some cases this +will mean that sending still needs to be an async function to handle the cases +where the `send` operation needs to wait for more space in the channel. + +However there is an alternative to making `send` an async method. You can use +the `try_send` method, and handle sending failures by simply killing the actor. +This can be useful in cases where the actor is managing a `TcpStream`, +forwarding any messages you send into the connection. In this case, if writing +to the `TcpStream` can't keep up, you might want to just close the connection. + +### Multiple handle structs for one actor + +If an actor needs to be sent messages from different places, you can use +multiple handle structs to enforce that some message can only be sent from some +places. + +When doing this you can still reuse the same `mpsc` channel internally, with an +enum that has all the possible message types in it. If you _do_ want to use +separate channels for this purpose, the actor can use [`tokio::select!`] to +receive from multiple channels at once. +```rs +loop { + tokio::select! { + Some(msg) = chan1.recv() => { + // handle msg + }, + Some(msg) = chan2.recv() => { + // handle msg + }, + else => break, + } +} +``` +You need to be careful with how you handle when the channels are closed, as +their `recv` method immediately returns `None` in this case. Luckily the +`tokio::select!` macro lets you handle this case by providing the pattern +`Some(msg)`. If only one channel is closed, that branch is disabled and the +other channel is still received from. When both are closed, the else branch runs +and uses `break` to exit from the loop. + +[`tokio::select!`]: https://docs.rs/tokio/1/tokio/macro.select.html + +### Actors sending messages to other actors + +There is nothing wrong with having actors send messages to other actors. To do +this, you can simply give one actor the handle of some other actor. + +You need to be a bit careful if your actors form a cycle, because by holding on +to each other's handle structs, the last sender is never dropped, preventing +shutdown. To handle this case, you can have one of the actors have two handle +structs with separate `mpsc` channels, but with a `tokio::select!` that looks +like this: +```rs +loop { + tokio::select! { + opt_msg = chan1.recv() => { + let msg = match opt_msg { + Some(msg) => msg, + None => break, + }; + // handle msg + }, + Some(msg) = chan2.recv() => { + // handle msg + }, + } +} +``` +The above loop will always exit if `chan1` is closed, even if `chan2` is still +open. If `chan2` is the channel that is part of the actor cycle, this breaks the +cycle and lets the actors shut down. + +An alternative is to simply call [`abort`] on one of the actors in the cycle. + +[`abort`]: https://docs.rs/tokio/1/tokio/task/struct.JoinHandle.html#method.abort + +### Multiple actors sharing a handle + +Just like you can have multiple handles per actor, you can also have multiple +actors per handle. The most common example of this is when handling a connection +such as a `TcpStream`, where you commonly spawn two tasks: one for reading and +one for writing. When using this pattern, you make the reading and writing tasks +as simple as you can — their only job is to do IO. The reader task will just +send any messages it receives to some other task, typically another actor, and +the writer task will just forward any messages it receives to the connection. + +This pattern is very useful because it isolates the complexity associated with +performing IO, meaning that the rest of the program can pretend that writing +something to the connection happens instantly, although the actual writing +happens sometime later when the actor processes the message. + +## Beware of cycles + +I already talked a bit about cycles under the heading “Actors sending messages +to other actors”, where I discussed shutdown of actors that form a cycle. +However, shutdown is not the only problem that cycles can cause, because a cycle +can also result in a deadlock where each actor in the cycle is waiting for the +next actor to receive a message, but that next actor wont receive that message +until its next actor receives a message, and so on. + +To avoid such a deadlock, you must make sure that there are no cycles of +channels with bounded capacity. The reason for this is that the `send` method on +a bounded channel does not return immediately. Channels whose `send` method +always returns immediately do not count in this kind of cycle, as you cannot +deadlock on such a `send`. + +Note that this means that a oneshot channel cannot be part of a deadlocked +cycle, since their `send` method always returns immediately. Note also that if +you are using `try_send` rather than `send` to send the message, that also +cannot be part of the deadlocked cycle. + +Thanks to [matklad](https://matklad.github.io/) for pointing out the issues +with cycles and deadlocks. diff --git a/src/welcome.md b/src/welcome.md new file mode 100644 index 0000000..0b10126 --- /dev/null +++ b/src/welcome.md @@ -0,0 +1,14 @@ +# Welcome + +Welcome to Rust Philosopher, a collection of Rust best practices intended for both human and GenAI consumption. + +This book aims to provide practical guidance and reference material for Rust development, covering patterns, best practices, and real-world examples that can help developers write better Rust code. + +## What You'll Find Here + +This documentation is organized into several sections: + +- **Reference Material**: Deep-dive articles and guides on specific Rust topics and patterns +- More sections coming soon... + +Whether you're a beginner learning Rust or an experienced developer looking for best practices, this resource aims to provide valuable insights and practical examples.