<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Lori-Shu</title>
    <description>The latest articles on DEV Community by Lori-Shu (@lorishu).</description>
    <link>https://dev.to/lorishu</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3968337%2F8e47296c-e45d-440a-a8d1-8667409160db.png</url>
      <title>DEV Community: Lori-Shu</title>
      <link>https://dev.to/lorishu</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/lorishu"/>
    <language>en</language>
    <item>
      <title>Bridging the Gap: How Software Displays Modern 10-bit HDR Content on SDR Monitors</title>
      <dc:creator>Lori-Shu</dc:creator>
      <pubDate>Sun, 08 Nov 2026 15:58:48 +0000</pubDate>
      <link>https://dev.to/lorishu/bridging-the-gap-how-software-displays-modern-10-bit-hdr-content-on-sdr-monitors-2pef</link>
      <guid>https://dev.to/lorishu/bridging-the-gap-how-software-displays-modern-10-bit-hdr-content-on-sdr-monitors-2pef</guid>
      <description>&lt;p&gt;In the modern media landscape, producing 10-bit HDR content has become the unquestioned industry standard. Yet, consumer display hardware hasn't evolved at quite the same breakneck pace; a vast majority of monitors still operate natively on the 8-bit SDR standard.&lt;/p&gt;

&lt;p&gt;Despite this hardware mismatch, we expect to seamlessly stream and view cutting-edge HDR media on our everyday screens. This exact friction birthed the software transformation pipeline. To bridge the gap and faithfully render 10-bit HDR content on legacy hardware, media players and graphics pipelines rely on two major transformations: Color Space Conversion and Tone Mapping.&lt;/p&gt;

&lt;h1&gt;
  
  
  1. Color Space Conversion (BT.2020 to BT.709)
&lt;/h1&gt;

&lt;p&gt;Most 10-bit HDR content is mastered in BT.2020NCL (Non-Constant Luminance), a wide-gamut standard defined by the International Telecommunication Union (ITU). However, standard monitors are calibrated for BT.709—the traditional sRGB-equivalent color space.&lt;/p&gt;

&lt;p&gt;Because contemporary displays and video pipelines must support both, mainstream media software uses a transformation matrix on the fly. This matrix maps the massive color volume of BT.2020 down into the narrower boundaries of BT.709 without completely distorting the color palette.&lt;/p&gt;

&lt;h1&gt;
  
  
  2. Tone Mapping (Adapting Luminance for SDR)
&lt;/h1&gt;

&lt;p&gt;Color space conversion alone isn't enough; if you only translate the colors without adjusting brightness, the image will look washed out or severely clipped because HDR's peak luminance far exceeds what an SDR panel can physically emit.&lt;/p&gt;

&lt;p&gt;To solve this, software employs tone mapping, which handles different HDR mastering standards:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SMPTE ST 2084 (Perceptual Quantizer / PQ)&lt;/strong&gt;: Widely adopted in cinema and professional media, PQ uses an absolute luminance scale (measured in nits, up to 10,000).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ARIB STD-B67 (Hybrid Log-Gamma / HLG)&lt;/strong&gt;: Popular in broadcasting, HLG is relative rather than absolute, making it inherently more backward-compatible with SDR displays—though often at the cost of peak visual fidelity compared to PQ.&lt;/p&gt;

&lt;p&gt;To make these high-brightness formats viewable on a standard SDR monitor, media software compresses the dynamic range, mapping ultra-bright highlights down into the limited brightness range of standard panels.&lt;/p&gt;

&lt;h1&gt;
  
  
  Engineering Insight
&lt;/h1&gt;

&lt;p&gt;Frictions between hardware evolution and software capability are a constant reality in systems engineering. Technology never moves at a uniform pace across every sector; hardware cycles are slow and capital-intensive, while software can iterate almost overnight. Engineers relentlessly build clever abstractions—like dynamic color matrices and real-time tone mapping—to bridge these physical gaps and keep our digital experiences seamless.&lt;/p&gt;

</description>
      <category>frontend</category>
      <category>hardware</category>
      <category>software</category>
      <category>softwaredevelopment</category>
    </item>
    <item>
      <title>Rust Toolchain Migration: Supercharging Build Times with LLD on Windows</title>
      <dc:creator>Lori-Shu</dc:creator>
      <pubDate>Tue, 03 Nov 2026 14:35:07 +0000</pubDate>
      <link>https://dev.to/lorishu/rust-toolchain-migration-supercharging-build-times-with-lld-on-windows-ohm</link>
      <guid>https://dev.to/lorishu/rust-toolchain-migration-supercharging-build-times-with-lld-on-windows-ohm</guid>
      <description>&lt;p&gt;Recently, driven by the need to slash compilation and linking times, I migrated the toolchain for my pet Rust project. Previously, the project relied on the battle-tested &lt;code&gt;stable-x86_64-pc-windows-gnu&lt;/code&gt; toolchain using the legacy &lt;code&gt;ld&lt;/code&gt; linker—a notorious bottleneck responsible for much of Rust's sluggish build reputation on Windows.&lt;/p&gt;

&lt;p&gt;While the speed payoff of switching to the &lt;code&gt;lld&lt;/code&gt; linker was immediate, the transition exposed a labyrinth of subtle, platform-specific hurdles on Windows. Before diving into the migration details, let’s quickly break down the anatomy of how Rust compiles code.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1eg62txze4pjf80xfusp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1eg62txze4pjf80xfusp.png" alt=" " width="704" height="384"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Under the Hood: The Rust Compilation Pipeline
&lt;/h1&gt;

&lt;p&gt;If you trace a Rust file from source text to binary, it moves through several sophisticated stages:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AST (Abstract Syntax Tree)&lt;/strong&gt;: rustc parses the source code into an AST, where macros are expanded.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;HIR (High-Level Intermediate Representation)&lt;/strong&gt;: The AST translates into HIR. This is where Rust’s borrow checker flexes its muscles, enforcing memory safety and ownership rules.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;MIR (Mid-Level Intermediate Representation)&lt;/strong&gt;: The HIR simplifies into MIR, marking the end of rustc's proprietary heavy lifting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;LLVM Optimization&lt;/strong&gt;: The pipeline hands off to the LLVM infrastructure. MIR is translated into platform-agnostic bitcode, where LLVM applies State-Of-The-Art (SOTA) optimizations. (LLVM’s power is precisely why Apple, Google, and massive Linux/Windows ecosystems rely on it).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Code Generation &amp;amp; Linking&lt;/strong&gt;: LLVM generates machine code for specific target architectures, after which linkers bundle system and third-party libraries into the final executable.&lt;/p&gt;

&lt;h1&gt;
  
  
  The Windows Toolchain Maze: GNU vs. GnuLLVM
&lt;/h1&gt;

&lt;p&gt;While Rust packages essential LLVM components out of the box, target-specific concerns—like operating system interfaces, C calling conventions, and ABIs—are bound to external packages.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The GNU Target (&lt;code&gt;stable-x86_64-pc-windows-gnu&lt;/code&gt;)&lt;/strong&gt;: Simply requires MinGW-W64-builds to complete the toolchain pipeline.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The GnuLLVM Target (&lt;code&gt;stable-x86_64-pc-windows-gnullvm&lt;/code&gt;)&lt;/strong&gt;: Requires standalone LLVM and llvm-mingw.&lt;/p&gt;

&lt;p&gt;Navigating llvm-mingw can be jarring due to the sheer volume of overlapping binaries. Essentially, stock LLVM builds provide target-agnostic components that aren't inherently tied to MSVC or GNU C conventions. llvm-mingw bridges this gap, translating raw machine code into standard MinGW-compatible formats that Windows can execute.&lt;/p&gt;

&lt;h1&gt;
  
  
  Lessons Learned from the Trenches
&lt;/h1&gt;

&lt;p&gt;Taming Windows toolchains is famously painful. It's the primary reason many developers flee to environments like MSYS2 or Windows Subsystem for Linux (WSL). However, because my pet project is a GUI application, native Windows integration is a must, and WSL's graphical UI maturity still introduces friction for native desktop apps.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Golden Rule&lt;/strong&gt;: Infrastructure upgrades like compiler toolchains should always be treated with extreme caution. Rigorous staging and testing are non-negotiable—never perform radical toolchain experiments blindly in production environments.&lt;/p&gt;

&lt;h1&gt;
  
  
  Bonus: Alternative Paths to Speed
&lt;/h1&gt;

&lt;p&gt;If you want to tap into &lt;code&gt;lld&lt;/code&gt;'s blazing speed without undergoing a full toolchain migration, you can adopt it simply by changing some settings of your projects. Modern alternatives are pushing the boundaries of link-time performance even further. If you are feeling adventurous, check out these next-gen linkers built by hard-core pioneers:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/rui314/mold" rel="noopener noreferrer"&gt;mold&lt;/a&gt; — A high-performance, modern linker designed to drastically reduce build times.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/wild-linker/wild" rel="noopener noreferrer"&gt;wild&lt;/a&gt; — An experimental, incredibly fast linker written in Rust.&lt;/p&gt;

</description>
      <category>performance</category>
      <category>rust</category>
      <category>tooling</category>
    </item>
    <item>
      <title>Optimize Service Logic of Your Rust Code by Replacing Arc::clone with Borrowing</title>
      <dc:creator>Lori-Shu</dc:creator>
      <pubDate>Thu, 29 Oct 2026 06:23:28 +0000</pubDate>
      <link>https://dev.to/lorishu/optimize-service-logic-of-your-rust-code-by-replacing-arcclone-with-borrowing-4ko7</link>
      <guid>https://dev.to/lorishu/optimize-service-logic-of-your-rust-code-by-replacing-arcclone-with-borrowing-4ko7</guid>
      <description>&lt;p&gt;In a previous post, I discussed the performance benefits of exposing references rather than cloning data outright. While refining my application logic recently, I realized that this principle applies broadly: eliminating unnecessary clones is one of the easiest ways to clean up general Rust code.&lt;/p&gt;

&lt;h1&gt;
  
  
  The &lt;code&gt;Arc::clone&lt;/code&gt; Trap
&lt;/h1&gt;

&lt;p&gt;Rust beginners often attempt to resolve stubborn borrow checker errors by simply cloning variables and moving them into closures or functions. While move is a fundamental idiom in Rust, excessive cloning should be avoided in production-grade code.&lt;/p&gt;

&lt;p&gt;I’ve certainly been guilty of scattering &lt;code&gt;Arc::clone&lt;/code&gt; across a codebase just to satisfy the compiler and push a feature across the finish line. The underlying motivation is often a casual approach to Arc.&lt;/p&gt;

&lt;p&gt;Arc (Atomic Reference Counted pointer) is a foundational smart pointer for multithreaded Rust. Even though cloning an Arc is far more lightweight than cloning a deep struct, its overhead is non-trivial:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Atomic Operations&lt;/strong&gt;: Cloning an Arc triggers atomic increment instructions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cache Invalidation&lt;/strong&gt;: These atomic operations cause cache line invalidation across CPU cores.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Contention&lt;/strong&gt;: High-frequency cloning under heavy concurrency can introduce subtle performance bottlenecks.&lt;/p&gt;

&lt;p&gt;Standard borrowing (&lt;code&gt;&amp;amp; or &amp;amp;mut&lt;/code&gt;), by contrast, incurs none of these costs.&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;Aside from scenarios where ownership transfer is strictly required--such as spawning new threads or moving data across async task boundaries—we should look to optimize our Rust architecture by replacing unnecessary &lt;code&gt;Arc::clone&lt;/code&gt; calls with proper borrowing. Clean code isn't just about compiling successfully; it's about respecting ownership and memory layout.&lt;/p&gt;

</description>
      <category>rust</category>
      <category>programming</category>
      <category>softwaredevelopment</category>
      <category>performance</category>
    </item>
    <item>
      <title>Implicit Function or Implicit Type? Demystifying Rust Closures</title>
      <dc:creator>Lori-Shu</dc:creator>
      <pubDate>Thu, 22 Oct 2026 14:54:15 +0000</pubDate>
      <link>https://dev.to/lorishu/implicit-function-or-implicit-type-demystifying-rust-closures-1pg7</link>
      <guid>https://dev.to/lorishu/implicit-function-or-implicit-type-demystifying-rust-closures-1pg7</guid>
      <description>&lt;h2&gt;
  
  
  Closure Note
&lt;/h2&gt;

&lt;p&gt;Rust closures often feel like a mysterious monolith, but they were born to solve a fundamental need: first-class functions behavior with state capture.&lt;/p&gt;

&lt;p&gt;In conventional linear execution, named functions are invoked immediately. Standard function pointers (fn()) let you store and call functions later, but they lack a crucial capability—they cannot capture or remember variables from their surrounding environment.&lt;/p&gt;

&lt;p&gt;That is why closures were introduced to Rust. Although we casually refer to closures as "functions," it is much more accurate to think of them as concrete, compiler-generated types.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Closures Work Under the Hood
&lt;/h2&gt;

&lt;p&gt;When you write a closure, it captures the variables it needs from its enclosing scope. Behind the scenes, the Rust compiler:&lt;/p&gt;

&lt;p&gt;Generates an anonymous, unique type for the closure.&lt;/p&gt;

&lt;p&gt;Stores the captured variables inside fields of this generated struct.&lt;/p&gt;

&lt;p&gt;Applies borrowing rules: By default, it uses "reborrowing" mechanics identical to regular function calls with reference arguments.&lt;/p&gt;

&lt;p&gt;Implements function traits: The compiler automatically implements one of the core closure traits based on how the variables are captured:&lt;/p&gt;

&lt;p&gt;Fn: Implemented if the closure only borrows captured variables immutably. It can be called multiple times.&lt;/p&gt;

&lt;p&gt;FnMut: Implemented if the closure borrows any variables mutably. It can also be called multiple times, modifying state along the way.&lt;/p&gt;

&lt;p&gt;FnOnce: The parent trait of the others. If the closure takes ownership of captured variables (such as via the move keyword) or consumes them, it implements FnOnce and can typically only be executed once.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Role of the move Keyword
&lt;/h2&gt;

&lt;p&gt;As counterintuitive, closure with move won't take ownership of the instances behind reference types. It just moves the variable into the closure. What is different from closure without move is that the outer variables (with owned or reference types) will be invalid after the closure is constructed.&lt;/p&gt;

&lt;p&gt;Because of Rust's rigorous borrow checker, we frequently rely on the move keyword with closures to satisfy lifetime constraints—especially when spawning threads or returning closures from functions. Instead of borrowing variables from the stack, move forces the closure to take ownership of those variables, moving them directly into the compiler-generated struct.&lt;/p&gt;

&lt;p&gt;While optimizing Rust code often involves careful lifetime management, mastering how closures handle memory under the hood is a rare and invaluable skill for writing high-performance systems.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>rust</category>
      <category>softwaredevelopment</category>
    </item>
    <item>
      <title>To Borrow or To Clone: The Rust Struct Exposure Dilemma</title>
      <dc:creator>Lori-Shu</dc:creator>
      <pubDate>Wed, 14 Oct 2026 03:43:23 +0000</pubDate>
      <link>https://dev.to/lorishu/to-borrow-or-to-clone-the-rust-struct-exposure-dilemma-pac</link>
      <guid>https://dev.to/lorishu/to-borrow-or-to-clone-the-rust-struct-exposure-dilemma-pac</guid>
      <description>&lt;p&gt;Every Rust developer eventually hits a crossroads when trying to expose private struct fields cleanly. Here is a breakdown of the common approaches, their hidden costs, and how to strike the right balance.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The Brute-Force Route: Making Fields Public
&lt;/h2&gt;

&lt;p&gt;The most direct fix for visibility issues is simply marking fields pub. While this instantly grants access, it sacrifices encapsulation and can introduce dangerous side effects. Giving outside callers full access means anyone can mutate internal state or even move values out of the struct entirely, risking broken invariants.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. The Convenience Trap: Overusing Clone
&lt;/h2&gt;

&lt;p&gt;Another route is writing getter methods that return cloned instances. It is frictionless enough that developers sometimes abuse it purely as an escape hatch to silence the borrow checker.&lt;/p&gt;

&lt;p&gt;The downside? Cloning performs a deep copy of the underlying memory. If you are exposing large data structures, frequent cloning introduces hidden CPU overhead and memory bandwidth pressure.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. The Idiomatic Choice: Strategic Borrowing
&lt;/h2&gt;

&lt;p&gt;The high-performance solution is leveraging references and lifetime annotations. Although lifetimes can steepen the learning curve for developers coming from garbage-collected languages, borrowing is vastly more efficient than cloning. No matter how massive the original type is, a reference only copies a pointer-sized footprint.&lt;/p&gt;

&lt;p&gt;Yet, zero-cost abstractions demand careful design. Combining complex lifetime bounds with multi-threaded safety and thread-pool concurrency can escalate quickly. Ultimately, writing production-grade Rust comes down to balancing clean borrowing strategies with minimizing resource contention.&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>rust</category>
      <category>softwareengineering</category>
    </item>
    <item>
      <title>From Thread to Task: Modern Concurrency Patterns</title>
      <dc:creator>Lori-Shu</dc:creator>
      <pubDate>Thu, 08 Oct 2026 14:48:06 +0000</pubDate>
      <link>https://dev.to/lorishu/from-thread-to-task-modern-concurrency-patterns-2oi3</link>
      <guid>https://dev.to/lorishu/from-thread-to-task-modern-concurrency-patterns-2oi3</guid>
      <description>&lt;p&gt;While conventional OS threading models offer raw parallel execution, modern programming languages have evolved distinct abstractions to overcome thread overhead and ergonomics challenges.&lt;/p&gt;

&lt;h2&gt;
  
  
  TypeScript &amp;amp; Python: Single-Threaded Event Loops
&lt;/h2&gt;

&lt;p&gt;TypeScript and Python historically embraced a single-threaded event loop architecture, multiplexing execution over a single OS thread to yield non-blocking I/O and "emulated" parallelism without the headache of thread-resource management or lock contention. However, to bypass the CPU bottlenecks of this single-threaded design, Python has recently moved toward true OS-thread concurrency models (i.e., free-threaded CPython).&lt;/p&gt;

&lt;h2&gt;
  
  
  Go &amp;amp; Java: Managed Green Threads
&lt;/h2&gt;

&lt;p&gt;Go and Java (via Virtual Threads) opted for user-space green-threading models (M:N scheduling), where a fixed pool of carrier OS threads executes millions of lightweight tasks. This drastically boosts throughput while preserving a synchronous, blocking programming style that seamlessly blends with legacy language syntax--bypassing the ecosystem splits common to traditional async models.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rust: Stackless State Machines
&lt;/h2&gt;

&lt;p&gt;Rust takes a radically explicit approach by compiling async functions into zero-cost, stackless state machines. Async tasks in Rust are typed as Futures—essentially tagged enums representing distinct state transitions. Built around an async/await syntax, Rust futures are passive by default and only execute when explicitly polled by an async runtime/scheduler.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trade-offs &amp;amp; Abstraction Costs
&lt;/h2&gt;

&lt;p&gt;No single model completely solves concurrency without trade-offs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The Function Coloring Problem: Both TypeScript and Rust inherit function coloring via async/await, creating friction when bridging synchronous blocking contexts with async runtimes.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Ergonomics &amp;amp; Mental Overhead: Java’s approach can demand additional developer overhead when orchestrating complex future/task pipelines manually.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Implicit Contexts: Go’s seamless approach feels nearly optimal, yet it conceals execution boundaries by making concurrent tasks look identical to standard synchronous functions.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>architecture</category>
      <category>go</category>
      <category>programming</category>
      <category>softwareengineering</category>
    </item>
    <item>
      <title>Architectural Decisions: Multi-Process vs. Multi-Threading</title>
      <dc:creator>Lori-Shu</dc:creator>
      <pubDate>Wed, 30 Sep 2026 15:47:28 +0000</pubDate>
      <link>https://dev.to/lorishu/architectural-decisions-multi-process-vs-multi-threading-2fif</link>
      <guid>https://dev.to/lorishu/architectural-decisions-multi-process-vs-multi-threading-2fif</guid>
      <description>&lt;p&gt;When designing parallel and concurrent systems, developers invariably face a core architectural dilemma: Multi-Process vs. Multi-Threading.&lt;/p&gt;

&lt;p&gt;If you learned systems programming in C, you likely remember fork() and join() (or waitpid()) as the default primitives for spawning and managing execution units. In the early days of Linux, POSIX thread implementations were notoriously fragile, making process cloning the de facto standard for parallelism.&lt;/p&gt;

&lt;p&gt;However, as operating systems matured and production-grade threading models stabilized, a massive wave of systems migrated toward multi-threading.&lt;/p&gt;

&lt;h1&gt;
  
  
  The Case for Multi-Threading
&lt;/h1&gt;

&lt;p&gt;Multi-threaded architectures offer compelling performance and developer-experience advantages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Seamless Memory Sharing:&lt;/strong&gt; Sharing state and pointers across threads is fundamentally simpler and cheaper than orchestrating Inter-Process Communication (IPC) mechanisms like shared memory segments, pipes, or Unix domain sockets.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lower Allocation Overhead:&lt;/strong&gt; Spawning a thread carries significantly less CPU and memory overhead compared to cloning an entire process address space.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Predictable Lifecycle Management:&lt;/strong&gt; Thread lifecycles are naturally tied to their parent process. When the host process exits, its threads are cleaned up automatically, reducing the risk of dangling zombie processes or lingering resource leaks.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Why Multi-Process Is Far From Obsolete
&lt;/h1&gt;

&lt;p&gt;Despite the popularity of threads, the multi-process model remains indispensable for specific high-assurance workloads:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Error Isolation &amp;amp; Fault Tolerance:&lt;/strong&gt; A panic, segmentation fault, or unhandled exception in a worker thread can crash the entire application. In contrast, a crashed process dies in isolation, leaving the primary supervisor running intact—making it ideal for sandboxing and plugin ecosystems (e.g., modern web browsers).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Granular Observability &amp;amp; Debugging:&lt;/strong&gt; Because each child process operates with a distinct PID, OS-level tools like &lt;code&gt;htop&lt;/code&gt;, &lt;code&gt;perf&lt;/code&gt;, and &lt;code&gt;strace&lt;/code&gt; can monitor resource usage, memory footprints, and system calls per unit much more cleanly than inside a dense multi-threaded runtime.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Language-Specific Workarounds:&lt;/strong&gt; In languages constrained by a Global Interpreter Lock (GIL)—such as Python or Ruby—multi-processing remains the primary strategy for bypassing single-core bottlenecks to achieve true multi-core utilization.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Summary
&lt;/h1&gt;

&lt;p&gt;Neither pattern is inherently superior; the optimal choice depends on your application's constraints:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reach for Multi-Threading when performance, shared memory access, and low latency are your top concerns.&lt;/li&gt;
&lt;li&gt;Reach for Multi-Processing when safety, crash resilience, and strict fault isolation outweigh the overhead of IPC.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>architecture</category>
      <category>computerscience</category>
      <category>performance</category>
      <category>systemdesign</category>
    </item>
    <item>
      <title>Shrinking Your Rust Allocations: Replacing Vec and String with Boxed Slices</title>
      <dc:creator>Lori-Shu</dc:creator>
      <pubDate>Mon, 28 Sep 2026 16:00:43 +0000</pubDate>
      <link>https://dev.to/lorishu/shrinking-your-rust-allocations-replacing-vec-and-string-with-boxed-slices-20jl</link>
      <guid>https://dev.to/lorishu/shrinking-your-rust-allocations-replacing-vec-and-string-with-boxed-slices-20jl</guid>
      <description>&lt;p&gt;When building high-performance systems in Rust, managing dynamic allocations efficiently is key to keeping your memory footprint lean.&lt;/p&gt;

&lt;p&gt;Developers often reach for &lt;code&gt;Vec&amp;lt;T&amp;gt;&lt;/code&gt; and &lt;code&gt;String&lt;/code&gt; by default whenever they need owned collections. However, if data is read-only after creation—such as static assets, loaded images, or unmodifiable configuration strings—holding onto expandable collections wastes unnecessary bytes on the stack.&lt;/p&gt;

&lt;p&gt;By "downgrading" fixed-size collections from dynamic types like &lt;code&gt;Vec&amp;lt;T&amp;gt;&lt;/code&gt; and &lt;code&gt;String&lt;/code&gt; to boxed slices like &lt;code&gt;Box&amp;lt;[T]&amp;gt;&lt;/code&gt; and &lt;code&gt;Box&amp;lt;str&amp;gt;&lt;/code&gt;, you can trim excess heap metadata without sacrificing ownership.&lt;/p&gt;

&lt;h1&gt;
  
  
  Why Box&amp;lt;[T]&amp;gt; Beats Vec for Static Data
&lt;/h1&gt;

&lt;p&gt;A &lt;code&gt;Vec&amp;lt;T&amp;gt;&lt;/code&gt; and a &lt;code&gt;String&lt;/code&gt; carry 24 bytes of stack metadata (on 64-bit systems):&lt;/p&gt;

&lt;p&gt;A heap pointer (8 bytes)&lt;/p&gt;

&lt;p&gt;The current length (8 bytes)&lt;/p&gt;

&lt;p&gt;The allocated capacity (8 bytes)&lt;/p&gt;

&lt;p&gt;Because dynamic vectors must support growing and shrinking, they maintain spare capacity. But if your data is loaded once and never resized, tracking capacity is unnecessary overhead.&lt;/p&gt;

&lt;p&gt;Converting to a boxed slice (&lt;code&gt;Box&amp;lt;[T]&amp;gt;&lt;/code&gt; or &lt;code&gt;Box&amp;lt;str&amp;gt;&lt;/code&gt;) drops the capacity field entirely, shrinking stack metadata down to 16 bytes (pointer + length). Across thousands of instances, this metadata reduction adds up quickly.&lt;/p&gt;

&lt;h1&gt;
  
  
  Practical Implementation
&lt;/h1&gt;

&lt;p&gt;Here is how you can convert dynamic types into boxed slices using &lt;code&gt;.into_boxed_slice()&lt;/code&gt; and &lt;code&gt;.into_boxed_str()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;path&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;NamedImage&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Box&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Box&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;impl&lt;/span&gt; &lt;span class="n"&gt;NamedImage&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;img_path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;img_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;Self&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Load raw image data and shrink allocation from Vec&amp;lt;u8&amp;gt; to Box&amp;lt;[u8]&amp;gt;&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;buf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;image&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;load_png&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;img_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.into_boxed_slice&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="c1"&gt;// Convert owned String to Box&amp;lt;str&amp;gt;, dropping capacity overhead&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;img_name&lt;/span&gt;&lt;span class="nf"&gt;.into_boxed_str&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="k"&gt;Self&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When &lt;code&gt;.into_boxed_slice()&lt;/code&gt; or &lt;code&gt;.into_boxed_str()&lt;/code&gt; is called, Rust reallocates or re-slices the heap allocation to match the exact length of the data, freeing any excess capacity back to the allocator.&lt;/p&gt;

</description>
      <category>performance</category>
      <category>rust</category>
      <category>software</category>
    </item>
    <item>
      <title>Beyond &amp;self and &amp;mut self: Rust’s Underrated Method Receivers</title>
      <dc:creator>Lori-Shu</dc:creator>
      <pubDate>Sun, 27 Sep 2026 16:51:34 +0000</pubDate>
      <link>https://dev.to/lorishu/beyond-self-and-mut-self-rusts-underrated-method-receivers-4jad</link>
      <guid>https://dev.to/lorishu/beyond-self-and-mut-self-rusts-underrated-method-receivers-4jad</guid>
      <description>&lt;p&gt;If you write Rust, you interact with &amp;amp;self and &amp;amp;mut self every single day. They are the backbone of idiomatic Rust, signaling at a glance whether a method reads from or mutates an instance.&lt;/p&gt;

&lt;p&gt;However, Rust’s ownership and type system goes deeper than just standard references. Tucked inside the language specification is arbitrary self types—allowing specialized receivers like self: &amp;amp;Arc and self: &amp;amp;Mutex.&lt;/p&gt;

&lt;p&gt;While rare in everyday application code, these self-types are power tools when architecting concurrent and multi-threaded systems.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Standard Receivers: A Quick Refresh
&lt;/h2&gt;

&lt;p&gt;Before diving into the exotic variants, here is how the standard receivers behave:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&amp;amp;self&lt;/code&gt; (Shared Reference): Borrow the instance immutably. Multiple callers can execute this method simultaneously without mutating state.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&amp;amp;mut self&lt;/code&gt; (Exclusive Reference): Borrow the instance mutably. Guarantees exclusive access to modify the object's internal fields.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;self&lt;/code&gt; (By Value): Consume the instance, taking full ownership and moving it into the method.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Power Receivers: &amp;amp;Arc and &amp;amp;Mutex
&lt;/h2&gt;

&lt;p&gt;In concurrent Rust, state is frequently wrapped inside thread-safe smart pointers or synchronization primitives—most notably &lt;code&gt;Arc&amp;lt;T&amp;gt;&lt;/code&gt; (Atomic Reference Counting) and &lt;code&gt;Mutex&amp;lt;T&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Normally, calling a method on an Arc requires cloning the Arc explicitly before calling a standard method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Traditional approach&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;shared_node&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Arc&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;MyNode&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="nn"&gt;MyNode&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;process_node&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;shared_node&lt;/span&gt;&lt;span class="nf"&gt;.clone&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By explicit receiver typing, you can make the method itself demand the smart-pointer wrapper directly on &lt;code&gt;self&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;impl&lt;/span&gt; &lt;span class="n"&gt;MyNode&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Requires the caller to pass a reference to the Arc wrapping this instance&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;spawn_task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;Arc&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;Self&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;clone&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Arc&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;clone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nn"&gt;tokio&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;spawn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;move&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;clone&lt;/span&gt;&lt;span class="nf"&gt;.do_work&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>programming</category>
      <category>rust</category>
      <category>softwaredevelopment</category>
    </item>
    <item>
      <title>How do I encapsulate a tool type in Rust?</title>
      <dc:creator>Lori-Shu</dc:creator>
      <pubDate>Mon, 31 Aug 2026 14:05:56 +0000</pubDate>
      <link>https://dev.to/lorishu/how-do-i-encapsulate-a-tool-type-in-rust-324g</link>
      <guid>https://dev.to/lorishu/how-do-i-encapsulate-a-tool-type-in-rust-324g</guid>
      <description>&lt;p&gt;I’ve been wrapping a few tool types in a Rust project lately and ran into some friction. Along the way I picked up a few practical lessons on what a clean tool type should look like. Here’s what I landed on:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Decouple unrelated fields:&lt;/strong&gt;&lt;br&gt;
The first and hardest step is deciding which fields the type actually owns. Real-world code is full of types that do more than they should; those extra responsibilities are usually candidates for being pulled out.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prefer immutable interfaces:&lt;/strong&gt;&lt;br&gt;
A good tool type should expose only immutable methods so callers never need a mutable handle. Rust’s type system pushes you in this direction, and following it let me drop a lot of interior-mutability wrappers I had previously needed at runtime.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Distinguish different kinds of output:&lt;/strong&gt;&lt;br&gt;
Some values are fixed once the type is created; others change over time. The former can stay as plain owned types, while the latter are better expressed as shared types. I also try to avoid handing out mutable references. When mutation is required, specific methods should own that responsibility.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Insight:&lt;/strong&gt;&lt;br&gt;
A surprising amount of this is already enforced by the Rust type system. The rules can feel strict, but treating the compiler as a collaborator usually leads to cleaner design and less code.&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>rust</category>
      <category>software</category>
      <category>softwareengineering</category>
    </item>
    <item>
      <title>Introduction to Experiment Indicators</title>
      <dc:creator>Lori-Shu</dc:creator>
      <pubDate>Mon, 24 Aug 2026 15:43:57 +0000</pubDate>
      <link>https://dev.to/lorishu/introduction-to-experiment-indicators-eni</link>
      <guid>https://dev.to/lorishu/introduction-to-experiment-indicators-eni</guid>
      <description>&lt;p&gt;Scientific experiments typically need clear indicators to measure and quantify performance. This is especially true in data-processing contexts, where several key metrics deserve close attention.&lt;br&gt;
We can start with a few fundamentals familiar from high-school math: the average (mean), the mode, and the variance. The average corresponds to the "expectation" discussed in probability courses; it summarizes the overall property of a dataset in a single number. The mode is simply the value that appears most frequently. Variance indicates stability—if a dataset shows large variance, it is considered unstable.&lt;br&gt;
Residual is another useful indicator. It measures the difference between data points that have a time-domain character. Residuals are particularly valuable in signal processing, where data are typically ordered by time. They are calculated by taking the difference between values at separate time points.&lt;br&gt;
From residual we derive RSS (Residual Sum of Squares), also known as SSE (Sum of Squared Errors). RSS is obtained by squaring each residual and then summing the results. RMSE (Root Mean Squared Error) takes this a step further: it divides the RSS by the number of data points and then takes the square root. The result has the same magnitude as the original data and effectively represents the average residual. RMSE is especially powerful when the data contain both positive and negative values, because squaring prevents opposing residuals from canceling each other out.&lt;/p&gt;

</description>
      <category>analytics</category>
      <category>data</category>
      <category>science</category>
    </item>
    <item>
      <title>The Trend of Modern Programming Languages</title>
      <dc:creator>Lori-Shu</dc:creator>
      <pubDate>Sun, 23 Aug 2026 16:06:05 +0000</pubDate>
      <link>https://dev.to/lorishu/the-trend-of-modern-programming-languages-52cb</link>
      <guid>https://dev.to/lorishu/the-trend-of-modern-programming-languages-52cb</guid>
      <description>&lt;p&gt;In 2026, programming languages—the core tools of software engineers—are undergoing a clear and accelerating shift: they are becoming more static.&lt;br&gt;
Two major design paradigms have long shaped language design. On one side are the static-first languages such as C and Rust. These typically feature strong type systems and sophisticated, heavyweight compilers. On the other side are the dynamic-first languages such as Python and JavaScript. They traditionally rely on weaker type systems while leveraging powerful, professionally engineered runtimes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Insight&lt;/strong&gt;:The current trend is unmistakable: languages that once embraced dynamic features are steadily adopting more static characteristics. Many of them have borrowed ideas and mindsets from languages like Rust. Evidence shows that this shift delivers tangible benefits—faster startup times, reduced runtime memory usage, and greater code determinism.&lt;br&gt;
Recognizing this evolution helps us better understand the interplay between static and dynamic elements in our own software. The practical recommendation is straightforward: prefer static approaches whenever possible, and only fall back to dynamic solutions when something is genuinely difficult to make static.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>software</category>
      <category>softwareengineering</category>
    </item>
  </channel>
</rss>
