<?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: Trystan Sarrade</title>
    <description>The latest articles on DEV Community by Trystan Sarrade (@trystan_sarrade).</description>
    <link>https://dev.to/trystan_sarrade</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%2F284024%2F6fe37160-2c2f-42d6-b95d-db4c4b4c8b3b.png</url>
      <title>DEV Community: Trystan Sarrade</title>
      <link>https://dev.to/trystan_sarrade</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/trystan_sarrade"/>
    <language>en</language>
    <item>
      <title>How I took my Rust GUI from 135 MB to 30 MB by ditching the GPU</title>
      <dc:creator>Trystan Sarrade</dc:creator>
      <pubDate>Mon, 08 Jun 2026 10:26:59 +0000</pubDate>
      <link>https://dev.to/trystan_sarrade/how-i-took-my-rust-gui-from-135-mb-to-30-mb-by-ditching-the-gpu-2i7d</link>
      <guid>https://dev.to/trystan_sarrade/how-i-took-my-rust-gui-from-135-mb-to-30-mb-by-ditching-the-gpu-2i7d</guid>
      <description>&lt;p&gt;&lt;a href="https://github.com/Trystan-SA/rproc" rel="noopener noreferrer"&gt;rproc&lt;/a&gt; is a Linux resource and process monitor. Think Windows 11’s Task Manager, but native, fast, and open source. It draws live CPU, memory, disk and network graphs, lists every process with its icon, lets you sort and kill them, and surfaces systemd services and startup apps.&lt;/p&gt;

&lt;p&gt;A process monitor needs to be tiny. You usually open it precisely when the machine is already struggling under heavy load, so the tool itself has no business adding to the problem.&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.amazonaws.com%2Fuploads%2Farticles%2F9awdpgycccrq9fyzeqp3.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.amazonaws.com%2Fuploads%2Farticles%2F9awdpgycccrq9fyzeqp3.png" alt="The Performance view. Live charts, per-core usage, and the per-process attribution panel that shows which processes were behind any point you hover. All drawn on the CPU, no GPU context." width="799" height="516"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The first version, written in Rust with &lt;strong&gt;egui&lt;/strong&gt;, already used about 135 MB, lighter than most comparable tools. The rewrite pushed that down to ~30 MB: &lt;strong&gt;roughly 4.5× less than my own first version&lt;/strong&gt;, and up to &lt;strong&gt;87% less&lt;/strong&gt; than the heaviest mainstream monitor.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mission Center&lt;/strong&gt;  239 MB&lt;br&gt;
&lt;strong&gt;Resources&lt;/strong&gt;   200 MB  16% less&lt;br&gt;
&lt;strong&gt;Gnome System Monitor&lt;/strong&gt;    185 MB  22% less&lt;br&gt;
&lt;strong&gt;rproc, egui (before)&lt;/strong&gt;    ~135 MB 43% less&lt;br&gt;
&lt;strong&gt;rproc, Slint, GPU off (after)&lt;/strong&gt; ~30 MB    87% less&lt;/p&gt;

&lt;p&gt;The rewrite came down to dropping &lt;strong&gt;egui **and using **Slint&lt;/strong&gt; instead. But the interesting part isn’t the number. It’s where those 100 MB were hiding, because almost none of it was code I wrote.&lt;/p&gt;
&lt;h2&gt;
  
  
  Where does a GUI’s memory actually go?
&lt;/h2&gt;

&lt;p&gt;Before optimizing anything, you have to understand what the number even means. The figure a monitor shows for a process is its RSS (Resident Set Size): how much physical RAM the process is using right now. And RSS is not just “the memory your program asked for.” It’s the sum of several things, most of which you never wrote:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Your own data. The structures your code allocates: rproc’s process list, the little history buffers behind each graph, a few thousand short strings. A couple of MB, realistically.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;All the code you link in. Your program, plus every library it depends on, loaded into memory.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Libraries pulled in by those libraries. You add one dependency and it quietly drags in ten more. This is where the surprises hide.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Graphics memory. The moment a program talks to the GPU, the graphics driver maps a pile of its own buffers (and sometimes a slice of video memory) into your process. It all counts as “your” RAM, even though you never asked for a byte of it.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The intuitive assumption (small program, small memory) is simply wrong. What sets your real floor is everything your dependencies drag along with them. rproc’s actual job, reading numbers out of the system, was never the expensive part. Drawing those numbers was.&lt;/p&gt;

&lt;p&gt;So the rewrite was never really about writing tighter Rust. It was about choosing dependencies that don’t open expensive doors behind your back.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why egui was 135 MB: immediate mode and the GPU
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;egui&lt;/strong&gt; is a joy to build with. It’s what’s called an &lt;strong&gt;immediate-mode&lt;/strong&gt; GUI, and the idea is simple: there’s no interface you build once and then update. Instead, on every single frame, you re-run your UI code from top to bottom &lt;code&gt;if button("Kill").clicked()&lt;/code&gt; and the library redraws the whole thing from scratch. It’s the same approach as Dear ImGui in the C++ world, and games love it because it slots straight into a render loop that’s already running 60 times a second.&lt;/p&gt;

&lt;p&gt;But “redraw everything, 60 times a second” only makes sense if something very fast is doing the drawing: a graphics card. So egui, through its eframe wrapper, quietly sets up a full GPU pipeline:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;A connection to the graphics driver (in graphics jargon, an OpenGL or wgpu “context”).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The graphics driver itself, loaded into your program. On NVIDIA’s proprietary driver that’s tens of MB of code and buffers, and once the connection is open you can’t really opt out of it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A texture atlas: egui pre-renders every letter of every font, plus your icons, into one big image on the GPU so the card can stamp them onto the screen. Handy, but it costs memory.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of this is wasteful for what egui is built to do. It’s exactly right for drawing a UI on top of a 3D game at 60 fps. It’s just wildly oversized for a window that updates its numbers once a second and otherwise sits still. I was paying for a real-time game renderer to display text that barely changes.&lt;/p&gt;

&lt;p&gt;That’s the lesson hiding in those 135 MB: the weight wasn’t egui’s own code, it was the GPU machinery egui takes for granted. A process monitor needs none of it. It needs to draw some text, some boxes and a few line charts, every now and then.&lt;/p&gt;
&lt;h3&gt;
  
  
  Retained mode, and the software renderer
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Slint&lt;/strong&gt; flips both of those choices.&lt;/p&gt;

&lt;p&gt;First, it’s &lt;strong&gt;retained-mode&lt;/strong&gt;, the opposite of immediate-mode. You describe your interface once, in .slint files (a small, declarative language, a bit like HTML/CSS), and Slint keeps that description in memory. When a value changes, it redraws only the parts tied to that value. When nothing changes, it does nothing at all. For an app that mostly sits there showing numbers, that matches reality far better than “redraw the whole window, constantly.”&lt;/p&gt;

&lt;p&gt;Second, and this is where the memory actually disappeared, Slint can draw the entire UI without a GPU at all, using what’s called a software renderer: it paints the pixels with the CPU, straight into a plain block of memory, then hands that finished image to the system to put on screen. No driver connection, no graphics driver loaded into your program, no texture atlas. The whole GPU pile from the previous section simply never exists.&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.amazonaws.com%2Fuploads%2Farticles%2Flqywqjgr6s29te5067ez.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.amazonaws.com%2Fuploads%2Farticles%2Flqywqjgr6s29te5067ez.png" alt="Same widgets, two pipelines. egui hands geometry to a GPU stack (an OpenGL context, the graphics driver and a VRAM texture atlas) that stays mapped whether the UI changes or not. Slint's software renderer rasterizes straight to a pixel buffer on the CPU, so none of that is loaded. That deleted stack is most of the 100 MB." width="800" height="448"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This one change, dropping the GPU renderer, is the biggest single win in the project. egui couldn’t offer it: take the GPU away and it has nothing left to draw with. Slint paints the same window on the CPU and never opens that whole stack, which accounts for most of the gap between 135 MB and the new baseline. (The smaller savings, the icon shrinking, the optional GPU monitoring, and so on, come later.)&lt;/p&gt;

&lt;p&gt;And that’s the counter-intuitive headline of the rewrite: for a plain 2D desktop app, not using the GPU is the optimization. We’ve all been trained to think “GPU equals fast equals good.” For a game, sure. For a window full of text and a few charts, the GPU is a tax of tens of MB to make something that was already instant slightly more instant, and you pay for it in memory and in slower startup.&lt;/p&gt;
&lt;h3&gt;
  
  
  The catch: drawing the live graphs
&lt;/h3&gt;

&lt;p&gt;Dropping the GPU isn’t free. A software renderer is deliberately limited (that’s part of how it stays small and portable), and the live graphs were where that bit hardest.&lt;/p&gt;

&lt;p&gt;On a GPU toolkit you’d draw a line chart by building a little text description of the shape at runtime, something like &lt;strong&gt;"M 0 0 L 1 5 L 2 3 …"&lt;/strong&gt; (the same syntax SVG uses), and rebuilding it every time new data arrives. Slint’s software renderer won’t accept a shape assembled on the fly like that, and it won’t let you loop to generate one either: it wants the structure of the shape known up front, when the app is compiled.&lt;/p&gt;

&lt;p&gt;So I turned it around. Each graph is a fixed line of &lt;strong&gt;60 points&lt;/strong&gt;, written out once in the .slint file, where only the position of each point is wired to live data. Rust keeps the last 60 measurements; when a new one arrives the oldest falls off and the points shift along; Slint just moves the existing line segments to their new spots. A little layout glue lets the whole thing stretch to fill whatever space it’s given.&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;// 60 statically-declared points; coordinates bound to the model.&lt;/span&gt;
&lt;span class="c1"&gt;// No runtime-built command string, no `for` inside the Path.&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;viewbox&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="py"&gt;.width&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="n"&gt;px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;viewbox&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="py"&gt;.height&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="n"&gt;px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;MoveTo&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;            &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="py"&gt;.pts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;LineTo&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="py"&gt;.step&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="py"&gt;.pts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;LineTo&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="py"&gt;.step&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="py"&gt;.pts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="c1"&gt;// … 57 more, generated once at build time …&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It’s less elegant than handing over a string, but it’s predictable: always 60 segments, no memory churned on every redraw, and the renderer knows the shape ahead of time. The limitation pushed me toward a cleaner design than I’d have bothered with if the easy path had been available.&lt;/p&gt;

&lt;h3&gt;
  
  
  The bug that only a refresh tick can cause
&lt;/h3&gt;

&lt;p&gt;Here’s a bug that’s a pure consequence of how the app refreshes. rproc reloads its data on a timer, a few times a second. The tempting thing to do is rebuild the whole process list from scratch each time. Do that, and you create a race that’s almost impossible to reproduce on purpose: a mouse click is really two events, a press and then a release. If a refresh happens to land between them and rebuilds the list, the row you pressed no longer exists by the time you release, so the click just vanishes. Every few seconds, a click silently does nothing.&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.amazonaws.com%2Fuploads%2Farticles%2Ftwgoapz5ncnusbcl1a0c.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.amazonaws.com%2Fuploads%2Farticles%2Ftwgoapz5ncnusbcl1a0c.png" alt="The Processes tab. Each row carries a freedesktop icon, decoded and downscaled to ~20 px before caching rather than held at native resolution. Clickable rows live in a persistent model that's mutated in place, never rebuilt, so a click never gets dropped by a refresh." width="799" height="516"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The fix is to stop rebuilding the list wholesale. Instead the list stays put and its rows are updated in place, edited rather than recreated, so whatever is under your cursor survives a refresh that lands mid-click. It’s the kind of bug that never shows up in a screenshot or a quick demo. It shows up as a vague “clicks sometimes don’t work,” and the only way to kill it is to reason about the order things happen in, not to poke at the UI until it looks fine.&lt;/p&gt;

&lt;h3&gt;
  
  
  The other 100 MB: things you load whether you use them or not
&lt;/h3&gt;

&lt;p&gt;Swapping the renderer was the big win, but a real chunk came from simply not loading things unless they’re needed. It’s the same idea every time: the cheapest memory is a library you never load at all.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;GPU monitoring is a toggle, not a given. Reading NVIDIA stats means loading NVIDIA’s own libraries, about 20 MB on their own. The old build loaded them no matter what. Now it’s a single setting: turn GPU monitoring off and those libraries are never loaded at all. No GPU graphs, no 20 MB. That toggle is exactly the gap between rproc’s ~50 MB and ~30 MB.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The optional background service is off by default. rproc can keep recording history even while its window is closed, but that service also pulled in those NVIDIA libraries, another ~25 MB. Off unless you actually want it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;App icons are shrunk before they’re stored. Process icons come from the system theme at full size and used to be cached as-is. Now each one is shrunk to roughly the size it’s actually shown at (~20 px) before being kept. A list of 300 processes no longer holds 300 full-size images.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Handing freed memory back to the OS. By default the memory system keeps memory you’ve freed lying around, in case you need it again soon. Great for raw speed, bad for a monitor that should get smaller when idle. So on every refresh rproc explicitly returns that unused memory to the operating system, and the number it reports reflects what’s really in use.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Trimming dependencies of dependencies. One library rproc uses to draw its SVG icons was, by default, also dragging in an entire text-and-font system it didn’t need (the icons are shapes, not text). Turning that option off removed all of it. And one of Slint’s own dependencies needed a system font library installed, which broke the build server until I added it, exactly the kind of surprise that “libraries pulling in libraries” loves to spring on you.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each of these is small on its own. Stacked together they’re the gap between “a bit smaller” and “4.5× smaller,” and they’re all the same move: don’t set up what the user hasn’t asked for.&lt;/p&gt;

&lt;h3&gt;
  
  
  Doing the rewrite with Claude Code
&lt;/h3&gt;

&lt;p&gt;A UI rewrite is the kind of task that’s 80% mechanical and 20% genuinely tricky, which is exactly the shape AI is good at, if you keep it on the right side of that line.&lt;/p&gt;

&lt;p&gt;The mechanical 80%: translating six tabs of egui drawing code into .slint declarative views plus Rust glue that maps a Snapshot into Slint models. That’s a lot of typing, a lot of “do the same transformation 47 times,” and a lot of looking up Slint’s syntax. The PR is +7,122 / -3,755 across 47 files; I did not want to hand-write all of it, and I didn’t.&lt;/p&gt;

&lt;p&gt;What stayed firmly my job was the architecture and the gnarly 20%: the decision to go software-renderer in the first place, the fixed-60-point graph design, spotting the click-straddles-refresh race, deciding which libraries become opt-out toggles. Those are judgment calls that depend on understanding why the memory was where it was. The model is fast at “make this compile and look right”; it is not the one who should be deciding your memory architecture.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Rust GUI ecosystem, briefly
&lt;/h3&gt;

&lt;p&gt;If you’re choosing a Rust GUI toolkit, the memory story above is really a story about which trade-off each one makes. The short map:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;egui (immediate mode). Easiest to start with, GPU-first, redraws everything. Brilliant for dev tools, debug overlays and game UIs. The GPU machinery is the price, and you can’t opt out.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Slint (retained mode, multiple renderers). Declarative .slint files and, the part that mattered here, a real software renderer so you can run with no GPU at all. Designed with phones, embedded screens and low-memory devices in mind, which is exactly why it had the switch I needed.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;iced (retained mode). A clean, predictable architecture (modeled on the Elm language). Lovely to work with; still draws on the GPU.&lt;br&gt;
gtk-rs (the Rust binding to GTK). Mature, native, blends right into the Linux desktop, but you pull in the whole GTK runtime, which is a large baseline of its own.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Tauri / Dioxus-web (the webview route). Ship a web frontend inside the system’s built-in browser view. Great developer experience, but a webview is essentially a browser engine, and its baseline RAM dwarfs everything above.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There’s no single “best” here. There’s only “best for a thing that has to be lighter than the processes it watches.” For that one constraint, a retained-mode toolkit with an honest software renderer wins, and that’s a much narrower claim than “Slint beats egui.” egui was the right call to get rproc working. It was the wrong call to make it small.&lt;/p&gt;

&lt;h3&gt;
  
  
  Takeaways
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Measure the floor, not just your code. The memory my own code used was never the problem. What your dependencies quietly drag in, and the systems they switch on, set your real minimum.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The GPU is a feature, not a default. For a 2D, mostly-idle desktop app, drawing on the CPU instead can be a 4.5× memory win. “GPU-accelerated” is a cost as much as a capability.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Make heavy things opt-in. A graphics driver, vendor GPU libraries, a background service: anything that costs tens of MB should stay off until the user actually asks for it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;How a toolkit redraws is a memory decision, not just a style one. “Redraw everything every frame” quietly signs you up for a permanent GPU pipeline; “redraw only what changed” doesn’t.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;AI handles the mechanical 80% and leaves the hard 20% to you. The rewrite was fast because Claude Code did the translation; it was correct because the architecture calls stayed human. And “looks done in a screenshot” is not the same as done.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The final figure: ~30 MB with the GPU off, ~50 MB with it on, against 185 to 239 MB for the popular alternatives. rproc is no longer anywhere near the top of its own process list, which, for a process monitor, was the only acceptable outcome.&lt;/p&gt;

</description>
      <category>rust</category>
      <category>architecture</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Live-Reload and Debugging Go Applications Within a Docker Container</title>
      <dc:creator>Trystan Sarrade</dc:creator>
      <pubDate>Sun, 26 Jan 2025 16:22:58 +0000</pubDate>
      <link>https://dev.to/trystan_sarrade/live-reload-and-debugging-go-applications-within-a-docker-container-5d07</link>
      <guid>https://dev.to/trystan_sarrade/live-reload-and-debugging-go-applications-within-a-docker-container-5d07</guid>
      <description>&lt;p&gt;During my journey of learning Golang, I wanted to replicate my preferred development workflow: live-reloading while debugging inside a Docker container. This approach had been a cornerstone of my work with Node.js, and I aimed to achieve the same seamless experience with Go. Surprisingly, I found limited resources online addressing the integration of live-reloading and debugging within a Docker environment.&lt;/p&gt;

&lt;p&gt;To clarify, &lt;em&gt;live-reloading&lt;/em&gt; refers to the automatic termination and relaunch of a process when changes are detected in the source files. This differs from &lt;em&gt;hot-reloading&lt;/em&gt;, where parts of the memory are patched dynamically without restarting the process.&lt;/p&gt;

&lt;p&gt;Debugging, on the other hand, is an essential tool for inspecting the execution flow of a program. While it may seem daunting to beginners, once mastered, debugging proves far more efficient and effective than scattering logs throughout your codebase.&lt;/p&gt;

&lt;p&gt;Finally, working within a Docker container has become a modern and reliable method for developing applications. This approach ensures that your application runs consistently across virtual machines or PaaS platforms, eliminating risks tied to operating system or server configuration discrepancies. Combining Docker with &lt;em&gt;live-reloading&lt;/em&gt; and debugging not only streamlines the development process but also sets a robust foundation for delivering reliable software.&lt;/p&gt;

&lt;p&gt;Here’s a quick guide to help you integrate live-reloading and debugging into your development workflow for Golang within a Docker container.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Quick note: This guide is based on my setup using Windows 11 with WSL (Windows Subsystem for Linux). If you're on Linux, the steps should be identical, and for macOS users, the process is likely very similar.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;If you're using Windows for Go development, I highly recommend leveraging WSL. The Windows file system is significantly slower than Linux (and WSL), which means compiling your Go programs natively on Windows can take much longer. By using a WSL host, you'll notice substantial improvements in both compilation speed and overall development efficiency.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stack used
&lt;/h2&gt;

&lt;p&gt;Here’s the stack we’ll be using to set up live-reloading and debugging for Golang in a Dockerized development environment:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Docker&lt;/strong&gt; is the cornerstone of our setup, providing a reproducible and isolated environment for development. By leveraging Docker, we can ensure that our application behaves consistently between local development and production environments, minimizing any surprises caused by differences in OS or server configurations.&lt;/p&gt;

&lt;p&gt;For live-reloading. We'll use &lt;strong&gt;&lt;a href="https://github.com/air-verse/air" rel="noopener noreferrer"&gt;Air&lt;/a&gt;&lt;/strong&gt;, a lightweight Golang tool that monitors changes to your source code and automatically recompiles and restarts your server. While tools like Nodemon or Inotify-tools can achieve similar functionality, Air is specifically designed for Go, making it an ideal choice for this setup.&lt;/p&gt;

&lt;p&gt;Since Golang doesn’t have a built-in debugger, we’ll use &lt;strong&gt;&lt;a href="https://github.com/go-delve/delve" rel="noopener noreferrer"&gt;Delve&lt;/a&gt;&lt;/strong&gt; a robust debugging tool for Go. Delve integrates seamlessly with popular IDEs like Visual Studio Code, allowing us to set breakpoints, step through code, and inspect variables directly.&lt;/p&gt;

&lt;p&gt;Our objective is to create a development workflow where the server automatically reloads when changes are saved to the codebase. Debugging remains fully functional, enabling us to troubleshoot and inspect the application in real time (e.g., through Visual Studio Code’s built-in debugger).&lt;/p&gt;

&lt;h2&gt;
  
  
  Golang Server using Fiber
&lt;/h2&gt;

&lt;p&gt;Here's a minimal example of a Go server using the Fiber framework, which you can use to test your live-reload and debugging configuration. Create a file named main.go and add the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="s"&gt;"github.com/gofiber/fiber/v2"&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;fiber&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;fiber&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;str&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="s"&gt;"Hello, World!"&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SendString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;":3000"&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;This is as simple as it gets—a lightweight server that responds with "Hello, World!" when accessed at &lt;a href="http://localhost:3000" rel="noopener noreferrer"&gt;http://localhost:3000&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Write &lt;code&gt;go run .&lt;/code&gt; in your terminal to test that out.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setup Docker
&lt;/h2&gt;

&lt;p&gt;Here’s a basic docker-compose.yml file to start your api container. This example is minimal and can be extended to include other services like a database, Redis, or Nginx as needed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;api&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./api&lt;/span&gt;
      &lt;span class="na"&gt;dockerfile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Dockerfile&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3000:3000'&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;2345:2345'&lt;/span&gt;
    &lt;span class="na"&gt;stop_grace_period&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;0.1s&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./api:/app&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;internal&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;build.context&lt;/code&gt;: used to tell docker-compose to check the &lt;code&gt;dockerfile&lt;/code&gt; present on the folder "api". I prefer to put my sources in another folder if I need to develop multiples other go services on this repository later on.&lt;br&gt;
&lt;code&gt;ports&lt;/code&gt;: expose the webserver running on port 3000. And we will expose port 2345 for the debugger server (we will define it later)&lt;br&gt;
&lt;code&gt;volumes&lt;/code&gt;: Bind directly to the container the content of the &lt;code&gt;/api&lt;/code&gt; folder (where all my go code is located on the Host) and put it to &lt;code&gt;/app&lt;/code&gt; on the docker volume. This way all changes to the project will visible directly to the docker container.&lt;/p&gt;
&lt;h2&gt;
  
  
  Dockerfile of the '/api' folder
&lt;/h2&gt;

&lt;p&gt;We saw previously that Docker-Compose is using the &lt;em&gt;dockerfile&lt;/em&gt; inside the &lt;code&gt;/api&lt;/code&gt; folder. Here is the content you should have for your dockerfile.&lt;/p&gt;

&lt;p&gt;Don't overthink the &lt;code&gt;delve&lt;/code&gt; and &lt;code&gt;air&lt;/code&gt; part. We will see how they work just after.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# Use the official Golang image as the base image&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; golang:1.23.2-alpine3.20&lt;/span&gt;

&lt;span class="c"&gt;# Set the Current Working Directory inside the container&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;

&lt;span class="c"&gt;# Install delve, used to debug the application (golang doesn't have a built-in debugger)&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;go &lt;span class="nb"&gt;install &lt;/span&gt;github.com/go-delve/delve/cmd/dlv@latest

&lt;span class="c"&gt;# Install air, used to hot reload the application&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;go &lt;span class="nb"&gt;install &lt;/span&gt;github.com/air-verse/air@latest

&lt;span class="c"&gt;# Copy go mod and sum files and install dependencies&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; go.mod go.sum ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;go mod download

&lt;span class="c"&gt;# Switch to root user (if necessary, though it should work, do it only for development purposes)&lt;/span&gt;
&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; root&lt;/span&gt;

&lt;span class="c"&gt;# Expose the ports of the container. Docker-Compose do it automatically but it's good to have it here&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 2345&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 3000&lt;/span&gt;

&lt;span class="c"&gt;# Run air for live-reload. The -c flag is used to specify the configuration file&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["air", "-c", "air.toml"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Air and Delve setup
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Air&lt;/em&gt; will watch the source files of your project and reload your application when changes are saved. But since we also want to use &lt;em&gt;Delve&lt;/em&gt; as debugger, it is him (&lt;em&gt;delve&lt;/em&gt;) that will launch our server instead of &lt;em&gt;Air&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;For that, we will create a &lt;code&gt;air.toml&lt;/code&gt; file in the same place your DockerFile is placed (inside &lt;code&gt;/api&lt;/code&gt; folder).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="py"&gt;root&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"."&lt;/span&gt;
&lt;span class="py"&gt;tmp_dir&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"tmp"&lt;/span&gt;

&lt;span class="nn"&gt;[build]&lt;/span&gt;
&lt;span class="py"&gt;full_bin&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"dlv debug --build-flags=&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;-gcflags='all=-N -l'&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt; --listen 0.0.0.0:2345 --headless --continue --accept-multiclient --output=dist/debug"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This configuration tells air to run a specific command when the binary is recreated (when your server reload).&lt;/p&gt;

&lt;p&gt;&lt;code&gt;dlv debug&lt;/code&gt;: It is the delve command that build and start your project with the debugger server attached to it.&lt;br&gt;
&lt;code&gt;--build-flags&lt;/code&gt;: Compile your golang program without optimization (make it faster to compile) but also without inline function (slower, but make your function accessible by the debugger).&lt;br&gt;
&lt;code&gt;--listen&lt;/code&gt;: Make your debugger server listen on the base host IP (&lt;em&gt;0.0.0.0&lt;/em&gt;, useful for your docker container) and on the port 2345.&lt;br&gt;
&lt;code&gt;--headless --continue --accept-multiclient&lt;/code&gt;: start directly your golang application with the debugger without waiting a client to connect to it first.&lt;br&gt;
&lt;code&gt;--output&lt;/code&gt;: Put the debug server compiled files into &lt;code&gt;dist/debug&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;It is all we need to make live-reloading and debugging work together !&lt;/p&gt;
&lt;h2&gt;
  
  
  Setup VSCode debugger
&lt;/h2&gt;

&lt;p&gt;Now, last step, we need to tell VSCode how to access the debugging server.&lt;/p&gt;

&lt;p&gt;create a new debug configuration by creating a new file under &lt;code&gt;.vscode/launch.json&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0.2.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"configurations"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Connect to container"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"go"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"debugAdapter"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"dlv-dap"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"request"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"attach"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"mode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"remote"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;The&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;container&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;port&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;host&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;must&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;match&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"port"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2345&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"host"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"localhost"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"trace"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"verbose"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"substitutePath"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="nl"&gt;"from"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/home/user/project/api"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"to"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/app"&lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;debugAdapter&lt;/code&gt;: We need to use that since the default debug adapter is not compatible with delve.&lt;br&gt;
&lt;code&gt;port&lt;/code&gt;: The port of the debugger server&lt;br&gt;
&lt;code&gt;host&lt;/code&gt;: The IP to connect to&lt;br&gt;
&lt;code&gt;substitutePath&lt;/code&gt;: This is the most useful setting, it will tell VSCode to match your host project path with the docker container volume project path. If you miss-configure this part, your breakpoints won't work since VSCode will be unable to match the line you set your breakpoint to with the docker container running application. &lt;/p&gt;

&lt;p&gt;And that's it ! Everything you need to have Live-Reload, Debugging and Docker working together. &lt;/p&gt;

</description>
      <category>go</category>
      <category>docker</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Colors for console.log with Deno</title>
      <dc:creator>Trystan Sarrade</dc:creator>
      <pubDate>Tue, 26 May 2020 11:33:42 +0000</pubDate>
      <link>https://dev.to/trystan_sarrade/colors-for-console-log-with-deno-no4</link>
      <guid>https://dev.to/trystan_sarrade/colors-for-console-log-with-deno-no4</guid>
      <description>&lt;p&gt;Chalk is a Nodejs package used to write console.log with colors. It's extremely useful for debugging your application by having easy to see big red highlights for your critical and error debug.&lt;/p&gt;

&lt;p&gt;If you are using Deno and wanted something similar, you are in the right place. &lt;/p&gt;

&lt;h2&gt;
  
  
  The different types of console debug
&lt;/h2&gt;

&lt;p&gt;JavaScript console has different levels of debug type. You have the common &lt;em&gt;console.log&lt;/em&gt; command. But you also have more of them. In Deno, those debugs have nothing different. &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.amazonaws.com%2Fuploads%2Farticles%2Ft6ipmdlnvf0c5v7mh2gm.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.amazonaws.com%2Fuploads%2Farticles%2Ft6ipmdlnvf0c5v7mh2gm.png" alt="Deno colors" width="453" height="226"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So let's add a little bit of colors to them !&lt;/p&gt;

&lt;h2&gt;
  
  
  Deno Color system
&lt;/h2&gt;

&lt;p&gt;Deno colors are similar to the chalk ones and are managers by one Deno standard library. Simply import the link &lt;em&gt;&lt;a href="https://deno.land/std/fmt/colors.ts" rel="noopener noreferrer"&gt;https://deno.land/std/fmt/colors.ts&lt;/a&gt;&lt;/em&gt; to your Deno code like this : &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.amazonaws.com%2Fuploads%2Farticles%2Fron02nxc82cam5t5260n.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.amazonaws.com%2Fuploads%2Farticles%2Fron02nxc82cam5t5260n.png" alt="Deno colors" width="667" height="259"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Deno color system works by adding the function, "Colors.[the name of the color]" to your console.log parameter. Now we have pretty colorful debug. But we can do more !&lt;/p&gt;

&lt;h2&gt;
  
  
  Override default console functions
&lt;/h2&gt;

&lt;p&gt;Having to write the function Colors.red every time you need a red text is not easy to use. But you can override the default console functions with your own :&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.amazonaws.com%2Fuploads%2Farticles%2Ftqh2ec040jsw9vqim1nu.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.amazonaws.com%2Fuploads%2Farticles%2Ftqh2ec040jsw9vqim1nu.png" alt="Deno colors" width="706" height="651"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, every time you will use console.error in your application, a red prefix will follow your log everywhere inside your application. You should create another .ts script named something like "debug" or "console" and import it from your main application file at the beginning.&lt;/p&gt;

</description>
      <category>deno</category>
      <category>beginners</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
