Rob's Blog | Python β€’ Rust β€’ Ramblings? https://sinon.github.io Zola en Sat, 30 Aug 2025 00:00:00 +0000 Writing more, writing TILs Sat, 30 Aug 2025 00:00:00 +0000 Unknown https://sinon.github.io/tils/til-about-tils-posts/ https://sinon.github.io/tils/til-about-tils-posts/ <p><a href="https://simonwillison.net/">Simon Willison</a> co-creator of Django, prolific blogger and general font of knowledge on how pelicans ride bicycles in this brave new LLM world, has a section of his blog devoted to TILs.</p> <p>To help make writing a more regular habit I am <del>copying</del> stealing this.</p> How Well Do New Python Type Checkers Conform? A Deep Dive into Ty, Pyrefly, and Zuban Fri, 29 Aug 2025 00:00:00 +0000 Unknown https://sinon.github.io/future-python-type-checkers/ https://sinon.github.io/future-python-type-checkers/ <ul> <li><a href="https://sinon.github.io/future-python-type-checkers/#introduction">Introduction</a> <ul> <li><a href="https://sinon.github.io/future-python-type-checkers/#the-incumbents">The Incumbents</a></li> <li><a href="https://sinon.github.io/future-python-type-checkers/#the-newcomers">The Newcomers</a> <ul> <li><a href="https://sinon.github.io/future-python-type-checkers/#ty-from-astral"><code>ty</code> from Astral</a></li> <li><a href="https://sinon.github.io/future-python-type-checkers/#pyrefly-from-meta"><code>pyrefly</code> from Meta</a></li> <li><a href="https://sinon.github.io/future-python-type-checkers/#zuban-from-david-halter"><code>zuban</code> from David Halter</a></li> </ul> </li> </ul> </li> <li><a href="https://sinon.github.io/future-python-type-checkers/#typing-conformance-suite-analysis">Typing Conformance Suite Analysis</a> <ul> <li><a href="https://sinon.github.io/future-python-type-checkers/#summary">Summary</a></li> <li><a href="https://sinon.github.io/future-python-type-checkers/#review-of-progress">Review of progress</a></li> <li><a href="https://sinon.github.io/future-python-type-checkers/#relevance">Relevance?</a> <ul> <li><a href="https://sinon.github.io/future-python-type-checkers/#the-gap-between-conformance-and-real-world-usage">The Gap Between Conformance and Real-World Usage</a></li> <li><a href="https://sinon.github.io/future-python-type-checkers/#practical-experience-vs-test-scores">Practical Experience vs. Test Scores</a></li> <li><a href="https://sinon.github.io/future-python-type-checkers/#what-this-means-for-adoption-decisions">What This Means for Adoption Decisions</a></li> </ul> </li> </ul> </li> <li><a href="https://sinon.github.io/future-python-type-checkers/#other-resources-to-learn-more">Other resources to learn more</a></li> <li><a href="https://sinon.github.io/future-python-type-checkers/#footnotes">Footnotes</a></li> </ul> <h1 id="introduction">Introduction</h1> <p>The Python type checking landscape is experiencing a particularly active phase of innovation in 2025. This year has witnessed the emergence of not one, not two, but <strong>three</strong> new Python type checking tools, each backed by significant pedigree and resources. While these tools have slightly different goals and philosophies, they share a common foundation: all are built in Rust with performance as a core design principle.</p> <p>This continued shift toward Rust-based implementations represents a significant evolution in the Python tooling ecosystem, promising faster type checking, better IDE integration, and improved developer experience for large codebases.</p> <h2 id="the-incumbents">The Incumbents</h2> <p>Before examining these new Rust-based tools, it's worth understanding the current landscape of Python type checkers that have established the foundation for static typing in Python:</p> <p><strong>mypy</strong> - The original and most widely adopted Python type checker, developed by Jukka Lehtosalo and now maintained by the mypy team. As the reference implementation for Python's type system, mypy has shaped many of the conventions and behaviors that newer tools aim to be compatible with. It's written in Python and offers comprehensive type checking capabilities, though performance can become a bottleneck on large codebases.</p> <p><strong>pyright/Pylance</strong> - Microsoft's type checker written in TypeScript/Node.js. Pyright powers the Pylance extension in VS Code and is known for its fast performance and strong IDE integration. It often implements new typing features before other checkers and provides rich editor feedback, making it popular among developers who prioritize IDE experience.</p> <p><strong>pyre</strong> - A type checker from Meta, written in a mix of OCaml and Python. Designed to handle Meta's massive Python codebase, pyre introduced several performance optimizations and incremental checking capabilities. However, Meta is now developing pyrefly as pyre's successor.</p> <p><strong>pytype</strong> - Google's type checker that takes a unique approach by performing type inference on unannotated Python code. Unlike other checkers that primarily validate existing type annotations, pytype can infer types from runtime behaviour and generate stub files for gradual typing adoption. Written in Python, it's particularly useful for analysing legacy codebases without type hints. However, Google announced in 2025 that pytype is being deprecated and Python 3.12 will be the last supported version of Python.</p> <h2 id="the-newcomers">The Newcomers</h2> <h3 id="ty-from-astral"><code>ty</code> from Astral</h3> <p><strong>Repository:</strong> <a href="https://github.com/astral-sh/ty/">https://github.com/astral-sh/ty/</a><br /> <strong>Development Repository:</strong> <a href="https://github.com/astral-sh/ruff/">https://github.com/astral-sh/ruff/</a></p> <p><strong>Key Highlights:</strong></p> <ul> <li>Strong focus on gradual guarantee principles</li> <li>Planned tight integration with Astral's existing linting tool <code>ruff</code>, with the aim to support type based linting rules.<sup class="footnote-reference"><a href="#1">1</a></sup></li> <li>Backing from a team combining Python core developers and very experienced Rust tooling developers</li> <li>Incremental computation at the function level leveraging the <a href="https://github.com/salsa-rs/salsa">salsa</a> library used by <a href="https://github.com/rust-lang/rust-analyzer/">rust-analyzer</a>. This could be particularly important on the LSP that <code>ty</code> provides to give very quick/responsive feedback loops in code editors.</li> </ul> <p><strong>Philosophy:</strong> Astral's approach emphasizes reliability and gradual adoption, making it easier for teams to incrementally add type checking to existing codebases without overwhelming developers with potential false positives.</p> <h3 id="pyrefly-from-meta"><code>pyrefly</code> from Meta</h3> <p><strong>Repository:</strong> <a href="https://github.com/facebook/pyrefly">https://github.com/facebook/pyrefly</a></p> <p><strong>Key Highlights:</strong></p> <ul> <li>Successor to Meta's existing <a href="https://github.com/facebook/pyre-check">pyre</a> type checker, designed to eventually replace it</li> <li>Enhanced type inference capabilities by default</li> <li>Potentially higher upfront adoption cost due to aggressive inference which might flag issues with correct code</li> <li>Backed by Meta's substantial engineering resources and real-world usage at scale</li> </ul> <p><strong>Philosophy:</strong> Meta prioritizes powerful inference and catching more potential issues out of the box, even if this means a steeper learning curve for teams new to type checking.</p> <h3 id="zuban-from-david-halter"><code>zuban</code> from David Halter</h3> <p><strong>Homepage:</strong> <a href="https://zubanls.com/">https://zubanls.com/</a><br /> <strong>Documentation:</strong> <a href="https://docs.zubanls.com/en/latest/">https://docs.zubanls.com/en/latest/</a></p> <p><strong>Key Highlights:</strong></p> <ul> <li>Created by the author of the popular Python LSP tool <code>jedi</code></li> <li>Aims for high-degree of compatibility with <code>mypy</code> to make adoption in large existing codebases seamless.</li> <li><del>Not FOSS<sup class="footnote-reference"><a href="#2">2</a></sup>, will require a license for codebases above 1.5 MB (~50,000 lines of code)<sup class="footnote-reference"><a href="#3">3</a></sup></del><strong>Update (September 2025):</strong> Now open source under the AGPL license, though commercial licensing is available for business who prefer to avoid AGPL compliance.</li> <li>Currently maintained by a single author seems a potential risk to long-term sustainability as Python typing does not stand still.</li> </ul> <p><strong>Philosophy:</strong> Zuban aims to provide the smoothest possible migration path from existing type checkers, particularly <code>mypy</code>, making it attractive for organizations with substantial existing typed codebases.</p> <h1 id="typing-conformance-suite-analysis">Typing Conformance Suite Analysis</h1> <p>The Python Typing Council maintains a <a href="https://github.com/python/typing/tree/main/conformance">Conformance test suite</a> which validates the behaviour of static type checkers against the expectations defined in the <a href="https://typing.python.org/en/latest/spec/index.html">Python typing specification</a>.</p> <p><code>ty</code> and <code>pyrefly</code> have not yet been added to the conformance suite, so it's harder to establish a baseline for their progress on this front.</p> <p>To help resolve this gap I have <a href="https://github.com/sinon/typing/pull/1">expanded the current test harness to support both of them</a><sup class="footnote-reference"><a href="#4">4</a></sup>, this also adds some additional debugging information to the html report to show the split between false negatives and false positives that the suite has detected in each test case.</p> <p>I've also included a local build of <code>ty</code> for two main reasons:</p> <ul> <li>I've wanted to investigate contributing to <code>ty</code> and/or <code>ruff</code> for a while, so this was good impetus to get things set up.</li> <li><code>ty</code> releases are cut relatively infrequently and I am impatient.</li> </ul> <h2 id="summary">Summary</h2> <p><strong>Generated 29/08/2025</strong></p> <blockquote> <p>NOTE</p> <p>The following section is slightly unfair, all of these tools are in alpha and only one of these tools (Zuban) has opted into the Conformance suite. This was mainly driven by own curiosity and seemed like an interesting project to understand the conformance test suite better.</p> <p>That being said even though <code>ty</code> is lagging on this metric at the moment it is still the type checker that I am most excited to use long-term because of the quality of the tooling Astral has built so far.</p> </blockquote> <table><thead><tr><th style="text-align: center">Type Checker</th><th style="text-align: center">Total Test Case Passes</th><th style="text-align: center">Total Test Case Partial</th><th style="text-align: center">Total False Positives</th><th style="text-align: center">Total False Negatives</th></tr></thead><tbody> <tr><td style="text-align: center">zuban 0.0.20</td><td style="text-align: center">97</td><td style="text-align: center">42</td><td style="text-align: center">152</td><td style="text-align: center">89</td></tr> <tr><td style="text-align: center">ty 0.0.1-alpha.19 (e9cb838b3 2025-08-19)</td><td style="text-align: center">20</td><td style="text-align: center">119</td><td style="text-align: center">371</td><td style="text-align: center">603</td></tr> <tr><td style="text-align: center">Local:ty ruff/0.12.11+27 (0bf5d2a20 2025-08-29)</td><td style="text-align: center">20</td><td style="text-align: center">119</td><td style="text-align: center">370</td><td style="text-align: center">590</td></tr> <tr><td style="text-align: center">pyrefly 0.30.0</td><td style="text-align: center">81</td><td style="text-align: center">58</td><td style="text-align: center">100</td><td style="text-align: center">187</td></tr> </tbody></table> <h2 id="review-of-progress">Review of progress</h2> <p><code>zuban</code> has a lead, having full <code>Pass</code> on ~69% of test cases, compared with ~15% for <code>ty</code> and ~58% for <code>pyrefly</code>. Which makes sense as though it's released in a similar time period to <code>ty</code> and <code>pyrefly</code> it has been in active development in private for several years.</p> <p>The thing that surprised me more was how much progress <code>pyrefly</code> has made when compared to <code>ty</code>. Both broke cover and released their first advertised alpha builds around the same time, in the run up to PyCon 2025. This can maybe be partially explained from a point raised in <a href="https://blog.edward-li.com/tech/comparing-pyrefly-vs-ty/">Edward Li's excellent blog post on the Typing Summit at PyCon 2025</a> which mentions that the <code>pyrefly</code> team devoted a lot of up-front time to solving some of the hard problems, such as generics.</p> <h2 id="relevance">Relevance?</h2> <p>I initially came across the conformance test suite because <code>ty</code> runs every PR against the test suite and diffs the PR results against the results from <code>main</code> to ensure changes are desired. From this it's become a surprisingly useful learning tool for some of the more advanced typing topics, but the advanced nature of these topics raises an important question: <strong>how relevant is the conformance suite pass rate for the average Python developer?</strong></p> <h3 id="the-gap-between-conformance-and-real-world-usage">The Gap Between Conformance and Real-World Usage</h3> <p>The conformance test suite focuses heavily on advanced typing features that, while important for the specification, may not reflect the day-to-day typing needs of most Python codebases. Many of the test cases cover complex scenarios involving:</p> <ul> <li>Advanced generic variance and bounds</li> <li>Complex protocol inheritance hierarchies</li> <li>Edge cases in structural subtyping</li> <li>Intricate interactions between multiple typing features</li> </ul> <p>In contrast, the majority of Python codebases primarily use:</p> <ul> <li>Basic type annotations (<code>str</code>, <code>int</code>, <code>List[str]</code>, etc.)</li> <li>Simple class hierarchies</li> <li>Optional types and Union types</li> <li>Basic generic containers</li> </ul> <h3 id="practical-experience-vs-test-scores">Practical Experience vs. Test Scores</h3> <p>My experience using the <code>ty</code> VSCode extension in place of <code>Pylance</code> across various projects and libraries tells a different story than the conformance test scores suggest. Despite <code>ty</code>'s relatively low 15% full pass rate, it has been surprisingly effective at catching real bugs and providing useful feedback for common typing patterns.</p> <p>This suggests that while conformance test coverage is important for specification compliance and handling edge cases, it may not be the best predictor of a type checker's utility for everyday Python development. The features that matter most for typical codebases appear to be working well across all three tools, even when they struggle with more esoteric typing scenarios.</p> <h3 id="what-this-means-for-adoption-decisions">What This Means for Adoption Decisions</h3> <p>For teams evaluating these type checkers, the conformance scores provide valuable insight into specification compliance, but shouldn't be the sole deciding factor. Consider:</p> <ul> <li><strong>For greenfield projects</strong>: Any of these tools will likely handle your immediate needs well</li> <li><strong>For large, complex codebases or libraries leaning on more esoteric generic patterns</strong>: Higher conformance scores may indicate better handling of advanced patterns you might encounter</li> <li><strong>For teams new to typing</strong>: The difference in conformance scores may be less relevant than IDE integration, error message quality, and performance</li> </ul> <h1 id="other-resources-to-learn-more">Other resources to learn more</h1> <ul> <li><a href="https://blog.edward-li.com/tech/comparing-pyrefly-vs-ty/">Edward Li's excellent blog post comparing <code>ty</code> and <code>pyrefly</code></a> which also contains the videos recorded at the PyCon typing summit which both <code>ty</code> and <code>pyrefly</code> gave presentations at.</li> <li><a href="https://www.youtube.com/watch?v=V1OmqEYoSz4">Happy Path Programming - 114 ty: Fast Python Type Checking with Carl Meyer</a></li> <li><a href="https://www.youtube.com/watch?v=huHF0Rv8L14">Happy Path Programming - 115 More Python Type Checking! Pyrefly with Aaron Pollack &amp; Steven Troxler</a></li> <li><a href="https://talkpython.fm/episodes/show/506/ty-astrals-new-type-checker-formerly-red-knot">Talk Python - ty: Astral's New Type Checker (Formerly Red-Knot)</a></li> </ul> <h1 id="footnotes">Footnotes</h1> <div class="footnote-definition" id="1"><sup class="footnote-definition-label">1</sup> <p>Which can be demonstrated in the <a href="https://github.com/astral-sh/ruff/issues?q=is%3Aissue%20state%3Aopen%20label%3Atype-inference">open issues</a> on ruff tagged with <code>type-inference</code> which are bugs or new features that can only be resolved with <code>ruff</code> having access to deeper type inference data that <code>ty</code> can supply.</p> </div> <div class="footnote-definition" id="2"><sup class="footnote-definition-label">2</sup> <p>David has indicated a plan to make <a href="https://github.com/python/typing/pull/2067#issuecomment-3177937964">source available in the future</a> when adding Zuban to the Python typing conformance suite.</p> </div> <div class="footnote-definition" id="3"><sup class="footnote-definition-label">3</sup> <p><del>Full pricing information at: https://zubanls.com/pricing/</del> <strong>Update (September 2025):</strong> Pricing is now available on request for the non-AGPL license.</p> </div> <div class="footnote-definition" id="4"><sup class="footnote-definition-label">4</sup> <p>This is just for this blog post, no plans to seek merging this.</p> </div> <!-- Reference links ---> TIL: Flowistry tool to understand information flow in Rust Thu, 28 Aug 2025 00:00:00 +0000 Unknown https://sinon.github.io/tils/til-flowistry/ https://sinon.github.io/tils/til-flowistry/ <p>When watching the excellent talk <a href="https://www.youtube.com/watch?v=R0dP-QR5wQo">Rust for Everyone</a> by Will Crichton the item that stood out as potentially most useful day to day was <a href="https://github.com/willcrichton/flowistry">Flowistry</a>. It is a VSCode extension, when a user clicks a variable the extension greys out all code that the variable does not interact with, it seems like it could be very useful when debugging.</p> About Wed, 27 Aug 2025 00:00:00 +0000 Unknown https://sinon.github.io/about/ https://sinon.github.io/about/ <p>I am Rob Hand, a Staff Software Engineer currently working on Personalisation at M&amp;S. Enjoy learning new things and using those things to solve more complex problems.</p> <p>For an (hopefully) more up to date snapshot see <a href="../now">Now</a> page.</p> <p>You can find me on <a href="https://github.com/sinon">GitHub</a> or send me <a href="https://bsky.app/profile/sinon-rh.bsky.social">a message on Bluesky</a>. My CV is available <a href="https://raw.githubusercontent.com/sinon/CV/main/cv.pdf">here</a>.</p> Boardgames Wed, 27 Aug 2025 00:00:00 +0000 Unknown https://sinon.github.io/boardgames/ https://sinon.github.io/boardgames/ Now Wed, 27 Aug 2025 00:00:00 +0000 Unknown https://sinon.github.io/now/ https://sinon.github.io/now/ <p><strong>(This is <a href="https://nownownow.com/about">a now page</a>, and if you have your own site, <a href="https://nownownow.com/about">you should make one</a>, too.)</strong></p> <ul> <li>reading: Children of Memory - Adrian Tchaikovsky</li> <li>watching: Nothing regular since Andor Season 2, looking forward to season 2 of Fallout</li> <li>learning: <a href="https://www.rust-lang.org/">RustπŸ¦€</a> and <a href="https://gleam.run/">Gleam⭐</a>.</li> <li>writing: Attempting to build the habit of Journaling with the help of <a href="https://logseq.com/">LogSeq</a></li> <li>listening: See on <a href="https://www.last.fm/user/robz88">Last.fm</a></li> <li>playing: Mainly Roguelike games that require less time commitment such as: <a href="https://store.steampowered.com/app/1280930/Astral_Ascent/">Astral Ascent</a> or <a href="https://store.steampowered.com/app/632360/Risk_of_Rain_2/">Risk of Rain 2</a></li> </ul> <p>Continuing to try and spend more time on hobbies instead of doomsrolling or watching too much YouTube once the work day is over and the kids are put to bed. <del>Not always</del> <strong>Rarely</strong> succeeding.</p> <p>Current Projects:</p> <ul> <li><a href="https://github.com/sinon/loxide">loxide</a> - Implementing an interpreter for lox language from https://craftinginterpreters.com/ in Rust. At the point of adding support for user defined functions.</li> <li><a href="https://github.com/sinon/z2p-axum">z2p-axum</a> - Following along with the Zero to Prod book but re-implementing the application in Axum instead of Actix.</li> </ul> <p>Finished:</p> <ul> <li><a href="https://github.com/sinon/chip8">chip8</a> - A <a href="https://en.wikipedia.org/wiki/CHIP-8">Chip-8</a> interpreter written in no-std Rust.</li> <li><a href="https://github.com/sinon/gridlife">gridlife</a> - A library for Conways Game of Life in Rust with a TUI to simulate random configurations.</li> <li><a href="https://github.com/sinon/pngme/">pngme</a> - Implementation of https://jrdngr.github.io/pngme_book/ which became my go to testing/learning project. Expanded the original with: GUI, Python library binding and crate splitting the project.</li> </ul> <p>On Hold:</p> <ul> <li><a href="https://github.com/sinon/snake">snake</a> - A snake clone TUI built using Ratatui</li> </ul> <p>Last updated on 27th August 2025.</p> TIL: Now page Wed, 27 Aug 2025 00:00:00 +0000 Unknown https://sinon.github.io/tils/til-now-page/ https://sinon.github.io/tils/til-now-page/ <p>Added a <a href="../../now">Now</a> page to blog based on <a href="https://nownownow.com/about">nownownow</a>. Since I don't post on social media this seemed like a nice way to share what I am up to at the moment (to my many many readers... <em><strong>crickets</strong></em>)</p> Bridging Python & Rust: A Walkthrough of using Py03 Sun, 18 May 2025 00:00:00 +0000 Unknown https://sinon.github.io/bridging-python-and-rust/ https://sinon.github.io/bridging-python-and-rust/ <ul> <li><a href="https://sinon.github.io/bridging-python-and-rust/#bridging-python-and-rust-a-practical-guide-with-pyo3">Bridging Python and Rust: A Practical Guide with PyO3</a></li> <li><a href="https://sinon.github.io/bridging-python-and-rust/#project-structure">Project Structure</a></li> <li><a href="https://sinon.github.io/bridging-python-and-rust/#step-1-exposing-rust-functions-with-pyo3">Step 1: Exposing Rust Functions with PyO3</a></li> <li><a href="https://sinon.github.io/bridging-python-and-rust/#step-2-building-and-packaging">Step 2: Building and Packaging</a></li> <li><a href="https://sinon.github.io/bridging-python-and-rust/#step-3-using-the-library-from-python">Step 3: Using the Library from Python</a></li> <li><a href="https://sinon.github.io/bridging-python-and-rust/#step-4-handling-errors">Step 4: Handling Errors</a></li> <li><a href="https://sinon.github.io/bridging-python-and-rust/#wrapping-up">Wrapping Up</a></li> </ul> <h1 id="bridging-python-and-rust-a-practical-guide-with-pyo3">Bridging Python and Rust: A Practical Guide with PyO3</h1> <p>Sometimes Python just isn't fast enough, or you want to reuse some Rust code without rewriting it. <a href="https://pyo3.rs">PyO3</a> makes it surprisingly easy to call Rust from Python (or less commonly vice-versa). Here’s how I created <a href="https://github.com/sinon/pngme/tree/main/crates/pngme-python">pngme-python</a> crate, to expose my already existing <a href="https://github.com/sinon/pngme/tree/main/crates/pngme">pngme</a> Rust crate as a python library.</p> <h2 id="project-structure">Project Structure</h2> <pre style="background-color:#191919;color:#ffffff;"><code><span>pngme/ </span><span>β”œβ”€β”€ src/ </span><span>β”‚ └── lib.rs </span><span>β”œβ”€β”€ Cargo.toml </span><span>pngme-python/ </span><span>β”œβ”€β”€ src/ </span><span>β”‚ └── lib.rs </span><span>β”œβ”€β”€ tests/ </span><span>β”‚ └── test_pngme.py </span><span>β”œβ”€β”€ Cargo.toml </span><span>β”œβ”€β”€ pyproject.toml </span><span>β”œβ”€β”€ README.md </span><span> </span></code></pre> <ul> <li><strong>pngme/src/lib.rs</strong>: The original Rust code with PNG manipulation functionality, that we want to expose as a Python library</li> <li><strong>pngme-python/src/lib.rs</strong>: The Py03 bindings.</li> <li><strong>test_pngme.py</strong>: Python tests to verify the bindings work correctly</li> <li><strong>pngme-python/src/Cargo.toml</strong>: The build configuration for the Rust portion of the Py03/Maturin build process.</li> <li><strong>pyproject.toml</strong>: Python packaging configuration for Maturin</li> </ul> <h2 id="step-1-exposing-rust-functions-with-pyo3">Step 1: Exposing Rust Functions with PyO3</h2> <p>PyO3 lets you turn Rust functions into Python-callable methods with minimal fuss. Here’s a trimmed-down version of the <code>encode</code>, <code>decode</code>, and <code>remove</code> functions from <a href="https://github.com/sinon/pngme/blob/main/crates/pngme-python/src/lib.rs">src/lib.rs</a>:</p> <pre data-lang="rust" style="background-color:#191919;color:#ffffff;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="color:#cccccc;"> </span><span>use </span><span style="color:#cccccc;">pyo3::prelude::</span><span>*</span><span style="color:#cccccc;">; </span><span style="color:#cccccc;"> </span><span style="color:#cccccc;">#[pymodule] </span><span style="color:#cccccc;">#[pyo3(name </span><span>= </span><span style="color:#ffd700;">&quot;pngme&quot;</span><span style="color:#cccccc;">)] </span><span style="color:#80d500;">mod </span><span style="color:#cccccc;">pngme_python { </span><span style="color:#cccccc;"> </span><span>use </span><span style="color:#cccccc;">pyo3::exceptions::{PyFileNotFoundError, PyIOError, PyValueError}; </span><span style="color:#cccccc;"> </span><span>use </span><span style="color:#cccccc;">pyo3::{prelude::</span><span>*</span><span style="color:#cccccc;">, PyResult}; </span><span style="color:#cccccc;"> </span><span>use </span><span style="color:#cccccc;">std::path::PathBuf; </span><span style="color:#cccccc;"> </span><span style="color:#cccccc;"> </span><span>use </span><span style="color:#cccccc;">pngme_lib::{decode </span><span>as</span><span style="color:#cccccc;"> png_decode, encode </span><span>as</span><span style="color:#cccccc;"> png_encode, remove </span><span>as</span><span style="color:#cccccc;"> png_remove, Error}; </span><span style="color:#cccccc;"> </span><span style="color:#cccccc;"> #[pyfunction] </span><span style="color:#cccccc;"> </span><span style="color:#80d500;">pub fn </span><span>encode</span><span style="color:#cccccc;">(</span><span style="font-style:italic;color:#8aa6c1;">path</span><span style="color:#cccccc;">: PathBuf, </span><span style="font-style:italic;color:#8aa6c1;">chunk_type</span><span style="color:#cccccc;">: String, </span><span style="font-style:italic;color:#8aa6c1;">message</span><span style="color:#cccccc;">: String) -&gt; PyResult&lt;()&gt; { </span><span style="color:#cccccc;"> </span><span style="color:#80d500;">let</span><span style="color:#cccccc;"> result </span><span>= </span><span style="color:#8aa6c1;">png_encode</span><span style="color:#cccccc;">(</span><span>&amp;</span><span style="color:#cccccc;">path, </span><span>&amp;</span><span style="color:#cccccc;">chunk_type, message); </span><span style="color:#cccccc;"> </span><span style="color:#80d500;">match</span><span style="color:#cccccc;"> result { </span><span style="color:#cccccc;"> </span><span style="color:#8aa6c1;">Ok</span><span style="color:#cccccc;">(</span><span>_</span><span style="color:#cccccc;">) </span><span>=&gt; </span><span style="color:#8aa6c1;">Ok</span><span style="color:#cccccc;">(()), </span><span style="color:#cccccc;"> </span><span style="color:#8aa6c1;">Err</span><span style="color:#cccccc;">(e) </span><span>=&gt; </span><span style="color:#80d500;">match</span><span style="color:#cccccc;"> e { </span><span style="color:#cccccc;"> Error::FileNotFound { </span><span>.. </span><span style="color:#cccccc;">} </span><span>=&gt; </span><span style="color:#8aa6c1;">Err</span><span style="color:#cccccc;">(PyFileNotFoundError::new_err(e.</span><span style="color:#8aa6c1;">to_string</span><span style="color:#cccccc;">())), </span><span style="color:#cccccc;"> Error::Read { source: s } </span><span>=&gt; </span><span style="color:#8aa6c1;">Err</span><span style="color:#cccccc;">(PyIOError::new_err(s.</span><span style="color:#8aa6c1;">to_string</span><span style="color:#cccccc;">())), </span><span style="color:#cccccc;"> Error::PNGParse </span><span>=&gt; </span><span style="color:#8aa6c1;">Err</span><span style="color:#cccccc;">(PyValueError::new_err(e.</span><span style="color:#8aa6c1;">to_string</span><span style="color:#cccccc;">())), </span><span style="color:#cccccc;"> Error::InvalidChunkType { source: s, </span><span>.. </span><span style="color:#cccccc;">} </span><span>=&gt; </span><span style="color:#8aa6c1;">Err</span><span style="color:#cccccc;">(PyValueError::new_err(s.</span><span style="color:#8aa6c1;">to_string</span><span style="color:#cccccc;">())), </span><span style="color:#cccccc;"> Error::PNGWrite { </span><span>.. </span><span style="color:#cccccc;">} </span><span>=&gt; </span><span style="color:#8aa6c1;">Err</span><span style="color:#cccccc;">(PyValueError::new_err(e.</span><span style="color:#8aa6c1;">to_string</span><span style="color:#cccccc;">())), </span><span style="color:#cccccc;"> Error::ChunkNotFound { </span><span>.. </span><span style="color:#cccccc;">} </span><span>=&gt; </span><span style="color:#8aa6c1;">Err</span><span style="color:#cccccc;">(PyValueError::new_err(e.</span><span style="color:#8aa6c1;">to_string</span><span style="color:#cccccc;">())), </span><span style="color:#cccccc;"> Error::StrConversion </span><span>=&gt; </span><span style="color:#8aa6c1;">Err</span><span style="color:#cccccc;">(PyValueError::new_err(e.</span><span style="color:#8aa6c1;">to_string</span><span style="color:#cccccc;">())), </span><span style="color:#cccccc;"> }, </span><span style="color:#cccccc;"> } </span><span style="color:#cccccc;"> } </span><span style="color:#cccccc;"> </span><span style="color:#cccccc;"> #[pyfunction] </span><span style="color:#cccccc;"> </span><span style="color:#80d500;">pub fn </span><span>decode</span><span style="color:#cccccc;">(</span><span style="font-style:italic;color:#8aa6c1;">path</span><span style="color:#cccccc;">: PathBuf, </span><span style="font-style:italic;color:#8aa6c1;">chunk_type</span><span style="color:#cccccc;">: String) -&gt; PyResult&lt;</span><span style="color:#8aa6c1;">String</span><span style="color:#cccccc;">&gt; { </span><span style="color:#cccccc;"> </span><span style="color:#80d500;">let</span><span style="color:#cccccc;"> result </span><span>= </span><span style="color:#8aa6c1;">png_decode</span><span style="color:#cccccc;">(</span><span>&amp;</span><span style="color:#cccccc;">path, </span><span>&amp;</span><span style="color:#cccccc;">chunk_type); </span><span style="color:#cccccc;"> </span><span style="color:#80d500;">match</span><span style="color:#cccccc;"> result { </span><span style="color:#cccccc;"> </span><span style="color:#8aa6c1;">Ok</span><span style="color:#cccccc;">(msg) </span><span>=&gt; </span><span style="color:#8aa6c1;">Ok</span><span style="color:#cccccc;">(msg), </span><span style="color:#cccccc;"> </span><span style="color:#8aa6c1;">Err</span><span style="color:#cccccc;">(e) </span><span>=&gt; </span><span style="color:#80d500;">match</span><span style="color:#cccccc;"> e { </span><span style="color:#cccccc;"> Error::FileNotFound { </span><span>.. </span><span style="color:#cccccc;">} </span><span>=&gt; </span><span style="color:#8aa6c1;">Err</span><span style="color:#cccccc;">(PyFileNotFoundError::new_err(e.</span><span style="color:#8aa6c1;">to_string</span><span style="color:#cccccc;">())), </span><span style="color:#cccccc;"> Error::Read { </span><span>.. </span><span style="color:#cccccc;">} </span><span>=&gt; </span><span style="color:#8aa6c1;">Err</span><span style="color:#cccccc;">(PyIOError::new_err(e.</span><span style="color:#8aa6c1;">to_string</span><span style="color:#cccccc;">())), </span><span style="color:#cccccc;"> Error::PNGParse </span><span>=&gt; </span><span style="color:#8aa6c1;">Err</span><span style="color:#cccccc;">(PyValueError::new_err(e.</span><span style="color:#8aa6c1;">to_string</span><span style="color:#cccccc;">())), </span><span style="color:#cccccc;"> Error::InvalidChunkType { source: s, </span><span>.. </span><span style="color:#cccccc;">} </span><span>=&gt; </span><span style="color:#8aa6c1;">Err</span><span style="color:#cccccc;">(PyValueError::new_err(s.</span><span style="color:#8aa6c1;">to_string</span><span style="color:#cccccc;">())), </span><span style="color:#cccccc;"> Error::PNGWrite { </span><span>.. </span><span style="color:#cccccc;">} </span><span>=&gt; </span><span style="color:#8aa6c1;">Err</span><span style="color:#cccccc;">(PyValueError::new_err(e.</span><span style="color:#8aa6c1;">to_string</span><span style="color:#cccccc;">())), </span><span style="color:#cccccc;"> Error::ChunkNotFound { </span><span>.. </span><span style="color:#cccccc;">} </span><span>=&gt; </span><span style="color:#8aa6c1;">Err</span><span style="color:#cccccc;">(PyValueError::new_err(e.</span><span style="color:#8aa6c1;">to_string</span><span style="color:#cccccc;">())), </span><span style="color:#cccccc;"> Error::StrConversion </span><span>=&gt; </span><span style="color:#8aa6c1;">Err</span><span style="color:#cccccc;">(PyValueError::new_err(e.</span><span style="color:#8aa6c1;">to_string</span><span style="color:#cccccc;">())), </span><span style="color:#cccccc;"> }, </span><span style="color:#cccccc;"> } </span><span style="color:#cccccc;"> } </span><span style="color:#cccccc;"> </span><span style="color:#cccccc;"> #[pyfunction] </span><span style="color:#cccccc;"> </span><span style="color:#80d500;">pub fn </span><span>remove</span><span style="color:#cccccc;">(</span><span style="font-style:italic;color:#8aa6c1;">path</span><span style="color:#cccccc;">: PathBuf, </span><span style="font-style:italic;color:#8aa6c1;">chunk_type</span><span style="color:#cccccc;">: String) -&gt; PyResult&lt;()&gt; { </span><span style="color:#cccccc;"> </span><span style="color:#80d500;">let</span><span style="color:#cccccc;"> result </span><span>= </span><span style="color:#8aa6c1;">png_remove</span><span style="color:#cccccc;">(</span><span>&amp;</span><span style="color:#cccccc;">path, </span><span>&amp;</span><span style="color:#cccccc;">chunk_type); </span><span style="color:#cccccc;"> </span><span style="color:#80d500;">match</span><span style="color:#cccccc;"> result { </span><span style="color:#cccccc;"> </span><span style="color:#8aa6c1;">Ok</span><span style="color:#cccccc;">(</span><span>_</span><span style="color:#cccccc;">) </span><span>=&gt; </span><span style="color:#8aa6c1;">Ok</span><span style="color:#cccccc;">(()), </span><span style="color:#cccccc;"> </span><span style="color:#8aa6c1;">Err</span><span style="color:#cccccc;">(e) </span><span>=&gt; </span><span style="color:#80d500;">match</span><span style="color:#cccccc;"> e { </span><span style="color:#cccccc;"> Error::FileNotFound { </span><span>.. </span><span style="color:#cccccc;">} </span><span>=&gt; </span><span style="color:#8aa6c1;">Err</span><span style="color:#cccccc;">(PyFileNotFoundError::new_err(e.</span><span style="color:#8aa6c1;">to_string</span><span style="color:#cccccc;">())), </span><span style="color:#cccccc;"> Error::Read { </span><span>.. </span><span style="color:#cccccc;">} </span><span>=&gt; </span><span style="color:#8aa6c1;">Err</span><span style="color:#cccccc;">(PyIOError::new_err(e.</span><span style="color:#8aa6c1;">to_string</span><span style="color:#cccccc;">())), </span><span style="color:#cccccc;"> Error::PNGParse </span><span>=&gt; </span><span style="color:#8aa6c1;">Err</span><span style="color:#cccccc;">(PyValueError::new_err(e.</span><span style="color:#8aa6c1;">to_string</span><span style="color:#cccccc;">())), </span><span style="color:#cccccc;"> Error::InvalidChunkType { source: s, </span><span>.. </span><span style="color:#cccccc;">} </span><span>=&gt; </span><span style="color:#8aa6c1;">Err</span><span style="color:#cccccc;">(PyValueError::new_err(s.</span><span style="color:#8aa6c1;">to_string</span><span style="color:#cccccc;">())), </span><span style="color:#cccccc;"> Error::PNGWrite { </span><span>.. </span><span style="color:#cccccc;">} </span><span>=&gt; </span><span style="color:#8aa6c1;">Err</span><span style="color:#cccccc;">(PyValueError::new_err(e.</span><span style="color:#8aa6c1;">to_string</span><span style="color:#cccccc;">())), </span><span style="color:#cccccc;"> Error::ChunkNotFound { </span><span>.. </span><span style="color:#cccccc;">} </span><span>=&gt; </span><span style="color:#8aa6c1;">Err</span><span style="color:#cccccc;">(PyValueError::new_err(e.</span><span style="color:#8aa6c1;">to_string</span><span style="color:#cccccc;">())), </span><span style="color:#cccccc;"> Error::StrConversion </span><span>=&gt; </span><span style="color:#8aa6c1;">Err</span><span style="color:#cccccc;">(PyValueError::new_err(e.</span><span style="color:#8aa6c1;">to_string</span><span style="color:#cccccc;">())), </span><span style="color:#cccccc;"> }, </span><span style="color:#cccccc;"> } </span><span style="color:#cccccc;"> } </span><span style="color:#cccccc;">} </span></code></pre> <p>The key parts of this implementation:</p> <ul> <li>The <code>#[pymodule]</code> macro creates a Python module from the Rust module it encloses.</li> <li>Each <code>#[pyfunction]</code> within the Rust module adds a Rust function to Python the <code>#[pymodule]</code>.</li> <li>Each Rust error type must be handled and mapped to the appropriate Python exceptions.</li> </ul> <h2 id="step-2-building-and-packaging">Step 2: Building and Packaging</h2> <p><a href="https://github.com/PyO3/maturin">Maturin</a> handles compiling the Rust code and packaging it as a Python wheel. Two configuration files control this process:</p> <p><strong>Cargo.toml</strong></p> <pre data-lang="toml" style="background-color:#191919;color:#ffffff;" class="language-toml "><code class="language-toml" data-lang="toml"><span style="color:#cccccc;">[package] </span><span style="color:#80d500;">name </span><span style="color:#cccccc;">= </span><span style="color:#ffd700;">&quot;pngme-python&quot; </span><span style="color:#80d500;">version </span><span style="color:#cccccc;">= </span><span style="color:#ffd700;">&quot;0.1.0&quot; </span><span style="color:#80d500;">edition </span><span style="color:#cccccc;">= </span><span style="color:#ffd700;">&quot;2021&quot; </span><span style="color:#cccccc;"> </span><span style="color:#cccccc;">[lib] </span><span style="color:#80d500;">name </span><span style="color:#cccccc;">= </span><span style="color:#ffd700;">&quot;pngme&quot; </span><span style="color:#80d500;">crate-type </span><span style="color:#cccccc;">= [</span><span style="color:#ffd700;">&quot;cdylib&quot;</span><span style="color:#cccccc;">] </span><span style="color:#cccccc;"> </span><span style="color:#cccccc;">[dependencies] </span><span style="color:#80d500;">pyo3 </span><span style="color:#cccccc;">= { </span><span style="color:#80d500;">version </span><span style="color:#cccccc;">= </span><span style="color:#ffd700;">&quot;0.18.3&quot;</span><span style="color:#cccccc;">, </span><span style="color:#80d500;">features </span><span style="color:#cccccc;">= [</span><span style="color:#ffd700;">&quot;extension-module&quot;</span><span style="color:#cccccc;">] } </span><span style="color:#80d500;">pngme-lib </span><span style="color:#cccccc;">= { </span><span style="color:#80d500;">path </span><span style="color:#cccccc;">= </span><span style="color:#ffd700;">&quot;../pngme&quot; </span><span style="color:#cccccc;">} </span></code></pre> <p><strong>pyproject.toml</strong></p> <pre data-lang="toml" style="background-color:#191919;color:#ffffff;" class="language-toml "><code class="language-toml" data-lang="toml"><span style="color:#cccccc;">[build-system] </span><span style="color:#80d500;">requires </span><span style="color:#cccccc;">= [</span><span style="color:#ffd700;">&quot;maturin&gt;=1.0,&lt;2.0&quot;</span><span style="color:#cccccc;">] </span><span style="color:#80d500;">build-backend </span><span style="color:#cccccc;">= </span><span style="color:#ffd700;">&quot;maturin&quot; </span><span style="color:#cccccc;"> </span><span style="color:#cccccc;">[project] </span><span style="color:#80d500;">name </span><span style="color:#cccccc;">= </span><span style="color:#ffd700;">&quot;pngme&quot; </span><span style="color:#80d500;">version </span><span style="color:#cccccc;">= </span><span style="color:#ffd700;">&quot;0.1.0&quot; </span><span style="color:#80d500;">description </span><span style="color:#cccccc;">= </span><span style="color:#ffd700;">&quot;Python bindings for pngme&quot; </span><span style="color:#80d500;">readme </span><span style="color:#cccccc;">= </span><span style="color:#ffd700;">&quot;README.md&quot; </span><span style="color:#cccccc;"> </span><span style="color:#cccccc;">[tool.maturin] </span><span style="color:#80d500;">features </span><span style="color:#cccccc;">= [</span><span style="color:#ffd700;">&quot;pyo3/extension-module&quot;</span><span style="color:#cccccc;">] </span></code></pre> <p>Building is as simple as:</p> <pre data-lang="bash" style="background-color:#191919;color:#ffffff;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#cccccc;"> </span><span style="color:#cccccc;">maturin develop </span></code></pre> <p>This creates a Python wheel that you can use directly or publish to PyPI.</p> <h2 id="step-3-using-the-library-from-python">Step 3: Using the Library from Python</h2> <p>Once built, and installed, just import and use the module in Python. An example can be found in <a href="https://github.com/sinon/pngme/blob/main/crates/pngme-python/tests/test_pngme.py">tests/test_pngme.py</a>:</p> <pre data-lang="python" style="background-color:#191919;color:#ffffff;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#cccccc;"> </span><span style="color:#80d500;">import </span><span style="color:#cccccc;">pngme </span><span style="color:#cccccc;"> </span><span>def test_pngme_encode</span><span style="color:#cccccc;">(): </span><span style="color:#cccccc;"> file_location </span><span>= </span><span style="color:#ffd700;">&quot;./crates/pngme-python/tests/dice.png&quot; </span><span style="color:#cccccc;"> pngme.encode(file_location, </span><span style="color:#ffd700;">&quot;ruSt&quot;</span><span style="color:#cccccc;">, </span><span style="color:#ffd700;">&quot;some message&quot;</span><span style="color:#cccccc;">) </span><span style="color:#cccccc;"> output </span><span>= </span><span style="color:#cccccc;">pngme.decode(file_location, </span><span style="color:#ffd700;">&quot;ruSt&quot;</span><span style="color:#cccccc;">) </span><span style="color:#cccccc;"> </span><span style="color:#80d500;">assert </span><span style="color:#cccccc;">output </span><span>== </span><span style="color:#ffd700;">&quot;some message&quot; </span><span style="color:#cccccc;"> pngme.remove(file_location, </span><span style="color:#ffd700;">&quot;ruSt&quot;</span><span style="color:#cccccc;">) </span><span style="color:#cccccc;"> nothing </span><span>= </span><span style="color:#cccccc;">pngme.decode(file_location, </span><span style="color:#ffd700;">&quot;ruSt&quot;</span><span style="color:#cccccc;">) </span><span style="color:#cccccc;"> </span><span style="color:#80d500;">assert </span><span style="color:#cccccc;">nothing </span><span>== </span><span style="color:#ffd700;">&quot;No secret message found&quot; </span></code></pre> <h2 id="step-4-handling-errors">Step 4: Handling Errors</h2> <p>PyO3 lets you map Rust errors to Python exceptions, so Python users get idiomatic error messages as shown below:</p> <pre data-lang="python" style="background-color:#191919;color:#ffffff;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#cccccc;"> </span><span style="color:#80d500;">import </span><span style="color:#cccccc;">pytest </span><span style="color:#cccccc;"> </span><span>def test_pngme_unknown_file</span><span style="color:#cccccc;">(): </span><span style="color:#cccccc;"> </span><span style="color:#80d500;">with </span><span style="color:#cccccc;">pytest.raises(</span><span style="color:#8aa6c1;">FileNotFoundError</span><span style="color:#cccccc;">) </span><span style="color:#80d500;">as </span><span style="color:#cccccc;">exc: </span><span style="color:#cccccc;"> pngme.encode(</span><span style="color:#ffd700;">&quot;unknown.png&quot;</span><span style="color:#cccccc;">, </span><span style="color:#ffd700;">&quot;ruSt&quot;</span><span style="color:#cccccc;">, </span><span style="color:#ffd700;">&quot;some message&quot;</span><span style="color:#cccccc;">) </span><span style="color:#cccccc;"> </span><span style="color:#80d500;">assert </span><span style="color:#ffd700;">&#39;File not found &quot;unknown.png&quot;&#39; </span><span>in </span><span style="color:#8aa6c1;">str</span><span style="color:#cccccc;">(exc.value) </span></code></pre> <h2 id="wrapping-up">Wrapping Up</h2> <p>PyO3 makes it easy to bring Rust’s speed and safety to Python, with natural error handling and a smooth workflow. If you want to squeeze more performance out of Python or reuse Rust code, give it a try.</p> <hr /> <p><strong>References:</strong></p> <ul> <li><a href="https://pyo3.rs">PyO3 Documentation</a></li> <li><a href="https://maturin.rs">Maturin Documentation</a></li> <li><a href="https://github.com/sinon/pngme/tree/main/crates/pngme-python">pngme-python Source Code</a></li> </ul> Project-Based Learning: The Method That Made Rust Finally Click Fri, 07 Feb 2025 00:00:00 +0000 Unknown https://sinon.github.io/project-based-learning/ https://sinon.github.io/project-based-learning/ <ul> <li><a href="https://sinon.github.io/project-based-learning/#why">Why?</a></li> <li><a href="https://sinon.github.io/project-based-learning/#previous-learning-attempts">Previous Learning Attempts</a></li> <li><a href="https://sinon.github.io/project-based-learning/#project-based-learning">Project based learning</a> <ul> <li><a href="https://sinon.github.io/project-based-learning/#pngme">PNGme</a></li> <li><a href="https://sinon.github.io/project-based-learning/#pngme">CodeCrafters</a></li> </ul> </li> <li><a href="https://sinon.github.io/project-based-learning/#conclusion-and-next-steps">Conclusion and Next Steps</a></li> </ul> <p>Learning a new programming language is like building muscle - sporadic gym visits won't get you far. After years of starting and abandoning Rust, I finally found a learning approach that sticks: hands-on projects that force you to write real code. This post traces my journey from tutorial hell to actually building things, and shares what worked (and what didn't) in hopes of helping others avoid the same pitfalls.</p> <h2 id="why">Why?</h2> <p>I always wanted to learn Rust, with several false starts along the way. The appeal is clear - Rust consistently ranks as the most loved programming language in Stack Overflow's annual developer survey for the past 8 years. It promises memory safety without garbage collection, fearless concurrency, and zero-cost abstractions.</p> <p>Modern programming languages require robust tools, and Rust delivers with its strict compiler, excellent tooling, and growing ecosystem. As a Python developer, Rust offers me a way to write performant, safe code without sacrificing productivity.</p> <h2 id="previous-learning-attempts">Previous Learning Attempts</h2> <p>My Rust journey has been a series of starts and stops:</p> <p>Pre-1.0 (2014): A brief experiment that ended quickly after writing a few basic functions. The borrow checker won that round.</p> <p>2020: My wife gifted me "The Rust Programming Language" book after overhearing my interest in Rust podcasts (particularly Rustacean Station). Finished the book and completed various small contained code examples but it was never put in to practice and started to fade.</p> <p>2023: Two slightly more serious attempts:</p> <ul> <li>Explored Rust + Kafka integration for a potential work proposal</li> <li>Started on <a href="https://www.zero2prod.com">Zero to Production in Rust</a> in October, though as I followed along with the book I was also <a href="https://github.com/sinon/z2p-axum">re-implementing it in Axum</a> instead of Actix Web which the book uses. This was an improvement as the topic of the book aligned nicely with my day job as a Backend focussed engineer but I found I was learning more about Axum than making strides with my Rust fundamentals.</li> </ul> <p>Late 2023: Returned to coding with Advent of Code. While this got me writing Rust daily for a few weeks, I ended up spending more time on AoC problem-solving patterns than Rust idioms.</p> <p>2024: Started preparing for another AoC attempt starting with some prep in November, more focused this time but still searching for a better learning approach.</p> <h2 id="project-based-learning">Project based learning</h2> <p>It was around this time when trying to form the habit of writing Rust with the aim of working through AoC2024 that I came across the suggestion of <a href="https://jrdngr.github.io/pngme_book/">PNGme</a> in a response to a similar request for guidance from someone learning Rust in <a href="https://old.reddit.com/r/rust">r/rust</a></p> <h3 id="pngme">PNGme</h3> <p>PNGme bills itself as "An Intermediate Rust Project". It comprises a series of chapters each with a clear goal to build some functionality that will eventually evolve into a CLI tool for reading PNG files and then embedding or reading secret messages stored within. Almost as important as the guidance are the suite of tests to verify each chapter as you go.</p> <p><a href="https://github.com/sinon/pngme/">My work</a> on this has expanded as the simple library has become a test bed to experiment with other parts of the Rust ecosystem such as:</p> <ul> <li>Python to Rust binding with <a href="https://pyo3.rs">pyO3</a> by building a <a href="https://github.com/sinon/pngme/tree/main/crates/pngme-python">PNGme python library</a> from the Rust library.</li> <li>Experimenting with GUI toolkit <a href="https://docs.rs/eframe/">eframe</a> using <a href="https://docs.rs/egui/">egui</a> by building a <a href="https://github.com/sinon/pngme/tree/main/crates/pngme-gui">GUI interface for PNGme</a>.</li> <li>A <a href="https://github.com/sinon/pngme/compare/main...pngme-www">html frontend for PNGme</a> using Maud and Axum.</li> <li>Splitting a codebase into various crates in a single workspace.</li> </ul> <h3 id="programming-projects-for-advanced-beginners">Programming Projects for Advanced Beginners</h3> <p>Upon finishing up PNGme the author gives some suggestions of other projects based learning resources. One was <a href="https://robertheaton.com/2018/12/08/programming-projects-for-advanced-beginners/">Programming Projects for Advanced Beginners</a>, a blog series by Robert Heaton which consist of prompts for small self-contained projects and some guidance on how you might approach and structure solving them.</p> <p>This led nicely into experimenting with <a href="https://docs.rs/ratatui">ratatui</a> a library designed to help you to build Text User Interfaces (TUIs) resulting in:</p> <ul> <li><a href="https://docs.rs/gridlife">gridlife</a>: A library and TUI CLI for simulating Conway's Game of Life automatons. This was a classic case of "the interest is smaller than you think". A ratatui maintainer, Orhun ParmaksΔ±z, requested changes to my repo for use in a project of theirs. The reason I had used ratatui in the first place was due to watching <a href="https://www.youtube.com/watch?v=hWG51Mc1DlM">a talk Orhun gave at EuroRust</a>. This also gave me the nudge I needed to publish the library to <a href="https://crates.io">crates.io</a>.</li> <li>A unfinished <a href="https://github.com/sinon/snake">snake</a> game.</li> </ul> <h3 id="codecrafters">CodeCrafters</h3> <p>After PNGme's success, I discovered <a href="https://codecrafters.io/">CodeCrafters</a> through Jon Gjengset's videos. The platform offers hands-on projects where you build clones of real-world tools: Git, Redis, Docker, and more. Each project breaks down into small, testable steps with clear feedback.</p> <p>What sets CodeCrafters apart is its focus on real-world implementations rather than toy problems. Building a BitTorrent client or Redis server forces you to understand both Rust and the underlying protocols. The automated tests provide immediate feedback, while the step-by-step progression keeps you motivated.</p> <p>The projects I worked on via CodeCrafters are:</p> <ul> <li><a href="https://github.com/sinon/loxide">loxide</a> - An implementation of an interpreter for the Lox language by Robert Nystrom from his excellent (and free) book <a href="https://craftinginterpreters.com/">Crafting Interpreter</a>. This has given me a new appreciation for my 4 years studying Computer Science, with some regret that my early roles as a software engineer didn't force me to make better use of what I learned before it started to atrophy.</li> <li><a href="https://github.com/sinon/rsh">rsh</a> - A POSIX shell implementation that gave me a small peek behind the curtain to the complexity within the humble shell.</li> </ul> <p>The real value of CodeCrafters is its focus on production-grade tools rather than toy problems. Building an interpreter forces you to understand Rust, lexing, parsing, and evaluation. Automated tests provide instant feedback, maintaining momentum.</p> <h2 id="conclusion-and-next-steps">Conclusion and Next Steps</h2> <p>Looking back on the last 3-4 months I am frankly shocked by how productive I have been, in that span of time I have done more development work for my own pleasure than I had in the nearly 10 years preceding it.</p> <ol> <li>Clear end goals keep you motivated</li> <li>Real-world projects force you to write idiomatic code</li> <li>Test suites provide immediate feedback</li> <li>Building actual tools is more engaging than solving puzzles</li> </ol> <p>Next steps:</p> <ul> <li>Complete the Lox Interpreter from CodeCrafters, and hopefully continue onto just following the book. As the CodeCrafters Interpreter project is not complete and stops after implementing functions.</li> <li>Contribute to some OSS project in the Rust ecosystem.</li> <li>Build a non-trivial web service in Axum.</li> </ul> <!-- Reference links ---> TIL: Cleaning exif data from images Thu, 24 Oct 2024 00:00:00 +0000 Unknown https://sinon.github.io/tils/cleaning-exif-data/ https://sinon.github.io/tils/cleaning-exif-data/ <p>When uploading images to blog from phone need to remove GPS and other private metadata. For the blog I use <a href="https://exiftool.org/">ExifTool</a> running <code>exiftool -gps:all= static/*.jpg</code></p> Stepping Out of My Comfort Zone: A Backend Engineer's Journey into Blogging Sun, 13 Oct 2024 00:00:00 +0000 Unknown https://sinon.github.io/starting-a-blog/ https://sinon.github.io/starting-a-blog/ <ul> <li><a href="https://sinon.github.io/starting-a-blog/#why-start-a-blog">Why start a blog?</a></li> <li><a href="https://sinon.github.io/starting-a-blog/#the-nuts-and-bolts">The nuts and bolts</a> <ul> <li><a href="https://sinon.github.io/starting-a-blog/#why-zola">Why Zola?</a></li> <li><a href="https://sinon.github.io/starting-a-blog/#why-github-pages">Why Github Pages?</a></li> </ul> </li> <li><a href="https://sinon.github.io/starting-a-blog/#conclusion-and-next-steps">Conclusion and Next Steps</a></li> <li><a href="https://sinon.github.io/starting-a-blog/#footnotes">Footnotes</a></li> </ul> <p>As a backend engineer with over a decade of experience, I've spent countless hours diving deep into code, architecting systems, and solving complex problems. But recently, I've felt a growing urge to step out of my comfort zone and embark on a new challenge: starting a blog. In this post, I'll discuss my motivations for starting a blog and briefly explain my choice of tools: Zola and GitHub Pages.</p> <h2 id="why-start-a-blog">Why start a blog?</h2> <ul> <li> <p><strong>Preserving Knowledge across Roles</strong>: In my current and past roles I have tried to follow parts of the recommendations in <a href="https://jmmv.dev/2021/04/always-be-quitting.html">Always be quitting</a> when it comes to documenting various aspects of my role. The problem with this is that even when it's a generic explainer or How To this information is lost to me once I move roles.</p> </li> <li> <p><strong>Personal knowledge management</strong>: Memory is imperfect, and trying to find the same piece of information you've already encountered can be frustrating, even in this brave new world of LLMs. A blog can serve as a personal wiki or Zettelkasten system, helping me organize and retrieve information more efficiently.</p> </li> <li> <p><strong><a href="https://en.wikipedia.org/wiki/Rubber_duck_debugging">Rubber duck debugging</a></strong><sup class="footnote-reference"><a href="#1">1</a></sup>: This technique for solving problems when combined with posting the updates in Slack or Teams naturally leads to a set of useful textual information, transforming this into a blog item that might help someone else or maybe more importantly my future self facing the same problem again is an attactive proposition.</p> </li> <li> <p><strong><a href="https://www.complexsystemspodcast.com/">Complex Systems Podcast</a></strong>: I have recently been enjoying the podcast Complex Systems hosted by Patrick McKenzie (Patio11) which in many of its episodes re-enforces the role of regular written communication and the benefits of it in critical thinking and self-examination.</p> </li> <li> <p><strong>"Show your workings"</strong>: During secondary school, it was drilled into me to always show my workings in mathematics and science. The right answer wasn't always enough; demonstrating how you came to that answer was almost as important<sup class="footnote-reference"><a href="#2">2</a></sup>. While I didn't fully appreciate this at the time, I've found throughout my professional life that following this mantra has solved or prevented many problems from developing.</p> <p>Blogging serves as an excellent platform for "showing my workings" in my professional context. By writing about my problem-solving processes, architectural decisions, or even my learning journey, I'm not just sharing the final solution but the entire thought process behind it. This approach offers several benefits:</p> <ul> <li>It helps me clarify my own thinking and often leads to new insights.</li> <li>It provides a valuable resource for others who might be facing similar challenges.</li> <li>It creates a record of my decision-making process, which can be incredibly useful when revisiting projects or defending choices later on.</li> <li>It encourages a culture of transparency and knowledge-sharing within the tech community.</li> </ul> <p>By treating my blog as a place to "show my workings," I'm not just creating content – I'm cultivating a habit of thorough analysis and clear communication that will serve me well throughout my career.</p> </li> </ul> <h2 id="the-nuts-and-bolts">The nuts and bolts</h2> <h3 id="why-zola">Why Zola?</h3> <ul> <li><a href="https://getzola.org">Zola</a> is written in Rust ⚑ which aligns well with my ongoing side project to improve my Rust proficiency. I'm actively seeking opportunities to apply and deepen my Rust knowledge, so Zola's Rust foundation was a significant factor in my decision. By using Zola regularly for blogging, I hope to familiarize myself with its codebase. This familiarity could potentially lead to contributing to Zola's open-source project in the future, further enhancing my Rust skills.</li> <li>It's a SSG (Static Site Generator) which works nicely with Github Pages.</li> <li>Builds off strong pre-existing tooling such as Hugo, Pelican and Jeykyll.</li> <li>It uses Tera as its template engine, which has a syntax similar to Jinja2 and Django templates. This familiarity is advantageous, as I have prior experience with both Jinja2 and Django, reducing the learning curve for creating and managing blog templates.</li> <li>Content written in CommonMark (expanded with some Github flavour via <a href="https://pulldown-cmark.github.io/pulldown-cmark/cheat-sheet.html"><code>pulldown-cmark</code></a>).</li> </ul> <h3 id="why-github-pages">Why Github Pages?</h3> <p>No real thought went into this choice, it was the default. Maybe in the future I will actually examine other options. This is mainly that it's free, easy and fits easily into my usual development flows.</p> <h2 id="conclusion-and-next-steps">Conclusion and Next Steps</h2> <p>Starting this blog represents a practical step in documenting my work and sharing knowledge. By using Zola and GitHub Pages, I'm balancing the desire to learn new tools with the need for a straightforward, low-maintenance platform.</p> <p>Moving forward, I plan to focus on:</p> <ol> <li>Documenting solutions to specific backend engineering problems I encounter</li> <li>Sharing brief explanations of useful techniques or patterns from my daily work</li> <li>Posting occasional updates on my progress with learning Rust and any Zola-related insights</li> <li>Writing about system design decisions and trade-offs from recent projects</li> </ol> <p>I don't have a fixed posting schedule, but I aim to write when I have something concrete and useful to share. The primary goal is to create a resource that's valuable for my future self, with the added benefit of potentially helping others facing similar challenges.</p> <p>This blog is a work in progress, and its direction may evolve based on what I find most useful and manageable alongside my primary work. If you happen to find something helpful here, all the better.</p> <h2 id="footnotes">Footnotes</h2> <div class="footnote-definition" id="1"><sup class="footnote-definition-label">1</sup> <p>Here is my current partner in debugging:</p> </div> <center> <img src="/RubberDuckDarkSouls.jpg" width="50%" height="50%" alt="Dark Souls Rubber duck"/> </center> <div class="footnote-definition" id="2"><sup class="footnote-definition-label">2</sup> <p>especially for the cases where you were actually wrong</p> </div> <!-- Reference links --->