<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:googleplay="http://www.google.com/schemas/play-podcasts/1.0"><channel><title><![CDATA[Sailing Fearless: Tech Bytes]]></title><description><![CDATA[Where I answer random questions about programming, command line usage, and IT trends]]></description><link>https://svfearless.substack.com/s/tech-bytes</link><image><url>https://substackcdn.com/image/fetch/$s_!8gwY!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F129c4c28-acb2-48ca-bf62-10c7058d8379_1280x1280.png</url><title>Sailing Fearless: Tech Bytes</title><link>https://svfearless.substack.com/s/tech-bytes</link></image><generator>Substack</generator><lastBuildDate>Sat, 02 May 2026 20:33:05 GMT</lastBuildDate><atom:link href="https://svfearless.substack.com/feed" rel="self" type="application/rss+xml"/><copyright><![CDATA[SV Fearless LLC]]></copyright><language><![CDATA[en]]></language><webMaster><![CDATA[svfearless@substack.com]]></webMaster><itunes:owner><itunes:email><![CDATA[svfearless@substack.com]]></itunes:email><itunes:name><![CDATA[SV Fearless]]></itunes:name></itunes:owner><itunes:author><![CDATA[SV Fearless]]></itunes:author><googleplay:owner><![CDATA[svfearless@substack.com]]></googleplay:owner><googleplay:email><![CDATA[svfearless@substack.com]]></googleplay:email><googleplay:author><![CDATA[SV Fearless]]></googleplay:author><itunes:block><![CDATA[Yes]]></itunes:block><item><title><![CDATA[How Do You Anchor?]]></title><description><![CDATA[More than you ever wanted to know about anchoring any kind of boat]]></description><link>https://svfearless.substack.com/p/how-do-you-anchor</link><guid isPermaLink="false">https://svfearless.substack.com/p/how-do-you-anchor</guid><dc:creator><![CDATA[SV Fearless]]></dc:creator><pubDate>Sun, 09 Feb 2025 14:12:57 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!r5bW!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd913f416-8271-4e25-81df-811f1f6d260e_1169x697.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>The Art of Anchoring</h2><p>This post is a creation of me wanting to know more about precisely how anchors work. There are a lot of rules of thumb on how much rode you should lay out but where did they come from? How do you know when to adjust it? What size anchor should I choose and why?</p><p>All these questions and more, I will answer based on what little I&#8217;ve been able to figure out. Let&#8217;s start with basics first.</p><h2>What is an anchor, anchor rode, and scope?</h2><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!r5bW!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd913f416-8271-4e25-81df-811f1f6d260e_1169x697.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!r5bW!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd913f416-8271-4e25-81df-811f1f6d260e_1169x697.jpeg 424w, https://substackcdn.com/image/fetch/$s_!r5bW!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd913f416-8271-4e25-81df-811f1f6d260e_1169x697.jpeg 848w, https://substackcdn.com/image/fetch/$s_!r5bW!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd913f416-8271-4e25-81df-811f1f6d260e_1169x697.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!r5bW!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd913f416-8271-4e25-81df-811f1f6d260e_1169x697.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!r5bW!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd913f416-8271-4e25-81df-811f1f6d260e_1169x697.jpeg" width="1169" height="697" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d913f416-8271-4e25-81df-811f1f6d260e_1169x697.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:697,&quot;width&quot;:1169,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Parts of an Anchor | Knowledge Centre&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Parts of an Anchor | Knowledge Centre" title="Parts of an Anchor | Knowledge Centre" srcset="https://substackcdn.com/image/fetch/$s_!r5bW!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd913f416-8271-4e25-81df-811f1f6d260e_1169x697.jpeg 424w, https://substackcdn.com/image/fetch/$s_!r5bW!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd913f416-8271-4e25-81df-811f1f6d260e_1169x697.jpeg 848w, https://substackcdn.com/image/fetch/$s_!r5bW!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd913f416-8271-4e25-81df-811f1f6d260e_1169x697.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!r5bW!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd913f416-8271-4e25-81df-811f1f6d260e_1169x697.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>An anchor is at its heart is something designed to secure a boat to the seabed so it doesn&#8217;t drift when pushed by wind, current, or tide. While it could be anything, like a big rock, in reality, its made of metal and has a shape that make it easy to secure itself to the bottom. They come in a wide variety of shapes but fundamentally, the composition of the seabed determines the most advantageous shape of anchor to use, most of which we won&#8217;t get into here.</p><div class="image-gallery-embed" data-attrs="{&quot;gallery&quot;:{&quot;images&quot;:[{&quot;type&quot;:&quot;image/jpeg&quot;,&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d41cd0c7-a2a8-49f6-ab40-a0922a0dea0d_500x329.jpeg&quot;},{&quot;type&quot;:&quot;image/jpeg&quot;,&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/4ec54a1e-ed53-4faf-87e1-43e41367e8a7_2000x2000.jpeg&quot;}],&quot;caption&quot;:&quot;In reality its hard to get a completely rope rode, they almost always have a length of chain attached&quot;,&quot;alt&quot;:&quot;&quot;,&quot;staticGalleryImage&quot;:{&quot;type&quot;:&quot;image/png&quot;,&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/7acfa4f6-c939-4c98-b5ab-fea81c77a6f2_1456x720.png&quot;}},&quot;isEditorNode&quot;:true}"></div><p>Rode is whatever connects your boat to the anchor. This is typically rope, chain, or a combination of rope and chain. Both have different properties which gives them distinct pros and cons, some of which we&#8217;ll get into here.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!zTub!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd6f1de8a-cbba-45ab-87b7-b0acfc4e6c28_1024x584.bin" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!zTub!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd6f1de8a-cbba-45ab-87b7-b0acfc4e6c28_1024x584.bin 424w, https://substackcdn.com/image/fetch/$s_!zTub!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd6f1de8a-cbba-45ab-87b7-b0acfc4e6c28_1024x584.bin 848w, https://substackcdn.com/image/fetch/$s_!zTub!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd6f1de8a-cbba-45ab-87b7-b0acfc4e6c28_1024x584.bin 1272w, https://substackcdn.com/image/fetch/$s_!zTub!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd6f1de8a-cbba-45ab-87b7-b0acfc4e6c28_1024x584.bin 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!zTub!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd6f1de8a-cbba-45ab-87b7-b0acfc4e6c28_1024x584.bin" width="1024" height="584" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d6f1de8a-cbba-45ab-87b7-b0acfc4e6c28_1024x584.bin&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:584,&quot;width&quot;:1024,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Sailing Made Easy Diagram&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Sailing Made Easy Diagram" title="Sailing Made Easy Diagram" srcset="https://substackcdn.com/image/fetch/$s_!zTub!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd6f1de8a-cbba-45ab-87b7-b0acfc4e6c28_1024x584.bin 424w, https://substackcdn.com/image/fetch/$s_!zTub!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd6f1de8a-cbba-45ab-87b7-b0acfc4e6c28_1024x584.bin 848w, https://substackcdn.com/image/fetch/$s_!zTub!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd6f1de8a-cbba-45ab-87b7-b0acfc4e6c28_1024x584.bin 1272w, https://substackcdn.com/image/fetch/$s_!zTub!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd6f1de8a-cbba-45ab-87b7-b0acfc4e6c28_1024x584.bin 1456w" sizes="100vw"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">ASA Scope recommendation</figcaption></figure></div><p>Scope is the ratio of rode length to rode height, as measured from where it connects to the anchor (the anchor shank), to where it physically attaches to the boat. We&#8217;ll get into this in more detail in a moment.</p><h2>Why does a boat drift?</h2><p>This may seem simple but lets cover it anyway. A boat with no external forces acting on it doesn&#8217;t drift. It sits in place, motionless. In the real world, the water and wind are constantly acting on the boat which makes it drift.</p><p>Examples of these forces are waves, swells, currents, tide, and wind. All of these things will push a boat around to one degree or another. In calm situations, the only forces significant enough to worry about are currents, tide, and wind. If it&#8217;s not calm, waves and swells can impart significant force as well. If you&#8217;re interested in the difference between waves and swells here&#8217;s a simple, short <a href="https://www.youtube.com/watch?v=Apu_GY_1Hmo&amp;ab_channel=NavigatewithAnkit">explanation</a>. If you don&#8217;t care, that&#8217;s fine, it&#8217;s not really relevant to the rest of the discussion. If you have trouble with his accent just mute the audio and read the notes.</p><p>The goal of an anchor, then, is to resist all of these forces and prevent the boat from moving.</p><h2>The importance of scope</h2><p>When anchoring, scope is one of the primary factors in determining how well your anchor will hold and thus keep your boat from drifting. The American Sailing Association (ASA), one of the largest organizations which promotes safe sailing standards and practices for non-professional sailing enthusiasts, recommends a 7:1 scope when anchoring overnight. As an example, if you&#8217;re anchoring in 7 feet of water and the rode is secured to your bow which is 3 feet above the water, you would lay out 70 feet of rode, (7+3) * 7 = 70.</p><p>The ASA also says that if you&#8217;re anchoring for a short time in calm weather, and aware of your surroundings, you can use a 5:1 scope. Say you&#8217;re anchoring to eat lunch for an hour or two and want to enjoy the calm water.</p><p>Including the deck height in the calculation is required. Lets say you anchored in 6 feet of water and put out 42 feet of rode believing that you were using a 7:1 scope. If your anchor attaches at your bow, which happens to be 4 feet higher than the waterline of the boat, your actual scope is 3.5:1.</p><p>The primary reason that scope is so important to anchoring is that even when an anchor is properly set, with the anchor flukes (the &#8220;arms&#8221; of the anchor that dig into the seabed) well secured, if you lift straight up from above the anchor, it&#8217;s easy to move it. This is exactly why the windlass doesn&#8217;t struggle lifting the anchor off the bottom unless something is wrong.</p><p>Paying out enough scope ensures that when the boat is pushed by the wind the force delivered to the anchor only pulls on the anchor horizontally, resulting in the anchor either not moving at all or (hopefully) digging the flukes deeper into the seabed (or lakebed or riverbed) to stop the movement.</p><p>This outlines one of the key pros of an all chain anchor rode. It&#8217;s heavy and that weight keeps the rode on the bottom contributing to only pulling against the anchor horizontally. More on this in a minute.</p><h2>Why are 7:1 and 5:1 recommended scopes?</h2><p>The answer to that question is what led me down the rabbit hole. What&#8217;s so special about 7:1? Will this hold in all kinds of weather? Is there a better scope in general? In specific conditions? Does the type or size of the anchor alter the scope?</p><p>The short answer is, I honestly couldn&#8217;t find a reason that 7:1 is the recommended scope other than it seems to work for most people most of the time. I couldn&#8217;t find any reason that 7:1 was picked vs 8:1 or 6.5:1.</p><p>This led me to wonder, how do I calculate the forces acting on the boat? If I know that, I at least know the minimum amount of force the anchor should hold against. Once I know that, maybe I can understand how different anchor sizes and types, and scopes, affect holding power.</p><h2>How anchors work in slightly more detail</h2><p>For the rest of this conversation I&#8217;m assuming the use of an all chain rode.</p><p>The weigh of an all chain anchor rode keeps the shank of the anchor on the bottom and ensures that the pull against the anchor is horizontal, but this is only true if there is enough weight to overcome the forces of wind, current, waves, <em>etc.</em>, that are pulling it from above. This is why you need to pay attention scope, and thus the amount of rode you&#8217;ve played out.</p><p>It turns out that there&#8217;s a term for the way the rode hangs between your boat and the anchor shank. It&#8217;s called a <a href="https://en.wikipedia.org/wiki/Catenary">catenary</a>. This is a term well understood and used in many branches of physics, engineering, and architecture, and it&#8217;s used in the design of everything from bridges to offshore oil and gas rigs.</p><p>Mathematically, the catenary curve is the graph of the hyperbolic cosine function and they were first written about by Galileo in 1638 who recognized that the shape of a catenary was different from a parabola and worthy of study in its own right.</p><p>A catenary is a hyperbolic function, meaning that the curve is much steeper at the top (at the boat of the boat in this case) than the bottom. To calculate the shape of a catenary between a boat and the shank of an anchor, where the initial slope of the chain at the shank is flat and completely horizontal you would use this formula:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;y = \\frac{wind_f}{C_w} * (cosh(\\frac{C_w*x}{C_w} -1))&quot;,&quot;id&quot;:&quot;RXPWHPZUYB&quot;}" data-component-name="LatexBlockToDOM"></div><p>Where wind subscript f is the force of the wind, and C subscript w is the weight of the chain per meter.</p><p>We only care about the endpoints of the catenary, however, not the entire curve, so we can simplify this to:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;C_L = \\frac{\\sqrt{(8Hh/w) + 4h^2}}{2}&quot;,&quot;id&quot;:&quot;GCLYHBNCXX&quot;}" data-component-name="LatexBlockToDOM"></div><p>where C subscript L is the length of the chain, H is the horizontal force acting on the boat (wind, wave, <em>etc.</em>), h is the depth of the water (plus height above the waterline of the rode attachment point), and w is the weight of the chain per meter.</p><h2>What does all that mean?</h2><p>Well, using high school algebra (if you can still remember that far back) you can notice a few relevant things. As H (forces acting on the boat) increases, length of chain will increase. If the weight of the chain increases, the length will decrease. If you increase h (height of the catenary or in our case depth + height above water), the length will again increase.</p><p>Now, all I need to know are the values of all of those variables and I can calculate the precise length of rode to pay out for those conditions.</p><p>So, let&#8217;s see, the force of the wind acting against my boat can be found with:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;F_{wind} = \\frac{1}{2} * \\rho * A * C_d *V^2&quot;,&quot;id&quot;:&quot;XGHLDGIIUU&quot;}" data-component-name="LatexBlockToDOM"></div><p>Where F is force in newtons, rho is air density, A is the projected area of the boat in square meters, C subscript d is the drag coefficient, and V is the windspeed in meters per second.</p><p>Calculating the force generated by waves became&#8230;well, really, really complicated. Instead I thought I&#8217;d just go with treating it as an additional load as a percentage of the force generated by the wind, which kind of works since for waves local wind is generating them. So maybe an additional 30-50% of the wind force for moderate and 100% of wind force for rough conditions. That&#8217;s probably too high but better safe than sorry.</p><p>The drag coefficient is a term in fluid dynamics that represents the resistance of a body moving through a fluid. It&#8217;s used in the drag equation to calculate the drag force on an object. The drag coefficient of the boat is stupidly difficult to calculate. Since I don&#8217;t have access to a computational fluid dynamics simulation, I just make some assumptions. Mast and rigging I assume to be 1.0 since they are not designed with aerodynamics in mind. For the rest I&#8217;ll assume a value between a sphere (0.5) and a streamlined body (0.2), let&#8217;s say around 0.35.</p><p>So the good news is, we actually <em>can</em> come up with an approximation of the forces acting against the boat which we can then plug into the catenary endpoint formula! I&#8217;m nerdy enough that I actually went through that exercise and did the math. It was pretty cool but ultimately, as you may have guessed, that technique is not practical for use in a daily situation. Just to see how the math works, I&#8217;ve used 12 ft as my cross-sectional width (2 hulls of 6 feet), and 20 feet as my cross-sectional height from waterline to top of salon roof. is my estimate of the cross-sectional area above the waterline for the hull and the 92.8 is my estimated cross-sectional area of the mast and rigging.<br>I&#8217;ve converted everything to meters instead of feet and 30 knots into meters per second just to make the math easier.<br>Let&#8217;s see how that math works with our assumptions:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;F_{wind} = \\frac{1}{2} * 1.2 * (.35*22.33 + 1.0*28.29)*15.43^2&quot;,&quot;id&quot;:&quot;HEEFTYHESY&quot;}" data-component-name="LatexBlockToDOM"></div><p>This comes out to about 5157.5 Newtons of force, or about 526 kilograms force (kgf).</p><p>There were a lot of assumptions that went into the calculation but I feel like that&#8217;s actually a reasonable number.  I expect that the fact that it&#8217;s a catamaran with 2 hulls and significantly higher coach roof make those assumptions more of a best guess than an reliable estimates.</p><h2>Generalities to the rescue!</h2><p>Fortunately, by pre-calculating the force applied on the boat using 30 knots of wind (34 knots is considered gale force so maybe I should have used that instead for safety sake), a formula that approximates the complicated math exists that works for 30-50 feet sailboats to maintain the catenary shape of the chain. This is also what we use on SV FearLess when we anchor:</p><div class="latex-rendered" data-attrs="{&quot;persistentExpression&quot;:&quot;C_L = 50_{ft} + (2*d){ft}&quot;,&quot;id&quot;:&quot;GUFGJIYSTP&quot;}" data-component-name="LatexBlockToDOM"></div><p>Where C subscript L is the total chain length and d is depth plus height above the waterline. So if we anchor in 8 feet of water, we would lay out <em>at a minimum</em> 50 feet + (2 * 12) = 74 feet of chain. Don&#8217;t forget to add the height that the chain comes out of the water and attaches to your bow. For us, the rode comes out of the water about 4 feet, which is why we use 12 instead of just the 8 feet for the water depth.</p><p>If you compare this number to the typically recommended 7:1 scope (8*7 = 56), you&#8217;ll see that we lay out 22 more feet of chain. By contrast, if we anchor in 20 feet of water we use much less chain than the 7:1 scope. 24*7 = 168 feet of chain, 50+(2*24) = 98 feet of chain. Given that we have 200 feet of chain rode onboard, this gives us a <em>lot</em> of anchoring options that we wouldn&#8217;t have if we just used the 7:1 scope rule.</p><h2>Details</h2><p>As always, there are caveats. </p><p>First, this is the minimum amount of chain to lay out. If you have the swing room to accommodate more chain, do it. In this case more is almost always better, at least as long as you don&#8217;t swing into a rock, dock, or another boat.</p><p>Second, be smart; if you know weather is incoming and you&#8217;re going to stay on anchor, lay out more chain to help compensate against the higher winds and waves. Don&#8217;t just roll the dice and hope for the best.</p><p>Third, always take tides into account. If you anchor in 8 feet of water at low tide and the tidal range is 3 feet, at high tide your chain length hasn&#8217;t changed but the height of the catenary has. If you used the catenary formula of 50+(8*2) you laid out 66 feet of chain. If high tide raises you to 9.5 feet, you need to have 50 +(11*2) or 72 feet of chain. Always lay out enough rode to be safe at high tide if possible.</p><p><strong>Resources</strong>:<br>The resources that I primarily relied on for this post were Chat-GPT, the excellent work done by the people at <a href="https://trimaran-san.de/en/anchor-chain-calculator/">Trimaran San</a>, the <a href="https://halekai.uk/posts/2020-01-15-mooring.html">Cutter Hale Kai</a>, and the <a href="https://sailing-blog.nauticed.org/anchoringhow-much-chain-and-rope/">NauticEd sailing blog</a>. Also, I found an interesting article on the merits of anchoring from the stern in heavy weather from the man who tested the <a href="https://en.wikipedia.org/wiki/Drogue">series drogue</a>.</p><p>All of these references do a much better job of explaining things than I do, particularly the Cutter Hale Kai when discussing moorings, so give them a try.</p>]]></content:encoded></item><item><title><![CDATA[Understanding Vim Registers]]></title><description><![CDATA[I love learning new things about old tools]]></description><link>https://svfearless.substack.com/p/understanding-vim-registers</link><guid isPermaLink="false">https://svfearless.substack.com/p/understanding-vim-registers</guid><dc:creator><![CDATA[SV Fearless]]></dc:creator><pubDate>Tue, 26 Mar 2024 12:07:51 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!MLN3!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc16c56da-af58-42e3-afbd-b04f7d03c4ed_1024x1024.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!MLN3!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc16c56da-af58-42e3-afbd-b04f7d03c4ed_1024x1024.webp" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!MLN3!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc16c56da-af58-42e3-afbd-b04f7d03c4ed_1024x1024.webp 424w, https://substackcdn.com/image/fetch/$s_!MLN3!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc16c56da-af58-42e3-afbd-b04f7d03c4ed_1024x1024.webp 848w, https://substackcdn.com/image/fetch/$s_!MLN3!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc16c56da-af58-42e3-afbd-b04f7d03c4ed_1024x1024.webp 1272w, https://substackcdn.com/image/fetch/$s_!MLN3!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc16c56da-af58-42e3-afbd-b04f7d03c4ed_1024x1024.webp 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!MLN3!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc16c56da-af58-42e3-afbd-b04f7d03c4ed_1024x1024.webp" width="1024" height="1024" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c16c56da-af58-42e3-afbd-b04f7d03c4ed_1024x1024.webp&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1024,&quot;width&quot;:1024,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:303656,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/webp&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!MLN3!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc16c56da-af58-42e3-afbd-b04f7d03c4ed_1024x1024.webp 424w, https://substackcdn.com/image/fetch/$s_!MLN3!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc16c56da-af58-42e3-afbd-b04f7d03c4ed_1024x1024.webp 848w, https://substackcdn.com/image/fetch/$s_!MLN3!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc16c56da-af58-42e3-afbd-b04f7d03c4ed_1024x1024.webp 1272w, https://substackcdn.com/image/fetch/$s_!MLN3!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc16c56da-af58-42e3-afbd-b04f7d03c4ed_1024x1024.webp 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2>Background</h2><p>I love using command-line tools. I have no idea why I find it so much fun but there you have it. As a result, I like to use terminal-based editors. I settled on Vim a very long time ago. If you want to know why, ask me and I&#8217;ll write a short post about my reasoning. I switched to using NeoVim a while back and have never looked back, however, I&#8217;m going to be using the term vim to refer to both of them interchangeably.</p><p>This is because if you&#8217;re handed an out-of-the-box, vanilla instance of them both, very few people will be able to tell the difference between them. Which is good because while I&#8217;m virtually guaranteed that any flavor of *nix I might use will have vim installed, the same can&#8217;t be said of nvim.</p><p>One of the things I like about using vim is that despite using it for decades, I&#8217;m always learning new things about it, and I&#8217;m somehow always surprised that I never figured it out sooner.</p><h2>Registers</h2><p>If you&#8217;ve used vim for any amount of time, you&#8217;ll probably know that it has registers. A register is a place where vim stores text for later use, <em>e.g.</em>, a copy-and-paste buffer. Vim has a <em>lot</em> of registers with a lot of different purposes.</p><p>There&#8217;s the unnamed register, the numbered registers, the named registers, and the read-only registers. Then there&#8217;s the expression register, the small delete register (yep, that&#8217;s a thing), and the selection and drop registers. But at the end of the day, they all store text.</p><p>I am not going to go into exhaustive detail about all of them. If you want me to do that, please ask, I&#8217;ll be happy to write about it. Today I just want to cover the ones that seem to get the most use.</p><h2>Good Old Copy and Paste</h2><p>Before we get going, I want to present the syntax of accessing a register. This is done by typing <code>&#8221;</code> followed by the register name or symbol while in normal mode. You paste the contents of the register using the <code>P</code> or <code>p</code> command. The uppercase command pastes the text in front of the cursor location, the lowercase command pastes the text behind the cursor location. To copy text into a register using the yank command you type, imaginatively, <code>y</code>. </p><p>The <code>Y</code> command will yank text from the cursor position to the end of the line in Vim versions 8.2 and beyond. Versions older than 8.2 yank the entire line. In all NeoVim versions, the behavior of the <code>Y</code> command is to yank from the cursor to the end of the line.</p><h2>The Unnamed Register</h2><p>As an example, the most used register that I can think of is the unnamed register. The unnamed register is the register where the text from the last delete or yank command gets stored.<br>Although it&#8217;s called &#8220;unnamed&#8221;, you can access it by typing the double-quote character. So to insert text from the unnamed register you type, `&#8221;&#8221;p`. The reason it&#8217;s called the &#8220;unnamed&#8221; register is that if you don&#8217;t explicitly provide a register, that&#8217;s the one that gets used. You can accomplish the same task simply by typing `p` without providing the register name. The same thing goes for the yank commands.</p><h2>The Named Registers</h2><p>Moving to the next most used registers, we come to the 26 named registers. These registers are, &#8220;named&#8221;, as the letters a-z, hence 26 of them. You might think that you could double that by using A-Z as well, but that isn&#8217;t what the creators went with. Instead when you yank text into a named register using the lowercase letter, you overwrite any existing text. When you use the uppercase letter, you append the yanked text to any existing register contents.</p><p>Now, I have to confess, I&#8217;m lazy, and while the idea of the named registers sounded great on paper when I first discovered them I went about using them in the most idiotic way possible. By trying to remember where I stored all of the different text snippets I had yanked. This was obviously a poor plan, and as a result, I would rarely use named registers.</p><p>In fact, the only time I can recall using named registers in the past was when I needed to swap two words or pieces of text which required that I use a named register because I would overwrite the first word if I used the unnamed register. This later developed into an even worse habit of only yanking and pasting one word at a time because it was just easier than using registers.</p><p>I made a fundamental mistake. I allowed my lack of familiarity with my tool dictate how I used it. This is invariably a bad idea.</p><h2>The Numbered Registers</h2><p>Eventually, I simply forgot about the named registers. I mean, I knew they were there, and I even knew how to use the commands, but I never developed the habit, so I just forgot about them. It was like I&#8217;d blocked them out even to the point of not using plugins for code snippet management because I just knew they used registers and registers were a pain to use.</p><p>Then came the moment that I saw someone use the <code>registers</code> command, or <code>reg</code> if you&#8217;re lazy like me. In this case, they were using it to show the numbered registers. The numbered registers are numbered 0-9 and are accessed using &#8220;0, &#8220;1, <em>etc., </em>where register 0 contains the text from the last yank command, and registers 1-9 contain a rolling list of the last 9 change or delete actions with 1 being the most recent and 9 being the oldest. The contents of 9 are replaced with the contents of 8 when a new change or delete action is taken and the contents of 9 are rolled off the end of the list and lost. <a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-1" href="#footnote-1" target="_self">1</a></p><p>Now, while they were looking for the numbered register contents, the <code>reg</code> command will show the contents of <em>all</em> registers that have text in them, including the named registers.</p><p>How had I been using vim for decades and not known about this? I felt like suddenly everything I&#8217;d ever been missing about using registers made sense and I was filled with a sense of endless possibilities.</p><h2>Always Learning</h2><p>And I won&#8217;t lie, I also felt like a right moron. How had I never seen, heard of, or researched this obvious answer?? This also highlights a rule I try to live by and frequently fall short of; re-evaluate what you think you know on a regular basis. You never know which of your assumptions or habits may be hindering you.</p><p>Now we come to the moment I reveal the true depths of my awe-inspiring stupidity in all its majestic glory. I use a convenience plugin called <a href="https://github.com/folke/which-key.nvim">which-key</a>. It provides a pop-up showing the possible keybindings that can complete the command you started to type. Guess what that includes? Correct! If I type the <code>&#8220;</code> command, that popup appears with the register list showing you exactly what text is in which register. I had been so averse to using registers that I&#8217;d never once typed in the command to access a register during the entire time I&#8217;d been using the plugin. That was at least a year.</p><p>After the angelic hosts stopped singing and I began playing with registers and the <code>reg</code> command, I discovered by accident that the which-key had already taken care of the inconvenience of managing registers this entire time if only I&#8217;d bothered to look. I was so averse to registers that even reading the homepage of the plugin when I first looked at it I didn&#8217;t even recognize that it solved my long-standing complaint against registers.</p><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-1" href="#footnote-anchor-1" class="footnote-number" contenteditable="false" target="_self">1</a><div class="footnote-content"><p>There is a caveat to the numbered list. While the rolling list behavior of the numbered list described above is correct, it only applies to change and delete actions taken on text that is at least a single full line of text. The reasoning being that larger text changes and deletions are more valuable for later retrieval. There is no way of changing this behavior, however, you can use scripting or key remapping to automate populating the named registers.</p></div></div>]]></content:encoded></item><item><title><![CDATA[Yet Another Dotfiles Management Article]]></title><description><![CDATA[I finally get prodded into overcoming my lazy factor]]></description><link>https://svfearless.substack.com/p/yet-another-dotfiles-management-article</link><guid isPermaLink="false">https://svfearless.substack.com/p/yet-another-dotfiles-management-article</guid><dc:creator><![CDATA[SV Fearless]]></dc:creator><pubDate>Sun, 18 Feb 2024 22:45:22 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!EhK_!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd951497a-2d41-43c5-ba15-67fbfba95595_1152x768.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!EhK_!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd951497a-2d41-43c5-ba15-67fbfba95595_1152x768.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!EhK_!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd951497a-2d41-43c5-ba15-67fbfba95595_1152x768.jpeg 424w, https://substackcdn.com/image/fetch/$s_!EhK_!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd951497a-2d41-43c5-ba15-67fbfba95595_1152x768.jpeg 848w, https://substackcdn.com/image/fetch/$s_!EhK_!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd951497a-2d41-43c5-ba15-67fbfba95595_1152x768.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!EhK_!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd951497a-2d41-43c5-ba15-67fbfba95595_1152x768.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!EhK_!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd951497a-2d41-43c5-ba15-67fbfba95595_1152x768.jpeg" width="1152" height="768" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d951497a-2d41-43c5-ba15-67fbfba95595_1152x768.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:768,&quot;width&quot;:1152,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:443299,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!EhK_!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd951497a-2d41-43c5-ba15-67fbfba95595_1152x768.jpeg 424w, https://substackcdn.com/image/fetch/$s_!EhK_!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd951497a-2d41-43c5-ba15-67fbfba95595_1152x768.jpeg 848w, https://substackcdn.com/image/fetch/$s_!EhK_!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd951497a-2d41-43c5-ba15-67fbfba95595_1152x768.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!EhK_!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd951497a-2d41-43c5-ba15-67fbfba95595_1152x768.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><p>After having many people poke at me and my latest annoyance at having to rebuild my preferred environment in a container image that isn&#8217;t under my control, I decided to bite the bullet and get my dotfiles under useful management.</p><p>Up until now, I&#8217;ve put off real management of my dotfiles by just creating a git repo and storing my dotfiles there. It was a half-baked solution at best and required a lot of tweaking by hand when I used it to try and recreate my CLI environment.</p><p>As the years rolled on my personalizations grew. A lot. I now have a lot of configurations to track and I&#8217;ve now found myself in the position of having to rebuild my podman containers on a semi-regular basis.</p><p>This last rebuild has motivated me to try and find a simple, portable, and functional solution to exporting and importing my dotfiles to a fresh environment. There may be better solutions out there but this one seems to work pretty well for me, at least so far. It begins, as many things in software seem to do, with GNU.</p><h2><strong>The Solution I Chose</strong></h2><p>I decided to go with GNU <a href="https://www.gnu.org/software/stow/">stow</a>. It&#8217;s really a symlink manager and its original purpose was to manage software versions. Since that point of origin, it has been widely adopted as a way to manage dotfiles. There are other solutions, such as <a href="https://github.com/anishathalye/dotbot">dotbot</a>, but the instructions say you need to install it as a git submodule, or if you have Python set up you can use PyPi. This seemed like I might head down another rabbit hole if I went that route and I didn&#8217;t want anything complex so I went with stow. It&#8217;s been around for over two decades so it&#8217;s a known quantity, there&#8217;s a lot of documentation for it (not that it needs a lot) and it&#8217;s available pretty much anywhere you can use a package manager.</p><h2><strong>Pre-requisites</strong></h2><p>You really only need to know a couple of commands to use Stow for managing dotfiles and the pre-requisites are pretty straightforward. You need to be familiar enough with your package manager to install packages, you need to be familiar enough with git to use it, and lastly, you need to install git and stow.</p><h2><strong>Implementation</strong></h2><p>I&#8217;m on a Mac so for me I just needed to execute a</p><p><code>brew install stow</code> (I already have git installed) and I&#8217;m off to the races. If you&#8217;re using homebrew and you&#8217;re on a Linux machine, I understand and please use your package manager of choice, if you&#8217;re on a Mac and you&#8217;re not using <a href="https://brew.sh/">brew</a>, shame on you, you&#8217;re doing it wrong.</p><h2><strong>Segue</strong></h2><p>Before getting started, since a lot of the rebuilding I&#8217;m doing is being done on my work-issued laptop, and both my personal GitHub and my work GitHub use SSH keys to manage <a href="https://cyral.com/glossary/authentication-authn-versus-authorization-authz/">AuthN/AuthZ</a> on GitHub, I need to juggle multiple SSH keys. Work also requires that I sign commits with GPG, which is a step beyond my personal paranoia and fear that someone really wants to tamper with my code. This means I need to take extra steps to make it convenient to commit code when I make changes to my dotfiles.</p><p>First I needed to make sure that the identities were set up properly. I did this in the SSH config file. This is typically located at <code>~/.ssh/config</code> and mine looks something like this:</p><pre><code>Host github-work
 HostName github.com
 ForwardAgent yes
 IdentityFile ~/.ssh/work_github
 IdentitiesOnly yes
Host github-personal
 HostName github.com
 User git
 ForwardAgent yes
 IdentityFile ~/.ssh/personal-gmail-github
 IdentitiesOnly yes</code></pre><p>In this example I&#8217;m mapping my 2 SSH keys to separate hosts, named github-work and github-personal, which can be thought of as aliases/profiles for our purposes. The only tweak worth pointing out here is that I deliberately use the IdentitiesOnly configuration. By default the ssh client will still send the default key. This can be problematic when using multiple accounts and by including the IdentitiesOnly directive it only sends the key explicitly given in IdentityFile.</p><h2><strong>End Seque</strong></h2><p>Now that we have SSH configured and stow installed, we get to the meat of the article. Spoiler alert, the seques are probably more interesting than the stow commands.</p><h2><strong>Directory Structure</strong></h2><p>When using stow you need to create a directory to hold the managed files and the directory structure of the files under management must mirror the directory structure of the files as they would appear in your home directory, <em>i.e.</em>, if your <code>.tmux.conf</code> file is in your home directory and the directory containing your managed files is called <code>dotfiles</code> then the managed tmux file would be located at <code>~/dotfiles/.tmux.conf</code> and similarly if your tmux plugin files are located at <code>~/.tmux/plugins/my-plugin</code> then the corresponding managed files would be located at <code>~/dotfiles/.tmux/plugins/my-plugin</code>.</p><h2><strong>Another Seque</strong></h2><p>In my case I already had a repo for my dotfiles called imaginatively, <code>dotfiles</code>, on github that I decided to clean out and reuse. So I just needed to clone the repo to set up version control. Given I have multiple accounts to juggle my clone command looked like this:</p><p><code>git clone github-personal:Clay-Ratliff/dotfiles.git</code></p><p>Note that I used the profile name created in the Host directive to use the correct ssh key. Also, given that I did not need to sign commits on my personal repo I also executed <code>git config commit.gpgsign false</code> which disabled gpg signing for that local repo, not globally.</p><h2><strong>End Seque. Again.</strong></h2><p>I&#8217;m going to go with the safest way to get started migrating your dotfiles to a managed system rather than the quickest. I&#8217;m a fan of do as a say not as I do and I don&#8217;t want to encourage other people to adopt my bad habits.</p><h2><strong>Back. Up. Your. Dotfiles.</strong></h2><p>Before you put your files under management make a backup. Only then would I move them to the stow directory. My preferred method is the simple expedience of the following:</p><pre><code>cp ~/.tmux.conf ~/.tmux.conf.bak
mv ~/.tmux.conf ~/dotfiles/</code></pre><p>So simple even I couldn&#8217;t screw it up.</p><h2><strong>Magic Happens Here</strong></h2><p>From here you can put your tmux config under control. The magic happens like this:</p><p><code>stow .</code></p><p>Are you not impressed? OK, I admit, it&#8217;s more than a little anti-climactic. If you look in your home directory you can see that stow has created a symlink of your tmux config file in your home directory. That&#8217;s it. You have met the magic, and the magic is boring.</p><h2><strong>Commit your changes. Now, not later.</strong></h2><p>At this point, I immediately commit that change and then remove the backup file because I know that I can reproduce a working copy of my tmux conf from the repo and move it back by hand if something weird happens.</p><p>You&#8217;ll want this comfort because things have been so boring and easy up till now you&#8217;re going to want to start running with scissors which is never a good idea now matter how well you know the path. All the other files will be a rinse and repeat of this.</p><h2><strong>Yet Another Segue</strong></h2><p>This <em>can</em> be a rinse and repeat action. However, if you&#8217;re like me (or a lot of other people) you are almost certainly using plugins, for your shell, for tmux, for your editor, <em>etc</em>., and if so, they&#8217;re probably under source control. It&#8217;s a common practice for plugin managers to simply pull the plugin down from github and place it in a plugins directory as a git repo and update the repo and plugins get updated.</p><p>The delima here is that you&#8217;ve placed your dotfiles directory in a git repo. If you try to add it as a normal file you&#8217;ll get something like this:</p><pre><code>[0] &lt;git:(main 350f7bd&#10033;&#9992;) &gt; ga .
warning: adding embedded git repository: .tmux/plugins/tmux-continuum
hint: You've added another git repository inside your current repository.
hint: Clones of the outer repository will not contain the contents of
hint: the embedded repository and will not know how to obtain it.
hint: If you meant to add a submodule, use:
hint:
hint:   git submodule add &lt;url&gt; .tmux/plugins/tmux-continuum
hint:
hint: If you added this path by mistake, you can remove it from the
hint: index with:
hint:
hint:   git rm --cached .tmux/plugins/tmux-continuum
hint:
hint: See "git help submodule" for more information.</code></pre><p>In this case, you should take the common sense approach and follow the advice. You can take a peek inside the repo config to get the url, in the above example that looks something like this (ignore the formatting stuff, I use a lot of customizations for my CLI):</p><pre><code>[2] &lt;git:(main 350f7bd+) &gt; cat ~/.tmux/plugins/tmux-continuum/.git/config
&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9516;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;
       &#9474; File: /Users/clay.ratliff/.tmux/plugins/tmux-continuum/.git/config
&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9532;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;
   1   &#9474; [core]
   2   &#9474;     repositoryformatversion = 0
   3   &#9474;     filemode = true
   4   &#9474;     bare = false
   5   &#9474;     logallrefupdates = true
   6   &#9474;     ignorecase = true
   7   &#9474;     precomposeunicode = true
   8   &#9474; [submodule]
   9   &#9474;     active = .
  10   &#9474; [remote "origin"]
  11   &#9474;     url = @github.com/tmux-plugins/tmux-continuum"&gt;https://git::@github.com/tmux-plugins/tmux-continuum
  12   &#9474;     fetch = +refs/heads/master:refs/remotes/origin/master
  13   &#9474; [branch "master"]
  14   &#9474;     remote = origin
  15   &#9474;     merge = refs/heads/master</code></pre><p>Then you can substitute the URL and follow the advice like so</p><pre><code>g submodule add @github.com/tmux-plugins/tmux-continuum"&gt;https://git::@github.com/tmux-plugins/tmux-continuum .tmux/plugins/tmux-continuum</code></pre><p>Now you&#8217;ve preserved all of the repo information and whatever plugin manager you use for whatever app should behave as expected.</p><h2><strong>An alternative approach</strong></h2><p>If you want to speed things up a bit, you can also use <code>stow --adopt .</code> as an alternative.</p><p>Normally, if a file exists and you attempt to import it into stow, then stow will throw an error because it will detect that the target file/directory already exists. In order words if I we had copied the tmux config file from the home directory into the dotfiles directory and run <code>stow .</code>, we would have gotten an error because the orginal .tmux.conf file would still have existed.</p><pre><code>&#9492;&#9472;[0] &lt;git:(main 16932a5&#9992;) &gt; stow .
WARNING! stowing . would cause conflicts:
  * existing target is neither a link nor a directory: .fzf.zsh
All operations aborted.</code></pre><p>In the example above I simply copied the .fzf.zsh file into the dotfiles directory and attempted to add it.</p><p>You can change this default behavior by using the adopt flag. Adopt will shorten the process by one step by removing the need to delete the target file/directory before stowing. The trade-off for skipping that step is that the files that you&#8217;re stowing can be altered. This is not normally a big deal, unless the package is already owned by stow.</p><p>Side note, files and directories managed by stow are called &#8216;packages&#8217; and any packages that stow knows about are considered &#8216;owned&#8217; by stow.</p><p>Imagine that you copied the tmux file in the above example into the stow directory and placed it under version control but didn&#8217;t stow it. Then you made some changes to the original config file later and realized that you hadn&#8217;t stowed it. If you now run <code>stow --adopt .tmux.conf</code> then stow will replace the .tmux.conf file in the home directory with a symlink to the .tmux.conf package that it now owns, however, if you run <code>git status</code> against that directory you&#8217;ll see that the file has changed. The original file in stow was overwritten by the modifed version from the home directory. If it&#8217;s under version control you can decide to keep or discard those changes. If it&#8217;s not, you&#8217;ll never know the file had changed.</p><p>It&#8217;s a weird edge case, especially considering that since it&#8217;s a symlink if I do something like <code>vim ~/.tmux.conf</code>to edit the file then I&#8217;m editing the file the symlink is pointing to (because that&#8217;s how symlinks work), and I honestly haven&#8217;t experienced any time where that&#8217;s an issue but I suppose it could happen?</p><h2><strong>Conclusion</strong></h2><p>This turned out to be a bit more of a confusing ramble than I was anticipating so if you found this more confusing than educational, please let me know by asking for clarification in the comments.<br>Also if you have another topic you&#8217;re interested in please let me know and I&#8217;ll see if it&#8217;s something I can answer!</p><p>Also if you have any interest in following along on the weird adventures of couple of newbie sailors living on a sailboat, you can subscribe to my newsletter at <a href="https://svfearless.substack.com/">Sailing FearLess</a>.</p><p></p>]]></content:encoded></item><item><title><![CDATA[A Rust Async Primer-Pt 3]]></title><description><![CDATA[In this last article, we&#8217;ll demonstrate how to include a separate runtime crate and use it to run concurrent tasks, as well as give a brief comparison of the current options.]]></description><link>https://svfearless.substack.com/p/a-rust-async-primer-pt-3</link><guid isPermaLink="false">https://svfearless.substack.com/p/a-rust-async-primer-pt-3</guid><dc:creator><![CDATA[SV Fearless]]></dc:creator><pubDate>Wed, 29 Nov 2023 06:00:33 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!WeE4!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc08d8b17-f275-4927-a6a3-80b9ab623756_1400x933.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!WeE4!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc08d8b17-f275-4927-a6a3-80b9ab623756_1400x933.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!WeE4!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc08d8b17-f275-4927-a6a3-80b9ab623756_1400x933.jpeg 424w, https://substackcdn.com/image/fetch/$s_!WeE4!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc08d8b17-f275-4927-a6a3-80b9ab623756_1400x933.jpeg 848w, https://substackcdn.com/image/fetch/$s_!WeE4!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc08d8b17-f275-4927-a6a3-80b9ab623756_1400x933.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!WeE4!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc08d8b17-f275-4927-a6a3-80b9ab623756_1400x933.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!WeE4!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc08d8b17-f275-4927-a6a3-80b9ab623756_1400x933.jpeg" width="1400" height="933" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c08d8b17-f275-4927-a6a3-80b9ab623756_1400x933.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:933,&quot;width&quot;:1400,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!WeE4!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc08d8b17-f275-4927-a6a3-80b9ab623756_1400x933.jpeg 424w, https://substackcdn.com/image/fetch/$s_!WeE4!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc08d8b17-f275-4927-a6a3-80b9ab623756_1400x933.jpeg 848w, https://substackcdn.com/image/fetch/$s_!WeE4!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc08d8b17-f275-4927-a6a3-80b9ab623756_1400x933.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!WeE4!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc08d8b17-f275-4927-a6a3-80b9ab623756_1400x933.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Photo by <a href="https://unsplash.com/@markusspiske?utm_source=medium&amp;utm_medium=referral">Markus Spiske</a> on <a href="https://unsplash.com/?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure></div><p>When we last left off we were using the primitive executor that is available with the futures crate, sometimes called futures-rs. The futures crate provides the common abstraction layer for async behavior in the Rust language. It is intended to be the basis for creating complex runtimes. As discussed in the previous articles, this is by design. Unless you intend to create a custom runtime, executors, etc., from scratch, you probably won&#8217;t be using futures directly.</p><p>I&#8217;ve chosen to use the async-std crate for this example simply because it&#8217;s easily read by anyone already familiar with the Rust std library. I&#8217;ll also be using timers to demonstrate that we&#8217;re actually running in a concurrent fashion as expected.</p><p>First, we&#8217;ll set up our cargo.toml file as follows.</p><pre><code>[package]
name = "rust-async"
version = "0.1.0"
edition = "2021" # See more keys and their definitions at <a href="https://doc.rust-lang.org/cargo/reference/manifest.html">https://doc.rust-lang.org/cargo/reference/manifest.html</a>[dependencies]
async-std = "1.12.0"
futures = "0.3"
rand = "0.8.5"</code></pre><p>I simply reused the original cargo.toml file and added the async-std and the rand crates as dependencies. We add the rand crate so that we can generate random sleep times for our function.</p><p>Next, we&#8217;ll alter the <code>main.rs</code> file to look like this:</p><pre><code>use std::time::{Instant, Duration};
use async_std::task;
use rand::Rng;

fn main() {
    let start_time = Instant::now();
    task::block_on(async_main());
    print!("Completed in {} ms", start_time.elapsed().as_millis());
}

async fn async_main() {
    let long_running_stuff = make_coffee_and_bacon();
    let other_stuff = make_eggs();
    futures::join!(long_running_stuff, other_stuff);
}

async fn make_coffee_and_bacon() {
    futures::join!(make_coffee(), make_bacon());
}

async fn make_coffee() {
    burning_time("Coffee".to_string()).await;
}

async fn make_eggs() {
    burning_time("Eggs".to_string()).await;
}

async fn make_bacon() {
    burning_time("Bacon".to_string()).await;
}

async fn burning_time(breakfast_item: String) {
    let mut rng = rand::thread_rng();
    println!("{} started!", breakfast_item);
    let sleep_duration =  Duration::from_millis(rng.gen_range(100..1500));    task::sleep(sleep_duration).await;
    println!("{} finished after {:?} ms", breakfast_item, sleep_duration.as_millis());
}</code></pre><p>This looks very similar to the final version of the code that we had at the end of the previous article after all the modifications we made. The set of changes that we made start with the <code>use</code> statements. We import <code>Instant</code> and <code>Duration</code> from the Rust std library. We use those for timing the execution of the code so that we can prove the functions are running concurrently. Instead of importing the <code>futures</code> crate, we import the <code>async-std</code> crate. We also import <code>rng::Rng</code> so that we can generate some random timings.</p><p>The only change to <code>main</code> other than creating a timer and printing the total execution time out is that instead of using the <code>block_on()</code> executor from the futures crate, we instead use the <code>task::block_on()</code> interface from the async-std crate. It behaves similarly to the one we saw in the <code>futures</code> crate but the executor is more sophisticated because while the task blocks the current thread, it also runs all of the async code concurrently. If one async function can&#8217;t complete, <em>e.g.</em>, it&#8217;s asleep, it will yield control to a task that can make progress. As an aside, if it seems weird that we use a blocking interface to run concurrent tasks, the reason is that we don&#8217;t want the program to exit prior to all of the tasks being driven to some form of completion.</p><p>If you compile this program and run it, you will see results similar to the ones shown here:</p><pre><code>&#9492;&#9472;[0] &lt;git:(master b7644d4&#10033;) &gt; cargo run
    Finished dev [unoptimized + debuginfo] target(s) in 0.03s
     Running `target/debug/rust-async`
Coffee started!
Bacon started!
Eggs started!
Bacon finished after 211 ms
Eggs finished after 430 ms
Coffee finished after 1318 ms
Completed in 1319 ms%
&#9484;&#9472;[clayratliff@pop-os] - [~/PersonalGithub/rust-async] - [2022-07-01 05:59:36]
&#9492;&#9472;[0] &lt;git:(master b7644d4&#10033;) &gt; cargo run
    Finished dev [unoptimized + debuginfo] target(s) in 0.03s
     Running `target/debug/rust-async`
Coffee started!
Bacon started!
Eggs started!
Coffee finished after 255 ms
Eggs finished after 1429 ms
Bacon finished after 1499 ms
Completed in 1500 ms%</code></pre><p>We can see from the results that while the breakfast items are always started in the same order, they are clearly being run concurrently because the order in which they are finished varies based on their random sleep times. We can also see that the total runtime of the code is always the length of time taken by the longest sleep plus one or two milliseconds.</p><p>Lastly, let&#8217;s take things one step further and create something that looks more like a real-world application. We&#8217;ll run multiple concurrent tasks to grab some data from the web and return a <code>Result&lt;T, E&gt;</code>, an extremely common return value for I/O.</p><p>First, let's add some needed crates to our cargo.toml file:</p><pre><code>[package]
name = "rust-async"
version = "0.1.0"
edition = "2021"# See more keys and their definitions at <a href="https://doc.rust-lang.org/cargo/reference/manifest.html">https://doc.rust-lang.org/cargo/reference/manifest.html</a>[dependencies]
async-std = "1.12.0"
futures = "0.3"
serde = "1.0.138"
surf = "2.3.2"</code></pre><p>We&#8217;re already familiar with the <code>async-std</code> and <code>futures</code> crates from the previous article. The data we&#8217;ll be pulling for our example will be stock ticker data in JSON format. <a href="https://crates.io/crates/serde">Serde</a> is a framework for serializing and deserializing data structures in Rust. More details can be found in the link but we&#8217;ll be using it to deserialize the JSON data. We&#8217;ll be pulling our ticker data from <a href="https://www.alphavantage.co/">Alpha Vantage</a>. Mostly because the signup is quick and easy. It also doesn&#8217;t cost anything but does give me live data to work with. A sample of the JSON data looks like this:</p><pre><code>{
    "Global Quote": {
        "01. symbol": "IBM",
        "02. open": "141.0000",
        "03. high": "141.6700",
        "04. low": "139.2600",
        "05. price": "141.1200",
        "06. volume": "4012106",
        "07. latest trading day": "2022-07-01",
        "08. previous close": "141.1900",
        "09. change": "-0.0700",
        "10. change percent": "-0.0496%"
    }
}</code></pre><p><a href="https://github.com/http-rs/surf">Surf</a> is an HTTP client framework that works well with async-std, and has a convenient integration with serde for handling JSON documents.</p><p>Now let&#8217;s take a look at our simple stock ticker example in <code>main.rs</code></p><pre><code>use async_std::task;
use futures::join;
use serde::Deserialize;
use std::time::Instant;
use surf::Result;

#[derive(Deserialize)]
struct GlobalQuote {
    #[serde(rename = "01. symbol")]
    symbol: String,
    #[serde(rename = "02. open")]
    open: String,
    #[serde(rename = "03. high")]
    high: String,
    #[serde(rename = "04. low")]
    low: String,
    #[serde(rename = "05. price")]
    price: String,
}

#[derive(Deserialize)]
struct TickerData {
    #[serde(rename = "Global Quote")]
    full_ticker: GlobalQuote,
}

fn main() {
    let start_time = Instant::now();
    task::block_on(async_main());
    println!("Completed in {} ms", start_time.elapsed().as_millis());
}

async fn async_main() {
    let api_key = "API_KEY";
    let ibm_symbol = format!(
        "<a href="https://www.alphavantage.co/query?function=GLOBAL_QUOTE&amp;symbol=IBM&amp;apikey=%7B">https://www.alphavantage.co/query?function=GLOBAL_QUOTE&amp;symbol=IBM&amp;apikey={</a>}",
        api_key
    );
    let tesla_symbol = format!(
        "<a href="https://www.alphavantage.co/query?function=GLOBAL_QUOTE&amp;symbol=TSLA&amp;apikey=%7B">https://www.alphavantage.co/query?function=GLOBAL_QUOTE&amp;symbol=TSLA&amp;apikey={</a>}",
        api_key
    );
    let apple_symbol = format!(
        "<a href="https://www.alphavantage.co/query?function=GLOBAL_QUOTE&amp;symbol=AAPL&amp;apikey=%7B">https://www.alphavantage.co/query?function=GLOBAL_QUOTE&amp;symbol=AAPL&amp;apikey={</a>}",
        api_key
    );
    let first_symbol = get_ticker(ibm_symbol);
    let second_symbol = get_ticker(tesla_symbol);
    let third_symbol = get_ticker(apple_symbol);
    let (result1, result2, result3) = join!(first_symbol, second_symbol, third_symbol);
    
    println!("IBM ticker price: {}", result1.unwrap().price);
    println!("Tesla ticker price: {}", result2.unwrap().price);
    println!("Apple ticker price: {}", result3.unwrap().price);
}

async fn get_ticker(url: String) -&gt; Result&lt;GlobalQuote&gt; {
    let start_time = Instant::now();
    let TickerData { full_ticker } = surf::get(&amp;url).recv_json().await?;
    println!("Async completed in {} ms", start_time.elapsed().as_millis());
    Ok(full_ticker)
}</code></pre><p>The structures <code>GlobalQuote</code> and <code>TickerData</code> will hold the deserialized data. The <code>#[derive(Deserialize)]</code> annotations handle implementing the Deserialize trait for serde. The <code>#[serde(rename = "")]</code> does what you probably expect it to do; it maps the JSON field name inside the quotes, to the field name in your struct.</p><p>The <code>main</code> function hasn&#8217;t changed at all. The first 4 lines in <code>async_main</code> just set up the URI for the stock data API. We then create a <code>Future</code> for our three stock symbols we want to get information on, creatively named <code>first_symbol, second_symbol, and third_symbol</code>. We then pass those <code>Future</code>'s to the <code>join!</code> macro, which we&#8217;ve also seen in the previous article. And finally, we print out the resulting stock prices.</p><p>The real meat, if you can call it that, lies in the trivial <code>get_ticker()</code> function. The <code>Result&lt;&gt;</code> that's returned is from the surf framework and is a wrapper for the standard library <code>Result</code>. We&#8217;re returning <code>GlobalQuote</code> because we want to return the actual stock data from the request to the calling function. The <code>let TickerData { full_ticker }</code> syntax indicates that the JSON should be mapped to the specified structure. It&#8217;s not super useful in our case since the values are all strings but if the returned data had been typed as something other than strings, <em>e.g.</em>, if the JSON document price were an actual number, we could have defined the field <code>price</code> as a <code>f64</code> and serde would have converted it to the appropriate type.</p><p>If you create an account and substitute your generated API key for the placeholder in the code, this should run out of the box and return a result similar to what you see below.</p><pre><code>Async completed in 226 ms
Async completed in 232 ms
Async completed in 278 ms
IBM ticker price: 141.1200
Tesla ticker price: 681.7900
Apple ticker price: 138.9300
Completed in 281 ms</code></pre><p>Again, you can see by the times that the requests are running concurrently and the total time for completion is within a few milliseconds of the longest-running task.</p><p>I had not done any significant asynchronous programming in other languages before attempting to understand it in Rust and I struggled to understand how it worked. My biggest struggle was a misconception that there was a clear standard on how async/await worked in Rust built on top of the futures-rs crate and that all of the runtimes were simply a more sophisticated version of futures. This turns out to be not quite the case.</p><p>There are three primary ecosystems for asynchronous Rust. These eco-systems are <a href="https://crates.io/crates/tokio">Tokio</a>, <a href="https://crates.io/crates/async-std">async-std</a>, and <a href="https://crates.io/crates/smol">smol</a>. I&#8217;ll take a brief look at each of them, and point out a couple of interesting things. The links provided throughout this article will give you much more detailed information.</p><p>Tokio is the oldest, released in 2016, and is the most widely used runtime at the time of writing. It uses custom I/O traits built on top of <a href="https://crates.io/crates/mio">mio</a>. This means it needs a compatibility layer to integrate with futures. It&#8217;s very configurable with many utilities but comes as one large crate although you can use feature flags to make the crate lighter.</p><p>Async-std was released in 2019 with the goal of being a full runtime async version of the Rust standard library and is built on top of futures. It also uses the <a href="https://crates.io/crates/async-executor">async-executor</a> crate, the executor provided by smol.</p><p>Smol is the newest, created in early 2020. It&#8217;s also built on top of futures and was created by Stjepan Glavina, co-creator of async-std, and also uses the futures crate. It&#8217;s intended to be as small as possible and as such the functionality is split into many different crates. It also provides a crate for compatibility with tokio.</p><p>I wanted to list the references that I used in the research for this article and I&#8217;m grateful to the authors. Some of the materials in them are outdated but this is a fast-moving target and they got me close enough.</p><ul><li><p><a href="https://www.youtube.com/watch?v=L7X0vpAU-sU">RustFest Barcelona talk</a></p></li><li><p><a href="https://www.philipdaniels.com/blog/2019/async-std-demo1/">Phil&#8217;s Blog</a></p></li><li><p><a href="https://runrust.miraheze.org/wiki/Async_crate_comparison">Rust community wiki</a></p></li></ul><p><strong>Conclusion</strong></p><p>I hope that this helps you understand Rust async better, rather than making things more confusing. I appreciate your time in making it this far. If you find that I got something wrong, or have something to add please let me know in the comments. </p><p>If you liked this article or the series, please like, share, and restack it.</p><p>Subscribe to get more tech tidbits when I publish them!</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://svfearless.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Sailing Fearless is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[A Rust Async Primer-Pt 2]]></title><description><![CDATA[This is part 2 in a 3 part series on understanding the basics of asynchronous programming in Rust. Part 1 focused on the concepts around asynchronous programming, and specifically, how Rust implements]]></description><link>https://svfearless.substack.com/p/a-rust-async-primer-pt-2</link><guid isPermaLink="false">https://svfearless.substack.com/p/a-rust-async-primer-pt-2</guid><dc:creator><![CDATA[SV Fearless]]></dc:creator><pubDate>Mon, 27 Nov 2023 06:00:31 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!l69B!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F59671390-2cce-43f5-bd8e-5bd78389961d_1400x933.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!l69B!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F59671390-2cce-43f5-bd8e-5bd78389961d_1400x933.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!l69B!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F59671390-2cce-43f5-bd8e-5bd78389961d_1400x933.jpeg 424w, https://substackcdn.com/image/fetch/$s_!l69B!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F59671390-2cce-43f5-bd8e-5bd78389961d_1400x933.jpeg 848w, https://substackcdn.com/image/fetch/$s_!l69B!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F59671390-2cce-43f5-bd8e-5bd78389961d_1400x933.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!l69B!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F59671390-2cce-43f5-bd8e-5bd78389961d_1400x933.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!l69B!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F59671390-2cce-43f5-bd8e-5bd78389961d_1400x933.jpeg" width="1400" height="933" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/59671390-2cce-43f5-bd8e-5bd78389961d_1400x933.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:933,&quot;width&quot;:1400,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!l69B!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F59671390-2cce-43f5-bd8e-5bd78389961d_1400x933.jpeg 424w, https://substackcdn.com/image/fetch/$s_!l69B!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F59671390-2cce-43f5-bd8e-5bd78389961d_1400x933.jpeg 848w, https://substackcdn.com/image/fetch/$s_!l69B!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F59671390-2cce-43f5-bd8e-5bd78389961d_1400x933.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!l69B!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F59671390-2cce-43f5-bd8e-5bd78389961d_1400x933.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Photo by <a href="https://unsplash.com/@markusspiske?utm_source=medium&amp;utm_medium=referral">Markus Spiske</a> on <a href="https://unsplash.com/?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure></div><p>First, let&#8217;s create a new project using Cargo with <code>cargo new rustasync --bin</code></p><p>Now, open up the generated Cargo.toml file and add the <a href="https://crates.io/crates/futures">futures-rs</a> crate to the dependencies section. You can follow the link above to learn more and find the complete documentation but in summary, it&#8217;s a bare-bones implementation of an async runtime. Your toml file should look similar to the one below:</p><pre><code>[package]
name = "rust-async"
version = "0.1.0"
edition = "2021"# See more keys and their definitions at <a href="https://doc.rust-lang.org/cargo/reference/manifest.html">https://doc.rust-lang.org/cargo/reference/manifest.html</a>[dependencies]
futures = "0.3"</code></pre><p>The futures-rs crate implements some of Rust's core abstractions for asynchronous programming. We&#8217;ll go over those in more depth later but for now, the <code>Future</code> trait is of the most relevance to us.</p><p>Let&#8217;s imagine that we&#8217;re going to fix breakfast at home. We might choose to make some coffee, eggs, and because I&#8217;m trying to be healthy, I&#8217;ll use the oven to make some bacon. My process would be something like starting the coffee maker and getting the coffee brewing. While that&#8217;s happening I could pre-heat the oven. While the oven is pre-heating I can get the bacon out and ready it for the oven. Once the bacon is ready I can put it in the oven, pour myself some coffee, and cook the eggs while waiting for the bacon to finish. I&#8217;m sure you get the idea at this point. If we were to prepare breakfast synchronously, one task after the other, it would take significantly longer to complete preparing breakfast and most of it would be cold by the time we got to eat it.</p><p>Let&#8217;s write a simple application to try and mimic this approach by using asynchronous code. Open up the main.rs file under the src directory and add the following code.</p><pre><code>// The block_on function simply blocks the current thread until the 
// Future has completed and returned.
use futures::executor::block_on;

fn main() {
    // The make_coffee, make_eggs, and make_bacon functions 
    // have all been annoted with the async keyword. 
    // This means that they will all return a Future.    
    // In fact, the following 2 functions are the same    
    // async fn foo() { // do stuff }
    // fn foo() -&gt; impl Future&lt;Outpout = ()&gt; {
    //     async { // do stuff }
    // }    

let coffee_future = make_coffee();
    // The block_on executor accepts a Future as input and blocks
    // the current thread until that future has completed
    block_on(coffee_future);
    let bacon_future = make_bacon();
    block_on(bacon_future);
    let eggs_future = make_eggs();
    block_on(eggs_future);
}

async fn make_coffee() {
    println!("Coffee started.");
}

async fn make_eggs() {
    println!("Eggs started.");
}

async fn make_bacon() {
    println!("Bacon started.");
}</code></pre><p>First, we import a very simple executor from the futures crate. In Rust futures are lazy. We&#8217;ll look at this in a bit more detail later, but creating a future does nothing on its own. It needs to be driven to completion by an executor. That&#8217;s the job of the <code>on_block()</code> executor. It accepts a Future as a parameter and executes that code while blocking the main thread.</p><p>The code above is not idiomatic because we create the Future that represents the async function, immediately call the <code>block_on()</code> executor, wait for the async code to complete, and repeat this process with the rest of our async functions. This effectively makes the execution synchronous. Let&#8217;s tweak the code a bit and make it look a bit more idiomatic.</p><p>So we know that we can start the coffee maker and let it work while we cook our bacon in the oven, so let's start those two tasks before starting our eggs.</p><p>First, we&#8217;ll add a new async function called <code>make_coffee_and_bacon()</code> shown below:</p><pre><code>async fn make_coffee_and_bacon() {
    make_coffee().await;
    make_bacon().await;
}</code></pre><p>Next, we&#8217;ll add another new async function called <code>async_main()</code>:</p><pre><code>async fn async_main() {
    let long_running_stuff = make_coffee_and_bacon();
    let other_stuff = make_eggs();
    futures::join!(long_running_stuff, other_stuff);
}</code></pre><p>Finally, we&#8217;ll change the existing <code>main()</code> function to look like this:</p><pre><code>fn main() {
    block_on(async_main());
}</code></pre><p>Let&#8217;s go through these changes one at a time. First, we create a new async function for the coffee and bacon tasks. You&#8217;ll notice that we use <code>await</code> inside the functions. Using <code>await</code> inside an <code>async</code> function is similar to using <code>on_block()</code> to wait until the <code>Future</code> has resolved, but instead of blocking the thread, it allows other tasks to run if the future is blocked, <em>e.g.</em>, waiting on input from a socket or stream.</p><p>Second, we&#8217;ve added the <code>async_main()</code> function. This is a pretty straightforward change. Since <code>await</code> can only be used inside async functions or async code blocks, and <code>main()</code> function cannot be annotated with the <code>async</code> keyword. While the thread that runs <code>main()</code> will block until the <code>block_on()</code> call resolves, everything from <code>async_main()</code>, all the way down the execution chain will run asynchronously. The third line is new. The <code>futures::join!()</code> macro is similar to the <code>await</code> keyword but it operates on multiple futures, returning only when all <code>Future</code>&#8217;s complete, regardless of the order of completion.</p><p>Earlier I mentioned that <code>Futures</code> are &#8220;lazy&#8221;, in that they do nothing on their own. They will never return a value unless driven to completion. This may seem strange, especially if you&#8217;re familiar with how other languages implement asynchronous code, but this is a deliberate choice by the language designers. By allowing <code>Future</code>'s to be lazy, creating an async function becomes a zero-cost operation. By &#8220;zero-cost&#8221;, I mean that there is no runtime impact for creating a <code>Future</code> unless you actually use them. If you want to see an observable example of this laziness in action, just remove the <code>.await </code>from the call to <code>make_coffee()</code> and <code>make_bacon()</code> in the <code>make_coffee_and_bacon()</code> function, and run the code. You&#8217;ll notice that when you run it after removing the .<code>await</code>, it will never print any output.</p><p>I wanted to note, in this specific example, that the order of execution of our async functions does not change. You could stick a random sleep in any, or all, of the three functions to change how long the execution time takes, but it will not impact the order the print statements appear.</p><p>This may seem counter-intuitive, after all, this is supposed to be asynchronous execution. Let's take a look at why. If you recall from the first article in the series, we mentioned that the <code>await</code> keyword is used by the generated state machine as a marker designating atomic units of execution. For example, in the async function <code>make_coffee_and_bacon()</code>, we first call <code>make_coffee().await</code>, followed by <code>make_bacon().await</code>. In the generated state machine, everything from the start of the function until the first <code>await</code> keyword is considered an atomic unit. Similarly, everything after the call to <code>make_coffee().await</code>, until the next <code>await</code> keyword would be another atomic unit. This means that the function <code>make_coffee()</code> will complete before running <code>make_bacon()</code>. The <code>await</code> keyword by itself doesn&#8217;t actually drive a Future to completion. It&#8217;s the <code>block_on()</code> call that drives the async code to completion. I used the term executor earlier but didn&#8217;t really define it. An executor is responsible for driving all of the async code it&#8217;s responsible for to completion. In our case, we pass <code>async_main()</code> to the <code>block_on()</code> executor and that executor is responsible for driving all of the async code that <code>async_main()</code> contains to completion, but <code>block_on()</code> doesn&#8217;t implement any logic which would allow it to multiplex execution. In order to allow code using async/await to execute in an asynchronous fashion, we need to use a more sophisticated executor. I think of it like this; a future is a computation we want to perform, the task schedules that futures execution, and the executor ensures that all of the tasks are completed, and coordinates interdependencies.</p><p>Incidentally, the reason the <code>join!()</code> macro exists is to allow a more flexible composition of tasks. For example, if we were to swap out <code>block_on()</code> for a more complex executor, we wouldn't need to make any other changes to our async code.</p><p>This was part 2 in a 3 part series. I&#8217;ve tried to present this information in a simple way while still giving enough details to provide a basic understanding. It turned out to be a bit more difficult than I anticipated. </p><p>In the next section, we&#8217;ll take a look at a more realistic implementation of Rust async and talk about some of the different runtimes available, as well as some of the gotchas. If you made it this far, please give the article a like, share it, or better yet, comment, so I know if you found this useful.</p><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;623eaade-094d-42d4-ac49-205f762e8673&quot;,&quot;caption&quot;:&quot;When we last left off we were using the primitive executor that is available with the futures crate, sometimes called futures-rs. The futures crate provides the common abstraction layer for async behavior in the Rust language. It is intended to be the basis for creating complex runtimes. As discussed in the previous art&#8230;&quot;,&quot;cta&quot;:&quot;Read full story&quot;,&quot;showBylines&quot;:true,&quot;size&quot;:&quot;lg&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;A Rust Async Primer-Pt 3&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:147052703,&quot;name&quot;:&quot;SV Fearless&quot;,&quot;bio&quot;:&quot;We wanted to live a life less ordinary so we sold our home, moved aboard a sailboat, and are learning to live a different kind of life.&quot;,&quot;photo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/8d80eeab-a1d7-4b14-9f05-b05b2d1c869c_3024x3024.jpeg&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2023-11-29T06:00:33.631Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/fetch/$s_!WeE4!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc08d8b17-f275-4927-a6a3-80b9ab623756_1400x933.jpeg&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://svfearless.substack.com/p/a-rust-async-primer-pt-3&quot;,&quot;section_name&quot;:&quot;Tech Bytes&quot;,&quot;video_upload_id&quot;:null,&quot;id&quot;:139107345,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:0,&quot;comment_count&quot;:0,&quot;publication_id&quot;:1963206,&quot;publication_name&quot;:&quot;Sailing Fearless&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!8gwY!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F129c4c28-acb2-48ca-bf62-10c7058d8379_1280x1280.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://svfearless.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Sailing Fearless is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[A Rust Async Primer-Pt. 1]]></title><description><![CDATA[A short stroll through asynchronous concepts in Rust.]]></description><link>https://svfearless.substack.com/p/a-rust-async-primer-pt-1</link><guid isPermaLink="false">https://svfearless.substack.com/p/a-rust-async-primer-pt-1</guid><dc:creator><![CDATA[SV Fearless]]></dc:creator><pubDate>Thu, 23 Nov 2023 18:49:46 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!QFv8!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9804a9ea-3edc-4fa6-9994-8ba105e6af7f_1400x933.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!QFv8!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9804a9ea-3edc-4fa6-9994-8ba105e6af7f_1400x933.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!QFv8!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9804a9ea-3edc-4fa6-9994-8ba105e6af7f_1400x933.jpeg 424w, https://substackcdn.com/image/fetch/$s_!QFv8!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9804a9ea-3edc-4fa6-9994-8ba105e6af7f_1400x933.jpeg 848w, https://substackcdn.com/image/fetch/$s_!QFv8!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9804a9ea-3edc-4fa6-9994-8ba105e6af7f_1400x933.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!QFv8!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9804a9ea-3edc-4fa6-9994-8ba105e6af7f_1400x933.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!QFv8!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9804a9ea-3edc-4fa6-9994-8ba105e6af7f_1400x933.jpeg" width="1400" height="933" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/9804a9ea-3edc-4fa6-9994-8ba105e6af7f_1400x933.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:933,&quot;width&quot;:1400,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Computer monitor showing code&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Computer monitor showing code" title="Computer monitor showing code" srcset="https://substackcdn.com/image/fetch/$s_!QFv8!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9804a9ea-3edc-4fa6-9994-8ba105e6af7f_1400x933.jpeg 424w, https://substackcdn.com/image/fetch/$s_!QFv8!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9804a9ea-3edc-4fa6-9994-8ba105e6af7f_1400x933.jpeg 848w, https://substackcdn.com/image/fetch/$s_!QFv8!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9804a9ea-3edc-4fa6-9994-8ba105e6af7f_1400x933.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!QFv8!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9804a9ea-3edc-4fa6-9994-8ba105e6af7f_1400x933.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Photo by <a href="https://unsplash.com/@markusspiske?utm_source=medium&amp;utm_medium=referral">Markus Spiske</a> on <a href="https://unsplash.com/?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure></div><p>These days, if you&#8217;re writing code in a reasonably modern language, it&#8217;s difficult to escape the idea of asynchronous programming. If you search for the most popular programming languages of 2022, every single entry in the list supports asynchronous programming. Most of them even use the same keywords in their syntax: async and await. This article will be the first in a series of articles that will present the general concepts, followed by a brief discussion of how async is implemented in Rust, and finally, some sample code to demonstrate the concepts.</p><h1><strong>Intro to Asynchronicity</strong></h1><p>Let&#8217;s start by clearing up some common misunderstandings. First Concurrency is the act of performing multiple tasks simultaneously. For example, I can both walk and chew bubble gum at the same time (most days). A different example would be making a pot of coffee while I read the morning paper. Both of these are examples of concurrency but the means by which they are achieved differ. Walking and chewing gum at the same time is an example of parallelism since I can do both things simultaneously. Making a pot of coffee while reading the paper is an example of synchronicity since I can&#8217;t physically do both tasks simultaneously. Instead, I prep the coffee maker and start it, then begin reading my paper. When the pot is done, I then stop reading, pour a cup, and go back to reading. I can work on both tasks at once, just not at the same time. In other words, I move on to another task, while waiting for the previous one to complete.</p><p>In languages that support async programming, in <em>very</em> simplified terms, async code is implemented using a compiler-generated state machine. This state machine uses the <code>await</code> keyword to break up the execution units into smaller chunks, creating a code block from the beginning of the method to the first <code>await</code> boundary as one task. The next execution chunk would be all the code between the first <code>await</code><strong>,</strong> and the second <code>await</code><strong>,</strong> if there is one. I mention this detail to point out that threads do not enter into the picture. They can<em>, </em>and certainly do, but the point is that asynchronous code has nothing to do with threads.</p><p>Why did anyone want to create this complicated-sounding mess? In short, it beats the alternatives. This technique allows us to create a large number of independent concurrent tasks without the expensive requirement of spinning up a large number of new threads, it&#8217;s easier to understand and reason about than the alternatives, and it&#8217;s performant.</p><h1><strong>How Rust Implements Async</strong></h1><p>When the designers of Rust implemented async/await they had some specific goals in mind, and as a result, there are several differences between Rust and most other languages.</p><p>In broad strokes, Rust async is built on two types; <code>Futures</code>'s and <code>Task</code>'s. A<code>Future</code> represents a piece of code that will execute and return a concrete result at some point in the future.</p><p>A <code>Task</code> is an in-process <code>Future</code> that is being driven to completion. In other words, a <code>Task</code> is a <code>Future</code> that can schedule its execution.</p><p>Async in Rust does not use heap allocations or dynamic dispatching, which makes it extremely performant and keeps the footprint small, allowing async to be used in embedded environments or other resource-constrained apps.</p><p>Rust has no built-in runtime for implementing async. It relies on separate crates to provide a runtime. As a consequence, you can select a runtime that aligns most appropriately with your needs. For example, there are both multi-threaded and single-threaded runtimes. It also means that if you&#8217;re not using async, your code won&#8217;t be bloated by unused features.</p><p>While Rust gives you the components you need to create a custom runtime, the reality is that very few people have the knowledge, time, or desire to put forth that level of effort. Instead, they&#8217;ll select an existing runtime. We&#8217;ll cover the primary runtimes later. In the next article, we&#8217;ll look at examples of async in Rust starting with a basic functioning async example, and adding complexity from there.</p><p>This has been part 1 of a 3-part series. If you enjoyed this article, please like, share, restack, or comment. Subscribe to keep up with the rest of the articles in the series where we move beyond introducing the concepts, and into actual code!</p><p>You can find part 2 of the series <a href="https://svfearless.substack.com/publish/post/139107220">here</a>.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://svfearless.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Sailing Fearless is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Yet Another Neovim Setup Article]]></title><description><![CDATA[Why do we need to keep revisiting this?]]></description><link>https://svfearless.substack.com/p/yet-another-neovim-setup-article-1148ef2303f3</link><guid isPermaLink="false">https://svfearless.substack.com/p/yet-another-neovim-setup-article-1148ef2303f3</guid><dc:creator><![CDATA[SV Fearless]]></dc:creator><pubDate>Tue, 14 Nov 2023 22:00:49 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/e086935e-f88a-44e5-bb24-12957d703e1c_800x534.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!J3C8!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad30fc24-454d-4154-9761-635be7b1f886_800x534.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!J3C8!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad30fc24-454d-4154-9761-635be7b1f886_800x534.jpeg 424w, https://substackcdn.com/image/fetch/$s_!J3C8!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad30fc24-454d-4154-9761-635be7b1f886_800x534.jpeg 848w, https://substackcdn.com/image/fetch/$s_!J3C8!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad30fc24-454d-4154-9761-635be7b1f886_800x534.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!J3C8!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad30fc24-454d-4154-9761-635be7b1f886_800x534.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!J3C8!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad30fc24-454d-4154-9761-635be7b1f886_800x534.jpeg" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ad30fc24-454d-4154-9761-635be7b1f886_800x534.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:null,&quot;width&quot;:null,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!J3C8!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad30fc24-454d-4154-9761-635be7b1f886_800x534.jpeg 424w, https://substackcdn.com/image/fetch/$s_!J3C8!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad30fc24-454d-4154-9761-635be7b1f886_800x534.jpeg 848w, https://substackcdn.com/image/fetch/$s_!J3C8!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad30fc24-454d-4154-9761-635be7b1f886_800x534.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!J3C8!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad30fc24-454d-4154-9761-635be7b1f886_800x534.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div></div></div></a><figcaption class="image-caption">Photo by <a href="https://unsplash.com/@ilyapavlov?utm_source=medium&amp;utm_medium=referral">Ilya Pavlov</a> on&nbsp;<a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure></div><h4>Why do we need to keep revisiting this?</h4><p>The short answer is we don&#8217;t.</p><p>The long answer is a bit more personal. While I don&#8217;t work as a developer anymore, I do still have to work with code occasionally, and I still, for whatever reason, enjoy tinkering with my tools. I find that talking about and tinkering with CLI tools is a lot of fun, and one of my favorite tools is Neovim. Every now and then I&#8217;ll re-evaluate my setup and try to improve on my previous config by setting up a new one from scratch, keeping what I felt worked well in the previous setup, and adding new things that I think will either improve on tools I already use or which will streamline a workflow that I didn&#8217;t previously have. And yes, believe it or not, as your career advances your workflow will also continue to change. Yet another reason occasional re-evaluation is a good thing.</p><p>To potentially save people some time, I want to mention that this article is not intended for people new to Vim or NeoVim. I assume that you have some previous knowledge of vim/vim and either have experience with Lua or, at the least, no aversion to it.</p><h4>Neovim vs&nbsp;Vim</h4><p>I wanted to get this out of the way real quick because there will probably be some confusion if you&#8217;re not familiar with the history. <a href="https://github.com/neovim/neovim">Neovim</a> is a fork of <a href="https://github.com/vim/vim">Vim</a> that has several notable improvements. The core functionality is compatible with Vim, <em>e.g.</em>, if you aliased a bare version of <code>vim</code> with a bare version <code>nvim</code>, a user opening the editor would never realize they were using NeoVim.</p><p>The notable improvements mentioned above are based on aggressive refactoring giving the following benefits:</p><ul><li><p>Overall performance improvements</p></li><li><p>Using the <a href="https://wiki.archlinux.org/title/XDG_Base_Directory#Specification">XDG Base Directory</a> standard for configuration instead of hardcoded directories</p></li><li><p>A more sophisticated plugin API that allows the use of RPC calls and Lua for plugin creation and scripting (Vimscript v1 is also supported)</p></li><li><p>Native support of <a href="https://en.wikipedia.org/wiki/Language_Server_Protocol">LSP</a></p></li><li><p>Support for asynchronous I/O</p></li></ul><p>I switched between the two editors without much thought for years but decided to finally commit to Nvim in June of 2022 when the decision was made to essentially re-implement vimscript from scratch instead of simply embracing an already existing language like Lua or Python. Are you going to re-invent a wheel that is already notoriously quirky and only usable in a single tool? That was when I decided just to embrace NeoVim.</p><h4>On with the Configuration</h4><p>Before we start a quick configuration note; nvim supports <code>init.vim</code> or <code>init.lua</code> as the default configuration file, but not both. If you&#8217;re going to commit to Nvim, you probably want to use <code>init.lua</code>, and since I do, I will.</p><p>I&#8217;ve always been kind of scattered when it comes to plugin management but I&#8217;d like to find a single solution and stick to it. Since I&#8217;m all in for learning new things, I&#8217;ll be starting off with a clean slate and using <a href="https://github.com/folke/lazy.nvim">Lazy.nvim</a> as my plugin manager. It&#8217;s still relatively new (and I&#8217;m new to it) but I&#8217;ve read good things about it, it embraces lazy loading of plugins which is nice, although load time has never been a real issue for me to be honest, and the default plugins it installs make for a fascinating user experience. There was a lot of exploration there.</p><p>One of the things I truly appreciate is that the instructions for installation start off by telling you to make a backup of your existing configuration and then show you how to do it. Thanks, buddy!</p><p>After following the install instructions and opening nvim, you&#8217;ll be visually assaulted by a bunch of scrolling text as lazy.nvim downloads all of the default plugins and installs them. When that finally ends you&#8217;ll see something like this that shows you the total number of plugins, whats&#8217; installed and what&#8217;s currently loaded:</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!fVet!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6c7fc5a3-7935-466f-a294-2ae45723dded_1200x776.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!fVet!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6c7fc5a3-7935-466f-a294-2ae45723dded_1200x776.png 424w, https://substackcdn.com/image/fetch/$s_!fVet!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6c7fc5a3-7935-466f-a294-2ae45723dded_1200x776.png 848w, https://substackcdn.com/image/fetch/$s_!fVet!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6c7fc5a3-7935-466f-a294-2ae45723dded_1200x776.png 1272w, https://substackcdn.com/image/fetch/$s_!fVet!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6c7fc5a3-7935-466f-a294-2ae45723dded_1200x776.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!fVet!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6c7fc5a3-7935-466f-a294-2ae45723dded_1200x776.png" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/6c7fc5a3-7935-466f-a294-2ae45723dded_1200x776.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:null,&quot;width&quot;:null,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!fVet!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6c7fc5a3-7935-466f-a294-2ae45723dded_1200x776.png 424w, https://substackcdn.com/image/fetch/$s_!fVet!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6c7fc5a3-7935-466f-a294-2ae45723dded_1200x776.png 848w, https://substackcdn.com/image/fetch/$s_!fVet!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6c7fc5a3-7935-466f-a294-2ae45723dded_1200x776.png 1272w, https://substackcdn.com/image/fetch/$s_!fVet!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6c7fc5a3-7935-466f-a294-2ae45723dded_1200x776.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a><figcaption class="image-caption">Once lazy.nvim has completed it&#8217;s initial&nbsp;setup</figcaption></figure></div><p>I&#8217;ll admit, I was a bit startled at first. It clearly just left me in a buffer displaying the lazy.nvim install plugin management interface as indicated by the menu at the top. While it was not what I was expecting, it was a simple and effective menu. The&nbsp;<code>?</code> action gives some useful context on what was going on.</p><p>When I hit&nbsp;<code>:q</code>I&#8217;d intuitively half expected to quit nvim, but instead, it dumped me to the default lazy.nvim popup interface.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!n6vf!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F15b64193-9e14-4184-81b1-d33ad1a47fa1_1200x776.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!n6vf!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F15b64193-9e14-4184-81b1-d33ad1a47fa1_1200x776.png 424w, https://substackcdn.com/image/fetch/$s_!n6vf!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F15b64193-9e14-4184-81b1-d33ad1a47fa1_1200x776.png 848w, https://substackcdn.com/image/fetch/$s_!n6vf!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F15b64193-9e14-4184-81b1-d33ad1a47fa1_1200x776.png 1272w, https://substackcdn.com/image/fetch/$s_!n6vf!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F15b64193-9e14-4184-81b1-d33ad1a47fa1_1200x776.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!n6vf!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F15b64193-9e14-4184-81b1-d33ad1a47fa1_1200x776.png" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/15b64193-9e14-4184-81b1-d33ad1a47fa1_1200x776.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:null,&quot;width&quot;:null,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!n6vf!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F15b64193-9e14-4184-81b1-d33ad1a47fa1_1200x776.png 424w, https://substackcdn.com/image/fetch/$s_!n6vf!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F15b64193-9e14-4184-81b1-d33ad1a47fa1_1200x776.png 848w, https://substackcdn.com/image/fetch/$s_!n6vf!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F15b64193-9e14-4184-81b1-d33ad1a47fa1_1200x776.png 1272w, https://substackcdn.com/image/fetch/$s_!n6vf!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F15b64193-9e14-4184-81b1-d33ad1a47fa1_1200x776.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a><figcaption class="image-caption">Default lazy.nvim</figcaption></figure></div><p>This is what you would see if you exit nvim and start it again. Clearly, the intent is to have a single plugin manager that, by default, turns nvim into an IDE any user of VS Code or IntelliJ would feel comfortable with. I would also add that I&#8217;d prefer it didn't have the giant modal proclaiming that it was Lazyvim on startup but I can&#8217;t really blame it, the resulting product is pretty nice.</p><p>The documentation for <a href="https://www.lazyvim.org/">lazy.nvim</a> is pretty good and after some reading and research, I found a more simplistic approach to using lazy.nvim that felt more like what I was after; using the plugin manager as a plugin manager and ignoring its opinion of what my IDE should be.</p><h4>Simplistic Approach</h4><p>I deleted the nvim directory and everything in it (remember not to delete your backup of the original configuration) starting over from scratch. Overkill perhaps but I like a clean slate approach, it helps make things more reproducible.</p><p>As I said earlier, my usual approach tends to look like a Frankenstein hodgepodge of configuration files, so I&#8217;m going to try and be less haphazard about my config and try to impose some structure on it from the start. I eventually decided on a pretty standard-looking folder structure as shown below.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!oAhY!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F24a21211-f9a9-4841-90a6-af7d29e09148_680x748.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!oAhY!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F24a21211-f9a9-4841-90a6-af7d29e09148_680x748.png 424w, https://substackcdn.com/image/fetch/$s_!oAhY!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F24a21211-f9a9-4841-90a6-af7d29e09148_680x748.png 848w, https://substackcdn.com/image/fetch/$s_!oAhY!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F24a21211-f9a9-4841-90a6-af7d29e09148_680x748.png 1272w, https://substackcdn.com/image/fetch/$s_!oAhY!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F24a21211-f9a9-4841-90a6-af7d29e09148_680x748.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!oAhY!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F24a21211-f9a9-4841-90a6-af7d29e09148_680x748.png" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/24a21211-f9a9-4841-90a6-af7d29e09148_680x748.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:null,&quot;width&quot;:null,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!oAhY!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F24a21211-f9a9-4841-90a6-af7d29e09148_680x748.png 424w, https://substackcdn.com/image/fetch/$s_!oAhY!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F24a21211-f9a9-4841-90a6-af7d29e09148_680x748.png 848w, https://substackcdn.com/image/fetch/$s_!oAhY!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F24a21211-f9a9-4841-90a6-af7d29e09148_680x748.png 1272w, https://substackcdn.com/image/fetch/$s_!oAhY!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F24a21211-f9a9-4841-90a6-af7d29e09148_680x748.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>I think it&#8217;s reasonably clear. The <code>user</code> directory is what I&#8217;ve chosen to name the directory where I&#8217;ll store my user-specific Lua scripts, and the <code>user/plugins</code> directory is where the plugins will go. I could have named the directory after my OS username, which is what most people do. I chose this just to be different. I suppose for a multi-user scenario, it would make sense to have the individual user directories in there, <em>e.g.,</em> <code>user/user.name</code> but I don&#8217;t think most people use that kind of setup and if I&#8217;m honest, I was just lazy and wanted to be different.</p><p>Below is my base level<code>init.lua</code> in the root <code>lua</code>directory. The <code>lazy.lua</code> script is in the root plugin directory. It puts the <a href="https://github.com/junegunn/fzf"><code>fzf</code></a> executable into the runtime path, loads my preferences for options (things like line numbers, tab stops, <em>etc</em>.), loads the lazy.vim settings (more on that later), and finally create an auto-command that lazy loads keymaps and user-defined auto-commands:</p><pre><code>-- Set runtime path to include fzf
vim.opt.rtp:prepend("/opt/homebrew/opt/fzf")

-- Load options file
require("user.options")
require("user.lazy")

vim.api.nvim_create_autocmd("User", {
  pattern = "VeryLazy",
  callback = function()
    require "user.autocmds"
    require "user.keymaps"
  end,
})

</code></pre><p>A couple of notes here&#8202;&#8212;&#8202;I load the options ahead of everything else because I keep my leader key in there and I want that mapped so that any subsequent plugins, autocommands, <em>etc.,</em> get mapped correctly; I also don&#8217;t know that the lazy loading of auto-commands or keymaps is all that useful, but it was keeping with the theme so I went with it.</p><p>The current <code>lazy.lua</code> file contains code that pulls the latest stable copy of lazy.nvim when needed, adds lazy.nvim to the runtime path, and lastly tells lazy where the import directories are located (it doesn&#8217;t import nested directories under <code>lua</code> before setting a couple of options on what to do regarding updates.</p><pre><code>local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim"
if not vim.loop.fs_stat(lazypath) then
  vim.fn.system({
    "git",
    "clone",
    "--filter=blob:none",
    "https://github.com/folke/lazy.nvim.git",
    "--branch=stable", -- latest stable release
    lazypath,
  })
end
vim.opt.rtp:prepend(lazypath)

require("lazy").setup({ {import = "user.plugins"}, {import = "user.plugins.lsp"} }, {
  checker = {
    enabled = true,
    notify = false,
  },
  change_detection = {
    notify = false,
  },
})</code></pre><p>Now when I start nvim it will start as it normally would I can use the command&nbsp;<code>:Lazy</code> to bring up the new plugin manager interface when needed.</p><h4>The Plugins</h4><p>The plugins listed in the screenshot are the ones that I&#8217;m currently using and have configured. A quick note on lazy; I was pleasantly surprised that when I created a new plugin file and saved it, lazy picked up the change and loaded the configuration.</p><p>Now that I&#8217;m embracing a Lua-only setup with nvim, I needed to transfer all of my vimScript stuff to Lua. I&#8217;m lazy and don&#8217;t like typing so I shamelessly stole my coworkers <code>options.lua</code> and <code>keymaps.lua</code> as a starting point to minimize effort. I also added a <code>autocmds.lua</code> file for any autocommands I&#8217;ll want to set up.</p><p>I set up nvim-tree as my file explorer, which is new to me. I traditionally haven&#8217;t used a file explorer. I also configured a nerd font to provide widgets.</p><p>I&#8217;ve got an <code>init.lua</code> for the plugins directory to load plugins that I don&#8217;t need to provide any specific configuration for and I&#8217;ve moved the color scheme into its own file. The <code>lazy.lua</code> file handles the configuration of the plugin manager specifically, as shown above. In addition to the <code>init.lua</code> file for simple plugins, there are individual files for plugins that require configuration to meet your needs.</p><p>There was more work involved than I&#8217;d hoped in migrating to a pure Lua configuration, but there are a ton of examples out there and the Neovim documentation is pretty good.</p><p>You can also see that I moved the lsp configuration into its own directory. The <code>lspconfig.lua</code> belongs to the <a href="https://github.com/neovim/nvim-lspconfig"><code>nvim-lspconfig</code></a> plugin which contains configurations for the nvim lsp client. It can get quite large so I&#8217;m thinking about moving out the configuration for individual languages into their own files. The other plugin there is <a href="https://github.com/williamboman/mason.nvim"><code>mason</code></a> which is used to configure external tools like linters, formatters, <em>etc.</em>, through a single interface.</p><p>Of the less specialized plugins themselves, there are only a few interesting items. I&#8217;ve never used <a href="https://github.com/stevearc/dressing.nvim">dressing</a>, <a href="https://github.com/nvim-treesitter/nvim-treesitter">tree-sitter</a>, <a href="https://github.com/nvim-telescope/telescope.nvim">telescope</a>, or <a href="https://github.com/folke/which-key.nvim">which-key</a>. I saw dressing looking around at nvim plugins and liked the way it looked so I thought I&#8217;d try it, nothing really functional was added there.</p><p>I&#8217;ve heard good things about tree-sitter and the plugins that wrap it, and other nvim users that I respect use it, so I thought I&#8217;d try that and I like the idea of some additional text objects.</p><p>People have been raving about telescope for years. I&#8217;ve used <a href="https://github.com/junegunn/fzf.vim/tree/master">fzf</a> for years and have been completely satisfied with it as a tool for finding files, but I&#8217;ve been working almost entirely with familiar code bases. It was postulated to me that for getting around large, unfamiliar code bases, fzf is as much hindrance as help. This seems reasonable so I thought I&#8217;d give it a shot and give me an excuse to start looking at new codebases. I still have fzf.vim configured though because I love using it to find buffers. I may change my mind later and go all in on telescope. We&#8217;ll see.</p><p>I&#8217;ve never used which-key either but I figured with all of the new plugins, something that gives me a menu for keystrokes will be helpful until I have them in muscle memory.</p><p>These were just my ramblings on re-configuring my nvim setup. Let me know if you have any questions, recommendations, or comments.</p>]]></content:encoded></item><item><title><![CDATA[Streaming Data is Undergoing a Paradigm Shift]]></title><description><![CDATA[Change is coming.]]></description><link>https://svfearless.substack.com/p/streaming-data-is-undergoing-a-paradigm-shift-515487483efc</link><guid isPermaLink="false">https://svfearless.substack.com/p/streaming-data-is-undergoing-a-paradigm-shift-515487483efc</guid><dc:creator><![CDATA[SV Fearless]]></dc:creator><pubDate>Wed, 18 Oct 2023 19:56:11 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/4ffa5457-78b0-4668-b668-a4bb8d14aa6b_800x800.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Change is coming.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!drS4!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd66dfac6-0b72-4c5a-9a2e-6f037e2e668b_800x800.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!drS4!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd66dfac6-0b72-4c5a-9a2e-6f037e2e668b_800x800.jpeg 424w, https://substackcdn.com/image/fetch/$s_!drS4!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd66dfac6-0b72-4c5a-9a2e-6f037e2e668b_800x800.jpeg 848w, https://substackcdn.com/image/fetch/$s_!drS4!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd66dfac6-0b72-4c5a-9a2e-6f037e2e668b_800x800.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!drS4!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd66dfac6-0b72-4c5a-9a2e-6f037e2e668b_800x800.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!drS4!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd66dfac6-0b72-4c5a-9a2e-6f037e2e668b_800x800.jpeg" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d66dfac6-0b72-4c5a-9a2e-6f037e2e668b_800x800.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:null,&quot;width&quot;:null,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;A large hub in space connecting a virtual network&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="A large hub in space connecting a virtual network" title="A large hub in space connecting a virtual network" srcset="https://substackcdn.com/image/fetch/$s_!drS4!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd66dfac6-0b72-4c5a-9a2e-6f037e2e668b_800x800.jpeg 424w, https://substackcdn.com/image/fetch/$s_!drS4!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd66dfac6-0b72-4c5a-9a2e-6f037e2e668b_800x800.jpeg 848w, https://substackcdn.com/image/fetch/$s_!drS4!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd66dfac6-0b72-4c5a-9a2e-6f037e2e668b_800x800.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!drS4!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd66dfac6-0b72-4c5a-9a2e-6f037e2e668b_800x800.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div></div></div></a></figure></div><h4>Kafka Tiered Storage for the&nbsp;Masses</h4><p>If you&#8217;re a Kafka fan you may have heard that Apache Kafka 3.6 RC 0 implements tiered storage for Kafka. This means a preview will be available in the opensource Kafka.</p><p>Until now tiered storage was not supported natively in Kafka and has only been available using third-party tools or modules, most of them proprietary, such as the Confluent tiered storage module, and Amazon Managed Streaming for Apache Kafka. This means that if you wanted tiered storage, you were almost certainly locked into a vendor paying for it.</p><p>With the release of 3.6 RC 0, tiered storage is now available to the masses. This may sound small but in reality, it&#8217;s a pretty big deal, and I believe that we&#8217;re on the precipice of a true paradigm shift in how we approach streaming data.</p><h3>Why This is Important</h3><p>Kafka has become a huge part of the streaming data ecosystem and is arguably one of, if not the, most common entry point of all data into a distributed system. Data is commonly consumed in real-time, but there&#8217;s also the capability to fetch historic data as well, as long as it&#8217;s within the retention policy parameters. Here is where Kafka has traditionally shown its weakness. To understand this weakness we need a little background.</p><h4>How Kafka Works (High&nbsp;Level)</h4><p>Kafka stores messages in append-only log segments on the brokers' local disk. The retention policy for these logs, configured by topic or system-wide, determines the oldest message that can be fetched. This mechanism is used to guarantee that consumers won&#8217;t miss messages even if the application fails or otherwise loses connectivity.</p><h4>The Storage&nbsp;Issue</h4><p>The total storage needed for the cluster is proportional to the number of topics/partitions, message rate, and the retention period. As a grossly over-simplified example without getting into topics, partitions, replication factors, index size, <em>etc.</em>, the approximate storage size per node of a hypothetical cluster with 12 nodes, a 3 day retention policy, 10k messages per second, and a message size of 1k, can be determined using the following method:<br><em>Storage (in bytes) = (Messages per second) * (Message size in bytes) * (Retention time in seconds)</em></p><p>This gives us a storage size per node of over 2.59TB. Granted, this isn&#8217;t a huge amount of storage, but that example is not a large Kafka cluster. As an example, LinkedIn claims to run over 100 Kafka clusters with more than 4,000 brokers, which serve more than 100,000 topics and 7 million partitions, resulting in over 7 trillion messages processed daily. Using the grossly oversimplified approach we used earlier and making some naive assumptions we can get a better idea of what that kind of scale looks like.</p><p>We&#8217;ll assume a 1k messages size, that the 7T messages are evenly distributed between clusters over a 24 hour period and a 3 day retention policy, and that it&#8217;s something like 100 clusters of 40 nodes each. Using the previous formula that would be around 190.3TB <em>per node</em>. The naive storage size for one of those clusters is over 7.6PB and we&#8217;re assuming a 3 day retention period.</p><h4>Compounding Storage&nbsp;Issues</h4><p>We&#8217;ve seen that the retention period has a huge effect on the storage needs of the cluster but that&#8217;s just the start. Kafka cluster storage is typically scaled by adding new nodes to the cluster but this lowers the cost efficiency because we&#8217;re adding an entirely new machine instead of just adding the storage by itself.</p><p>In an ideal world, we would keep the data indefinitely but due to inherent constraints around storage, it&#8217;s just too impractical to do. To add more complexity to this scenario, when a broker fails, the failed node is replaced by a new node. The new node must then copy all of the data that was on the failed broker from other replicas.</p><p>Let&#8217;s add a little more scaling complexity; when a new node is added to scale the cluster storage, cluster rebalancing assigns partitions to the new node which also requires copying of data. The time for recovery and rebalancing is proportional to the amount of data stored locally on a Kafka broker. When you have a lot of clusters running 100s of brokers, a node failure is a common occurrence, and a lot of time is spent in recovery adding stress to the system.</p><h3>Full Circle</h3><h4>Tiered Storage is the&nbsp;Solution</h4><p>Kafka messages are typically consumed in a streaming fashion using tail reads. That is to say that it&#8217;s leveraging the OS-level page cache to serve the data, not the disk. Older data is typically only read from the disk infrequently for backfill (lagging consumer) or node recovery purposes.</p><p>Tiered storage in Kafka configures two tiers for storage; local and remote. The remote tier uses S3, GCS, or some other arbitrary external, long-term storage service. The two tiers configure separate retention policies allowing the local storage policy to be reduced significantly, while the remote storage can be configured to retain data for as long as desired.</p><p>Storage has now effectively been decoupled from the broker, and by reducing the local retention policy we&#8217;ve reduced the amount of local data to be copied for node replacement and rebalancing.</p><p>This results in longer data retention, lower costs, faster scaling, and faster rebalancing.</p><h3>Implications of Tiered&nbsp;Storage</h3><p>Once a new feature gets released into the wild, people start playing with it in unexpected ways. Sometimes, if the thing changes a fundamental assumption, it grows and changes, and gets used in ways that are obvious only in hindsight. I feel that tiered storage in Kafka is one of those features. There has been a fundamental assumption by Kafka users that you never ever use Kafka for anything but recent data. It&#8217;s too expensive, it&#8217;s too difficult to manage. So they make Kafka the backbone of their streaming data and offload new messages as quickly as possible into other systems. They will transform the data and then push that data into other systems more appropriate for long-term storage, relational DBs for transactions, OLAP DBs for analytics, <em>etc</em>., creating the complex event-driven big data pipelines we all know and love. Tiered storage gives the middle finger to that assumption.</p><p>I&#8217;m not saying that existing ETL pipelines are going to go away. What I am saying is that people are going to begin experimenting with what&#8217;s possible. Can I have a Kafka cluster with 1PB of remote storage which can be accessed as needed? Can Kafka now become my single source of truth and distribute messages on demand to anyone who wants to query it?</p><p>Here&#8217;s a scenario to think about; Let&#8217;s assume that we have a Kafka as the entry point of all data into our data pipelines and we&#8217;ve enabled tiered storage, so we can scale extremely efficiently. What do we do with all of that data that we can now store cheaply and efficiently?</p><p>Let&#8217;s take our row-based data from Kafka and transform it into a columnar-based format like Parquet, using Flink or Spark, and dump that data into the Iceberg Open Table format which can then be queried directly.</p><p>We&#8217;re talking about merging the ingestion and storage layers and with some simple transformations, we&#8217;ve got data we can query by column or row. How many steps in the data pipeline can we eliminate or simplify?</p><p>What if the remote tier were directly transformed into Iceberg tables? I do think there is a paradigm shift coming in the streaming data ecosystem. It&#8217;s like seeing the outlines of a person through the fog, but you know it&#8217;s there.</p><p>Then again, I could be completely wrong. Who knows?</p>]]></content:encoded></item><item><title><![CDATA[Saturated Kafka Consumer Groups]]></title><description><![CDATA[How do you know if your service is at its limits?]]></description><link>https://svfearless.substack.com/p/saturated-kafka-consumer-groups-41ed24af40e7</link><guid isPermaLink="false">https://svfearless.substack.com/p/saturated-kafka-consumer-groups-41ed24af40e7</guid><dc:creator><![CDATA[SV Fearless]]></dc:creator><pubDate>Tue, 26 Sep 2023 21:21:04 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/e8a2f46d-c200-4cbf-b9ac-884ddc3c2998_512x512.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>How do you know if your service is at its limits?</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!suYi!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffe325f79-0f12-4dc0-b04b-d2be8e023625_512x512.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!suYi!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffe325f79-0f12-4dc0-b04b-d2be8e023625_512x512.jpeg 424w, https://substackcdn.com/image/fetch/$s_!suYi!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffe325f79-0f12-4dc0-b04b-d2be8e023625_512x512.jpeg 848w, https://substackcdn.com/image/fetch/$s_!suYi!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffe325f79-0f12-4dc0-b04b-d2be8e023625_512x512.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!suYi!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffe325f79-0f12-4dc0-b04b-d2be8e023625_512x512.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!suYi!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffe325f79-0f12-4dc0-b04b-d2be8e023625_512x512.jpeg" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/fe325f79-0f12-4dc0-b04b-d2be8e023625_512x512.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:null,&quot;width&quot;:null,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Man sitting in front of computer monitors&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Man sitting in front of computer monitors" title="Man sitting in front of computer monitors" srcset="https://substackcdn.com/image/fetch/$s_!suYi!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffe325f79-0f12-4dc0-b04b-d2be8e023625_512x512.jpeg 424w, https://substackcdn.com/image/fetch/$s_!suYi!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffe325f79-0f12-4dc0-b04b-d2be8e023625_512x512.jpeg 848w, https://substackcdn.com/image/fetch/$s_!suYi!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffe325f79-0f12-4dc0-b04b-d2be8e023625_512x512.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!suYi!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffe325f79-0f12-4dc0-b04b-d2be8e023625_512x512.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div></div></div></a><figcaption class="image-caption">Image Credit: NightCafe</figcaption></figure></div><p>I am not a Kafka expert, but I do work with it on an almost daily basis. Recently, one of my clients told me they were considering implementing a feature that would double their Kafka workload and asked if I could support them by helping right-sizing a new cluster. The ensuing conversation uncovered some questions I didn&#8217;t have answers for and I thought I&#8217;d share my search for one of those. Namely, had the client already saturated their consumer groups.</p><h4>What is a Consumer&nbsp;Group?</h4><p>In Kafka, a consumer reads messages from the broker, for example, temperature data published to a topic on the broker (by a producer), is read by an application that analyzes the data for trends (by a consumer). Simple enough.</p><p>However, if there are 5,000 sensors publishing data to that topic every minute, my single app may not be able to read the messages fast enough and a backlog starts to build up. In this case, one solution might be creating multiple consumers to process the data in parallel. Enter, consumer groups. Consumer groups allow multiple consumers to read from a single topic without stepping on each other's toes. It lets the processing of messages be parallelized across multiple consumers to provide horizontal scalability.</p><h4>What is Consumer Group Saturation?</h4><p>Consumer group saturation is pretty much what it sounds like. Just like a single consumer can get overwhelmed by the volume of messages it has to process, so too can a consumer group. Basically, messages are coming in faster than they can be processed and the message backlog grows faster than they can be consumed. This is referred to as saturation.</p><p>Saturation can occur for a bunch of different reasons but the most common ones can be grouped into</p><ul><li><p>Not enough consumers in the group</p></li><li><p>Network latency</p></li><li><p>Consumer group rebalancing</p></li><li><p>Consumers are slow/inefficient</p></li></ul><p>The not-enough-consumers issue tends to sneak up when you&#8217;re not paying attention and don&#8217;t have good monitoring in place. When you&#8217;re message volume increases over a long enough time period that the trend goes unnoticed, you&#8217;ll find that your backlog will grow sporadically, and eventually consistently until eventually you&#8217;re never able to catch up.</p><p>Network latency tends to be transient but can become a more persistent issue if you&#8217;re already on the edge of saturation as it will create a larger backlog and if you&#8217;re already on the edge of saturation you may not be able to catch up before something else happens to create a saturation event. These types of network events can exacerbate the situation outlined above.</p><p>Kafka topics are divided into partitions. Partitions are used to distribute the load across the cluster by assigning a different partition to each broker in the cluster. When a consumer group adds a consumer that consumer is assigned a subset of partitions that it will read from. When a consumer is added or removed from a consumer group, Kafka will rebalance the consumer group to evenly spread the load, <em>i.e.</em>, it will reassign the partitions to each consumer. And while the rebalancing process does its best to cause as little disruption to consumer processing as possible, in reality, a small storm of controlled chaos results. This means that while the rebalancing is in process some consumers may be sitting idle while others are working on overload. Adding and removing consumers frequently will create havoc on topics with high message throughput.</p><p>Much like not having enough consumers in the group, slow/inefficient processing of messages tends to sneak up on you over time, only in this case, it's probably due to application/code changes in the consumers that are processing the messages, rather than in the number of messages to process. Of course, you could turn that on its head and say that instead, it&#8217;s an issue with code optimization rather than consumer group saturation, but from Kafka&#8217;s point of view (and likely your DevOps/SREs perspective), &#8220;po-ta-to&#8221;, &#8220;po-TAH-to&#8221;.</p><h4>How Do I Know It&#8217;s Saturated?</h4><p>Before I throw out some key indicators, I want to take a moment to point out the obvious, but you need to establish a baseline for your performance metrics because as is the case with almost everything, each use case has its own peculiar needs. So make sure you know what it looks like when behaving well.</p><p>Assuming that you know what these metrics look like under normal circumstances, here are some things you can monitor to determine if your consumer groups are saturated, or headed that way.</p><p><strong>Consumer Group Lag:<br></strong>What does your consumer group lag look like? The difference between the current consumer group message offset and the offset in the topic represents the number of unprocessed messages for a consumer. Automated monitoring implementation is beyond the scope of this article but the most convenient way to determine the consumer group lag in an ad-hoc fashion is by using the Kafka Consumer Offset Checker tool included with your Kafka installation.&nbsp;<br>If the number of unprocessed messages is growing or sits at a high threshold without shrinking, you&#8217;re probably saturated.<br>Be sure to monitor the lag rate, <em>i.e.</em>, is the consumer group lag growing or shrinking over time? You can use this to determine trends and see if you&#8217;re approaching saturation before you start suffering from it.</p><p><strong>Consumer-based Metrics:<br></strong>How fast are you consumers processing messages? If the time is constant but consumer lag is increasing, you&#8217;re saturated.</p><p><strong>Resource Usage:<br></strong>High CPU and memory usage may indicate that the consumers are struggling to keep up with the load.</p><h4>How Do I Fix&nbsp;It?</h4><p><strong>Monitoring:</strong><br>The best medicine is preventative. Avoid getting into the issue in the first place with proper monitoring. Make sure that you are monitoring trends that can alert you if, for example, you see that over the past month your message throughput has grown by more than 10 percent.</p><p><strong>Avoid Consumer Group Rebalances:</strong><br>Are consumers rebalancing frequently? Rebalances are typically caused by adding or removing consumers from a consumer group. Avoid doing this on a frequent basis as it can wreak havoc with your message processing.</p><p><strong>Slight Tangent</strong><br>Remember that partitions are how Kafka spreads the load over the cluster allowing for scalability and parallelism. This leads to some potential gotchas.</p><ul><li><p>If you have more consumers than partitions in a consumer group, some of the consumers will sit idle.</p></li><li><p>If there are more partitions than consumers, some partitions will sit idle and some consumers will be assigned multiple partitions.</p></li></ul><p>Consumer groups and partitions need to be deliberately balanced. On an efficiency note, you can never remove partitions, you can only add, so if you&#8217;re unsure about the future, starting smaller is better.</p><p><strong>Add More Consumers:<br></strong>If you&#8217;ve determined that a lack of consumers is the source of saturation, one of the easiest ways to deal with this is to simply add more consumers to the group. Just be sure that you balance the number of partitions with the number of consumers.</p><p><strong>Slow or Inefficient Consumers:</strong><br>If you&#8217;ve determined that the consumers are too inefficient, then it&#8217;s time to look at optimization or re-architecting how your messages are consumed and/or processed. Go figure.</p><p><strong>Network Latency<br></strong>On the one hand, there&#8217;s very little most of us can do about this one other than change our provider. The good news is that as far as businesses are concerned, it&#8217;s one you&#8217;re least likely to encounter as a persistent issue.</p><p>If you found this quick overview of saturation useful please let me know by clapping or subscribing so I know what you like. If you hated it, or have any comments, corrections, or additions, please leave me a comment and I&#8217;ll try to use that feedback in the future.</p>]]></content:encoded></item><item><title><![CDATA[A Simple Cloud Development Environment]]></title><description><![CDATA[This is my dead simple Rust development setup on GCP]]></description><link>https://svfearless.substack.com/p/a-simple-cloud-development-environment-2f0d3fedf1e</link><guid isPermaLink="false">https://svfearless.substack.com/p/a-simple-cloud-development-environment-2f0d3fedf1e</guid><dc:creator><![CDATA[SV Fearless]]></dc:creator><pubDate>Sat, 22 Jul 2023 17:30:40 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/fe5bb298-a3ff-4e01-aff7-c09fe70dece9_800x800.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>This is my dead simple Rust development setup on GCP</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!BYjH!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F548ceef7-f612-4108-9c3a-c44ba19e1e01_800x800.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!BYjH!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F548ceef7-f612-4108-9c3a-c44ba19e1e01_800x800.jpeg 424w, https://substackcdn.com/image/fetch/$s_!BYjH!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F548ceef7-f612-4108-9c3a-c44ba19e1e01_800x800.jpeg 848w, https://substackcdn.com/image/fetch/$s_!BYjH!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F548ceef7-f612-4108-9c3a-c44ba19e1e01_800x800.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!BYjH!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F548ceef7-f612-4108-9c3a-c44ba19e1e01_800x800.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!BYjH!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F548ceef7-f612-4108-9c3a-c44ba19e1e01_800x800.jpeg" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/548ceef7-f612-4108-9c3a-c44ba19e1e01_800x800.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:null,&quot;width&quot;:null,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Image generaetd by Nightcafe&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Image generaetd by Nightcafe" title="Image generaetd by Nightcafe" srcset="https://substackcdn.com/image/fetch/$s_!BYjH!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F548ceef7-f612-4108-9c3a-c44ba19e1e01_800x800.jpeg 424w, https://substackcdn.com/image/fetch/$s_!BYjH!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F548ceef7-f612-4108-9c3a-c44ba19e1e01_800x800.jpeg 848w, https://substackcdn.com/image/fetch/$s_!BYjH!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F548ceef7-f612-4108-9c3a-c44ba19e1e01_800x800.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!BYjH!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F548ceef7-f612-4108-9c3a-c44ba19e1e01_800x800.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div></div></div></a></figure></div><h3>Quick and&nbsp;Dirty</h3><p>I do a lot of work in the cloud so moving my development environment there makes sense for some things. In addition, it makes my development environment very consistent which is important for things like benchmarking as I can reproduce the exact hardware, control bandwidth, and in general, have more precise control of my test box.</p><p>This article is mostly just to show how I create a quick and dirty development box in GCP since I was asked this question. I like the command line so there&#8217;s nothing fancy about this; no IDE installs, just a simple how-to on creating a VM that runs a container with the tools you choose. If you want to make it a bit more complex, feel free to add as many bells and whistles as you like.</p><h3>First..a Dockerfile</h3><p>As a starting point, for various reasons, I use podman instead of Docker, so I won&#8217;t be using Docker as the authenticating client for GCP, but more on that later. First I need to build a docker image so I have a <code>Dockerfile</code> that looks like this:</p><pre><code>FROM rust

ARG USERNAME
ARG UID
ARG UGROUP
ARG GECOS

RUN apt-get update -y &amp;&amp; apt-get install -y --no-install-recommends \
zsh \
git \
curl \
neovim 

RUN groupadd -g ${UGROUP} ${USERNAME}
RUN adduser --force-badname --shell /usr/bin/zsh --uid ${UID} --gecos "${GECOS}" --gid ${UGROUP} ${USERNAME} --disabled-password
RUN chown -R ${USERNAME}: /home/${USERNAME}

USER ${USERNAME}
RUN cd ~/
RUN sh -c "$(curl -fsSL https://raw.github.com/robbyrussell/oh-my-zsh/master/tools/install.sh)"

EXPOSE 28578</code></pre><p>This is pretty straightforward. In this case, I&#8217;m using the official Rust image on Dockerhub but this can be replaced with whatever base image is appropriate, <em>e.g.</em>, <a href="https://hub.docker.com/_/python">python</a>, <a href="https://hub.docker.com/_/openjdk">OpenJDK</a>, <em>etc</em>.</p><p>I pass in some arguments to the build command to create the user and group based as well, as you will see shortly.</p><p>I then update apt-get and install my base preferred tools for the CLI, configure a group and my user, and switch to that user before installing <a href="https://ohmyz.sh/">OMZ</a>. I expose port 28578 because I happened to be working with Redis when I created this one.</p><h3>Prep for&nbsp;pushing</h3><p>Using podman I build the file and tag it like so</p><pre><code>podman build \
--build-arg USERNAME="$(id -un)" \
--build-arg UID="$(id -u)" \
--build-arg UGROUP="1000" \
--build-arg GECOS="$(id -F)"  -t us-east1-docker.pkg.dev/aiven-sa-demo/clays-docker-registry/cr-rust:1.0 .</code></pre><p>I pass in the build arguments based on my user account to the build command using the <code>id</code> command. I could have passed in the <code>UGROUP</code> value as well but chose to do this as a hard-coded value.</p><p>After that, I authenticate against the GCP Artifact Registry using this command: <code>gcloud auth print-access-token | podman login -u oauth2accesstoken&#8202;&#8212;&#8202;password-stdin us-east1-docker.pkg.dev/bogus-project/bogus-registry</code> where you would paste your registry information in place of the bogus one listed in the command clip. The tag needs to be in the form <code>LOCATION-docker.pkg.dev/PROJECT-ID/REPOSITORY/IMAGE_NAME</code> on your local image.</p><h3><strong>Push It Real&nbsp;Good</strong></h3><p>Pushing the image feels sort of anti-climatic if you were expecting anything clever. It works just as you&#8217;d expect it to; <code>podman push us-east1-docker.pkg.dev/aiven-sa-demo/clays-docker-registry/cr-rust:1.0</code> just like its docker counterpart.</p><h3>Actual deployment</h3><p>The most anit-climactic part has come. Now we need to deploy the container in a VM. To do that, you use the <code>create-with-container</code> command like so:</p><p><code>gcloud compute instances create-with-container redis-experiments \<br>&#8202;&#8212;&#8202;container-image us-east1-docker.pkg.dev/aiven-sa-demo/clays-docker-registry/cr-rust:1.0</code></p><p>That&#8217;s really all there is to it. You now have a development environment deployed to the cloud and containerized. There are clearly a lot of improvements that can be made, like adding configuration files for your tooling or putting all of this into a source code repository and automating the deployments for example, but that&#8217;s the gist of it.</p><h4>Final Note</h4><p>Some of you may have wondered why I passed a hard-coded value for the group ID to my build command. The steps I outlined above work great in most situations. However, the machine I wrote this article on happens to be a Mac. If I&#8217;d used the <code>&#8220;$(id -g)&#8221;</code> to get the group ID of my account, it would have returned 20. The low numbers that are used on Mac for user accounts would have conflicted with the group IDs used by Linux so I passed a semi-arbitrary high number.</p><p>Just to make it more fun, if you&#8217;re on a newer Mac using ARM architecture, you won&#8217;t be able to create a container from an image built on your Mac because of the architecture differences. Unless you force the VM type to be compatible you&#8217;ll need to move the build process itself to the cloud or find a non-ARM machine to create the image.</p><p>If you made it this far, give me a clap or a follow to let me know if you found the article useful, and comment below if you have any questions, observations, or find any issues!</p>]]></content:encoded></item><item><title><![CDATA[Windows OS Embraces Rust]]></title><description><![CDATA[Will it last or will the recent behavior of the Rust Foundation scare MS away?]]></description><link>https://svfearless.substack.com/p/windows-os-embraces-rust-2a2b86ed586f</link><guid isPermaLink="false">https://svfearless.substack.com/p/windows-os-embraces-rust-2a2b86ed586f</guid><dc:creator><![CDATA[SV Fearless]]></dc:creator><pubDate>Sat, 06 May 2023 15:37:43 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/b4301f83-0b0e-4ab3-9071-505d43a5da59_800x533.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h4>Will it last or will the recent behavior of the Rust Foundation scare MS&nbsp;away?</h4><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!i6Jo!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0637a7b9-044c-4bb8-af71-fd5fbe774029_800x533.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!i6Jo!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0637a7b9-044c-4bb8-af71-fd5fbe774029_800x533.jpeg 424w, https://substackcdn.com/image/fetch/$s_!i6Jo!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0637a7b9-044c-4bb8-af71-fd5fbe774029_800x533.jpeg 848w, https://substackcdn.com/image/fetch/$s_!i6Jo!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0637a7b9-044c-4bb8-af71-fd5fbe774029_800x533.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!i6Jo!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0637a7b9-044c-4bb8-af71-fd5fbe774029_800x533.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!i6Jo!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0637a7b9-044c-4bb8-af71-fd5fbe774029_800x533.jpeg" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/0637a7b9-044c-4bb8-af71-fd5fbe774029_800x533.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:null,&quot;width&quot;:null,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!i6Jo!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0637a7b9-044c-4bb8-af71-fd5fbe774029_800x533.jpeg 424w, https://substackcdn.com/image/fetch/$s_!i6Jo!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0637a7b9-044c-4bb8-af71-fd5fbe774029_800x533.jpeg 848w, https://substackcdn.com/image/fetch/$s_!i6Jo!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0637a7b9-044c-4bb8-af71-fd5fbe774029_800x533.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!i6Jo!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0637a7b9-044c-4bb8-af71-fd5fbe774029_800x533.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div></div></div></a><figcaption class="image-caption">Photo by <a href="https://unsplash.com/@freestocks?utm_source=medium&amp;utm_medium=referral">freestocks</a> on&nbsp;<a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure></div><h4>Windows Loves&nbsp;Rust</h4><p>At BlueHat IL 2023, in Tel Aviv on March 29-30, David Weston, the director of OS Security for Windows, <a href="https://www.youtube.com/watch?v=8T6ClX-y2AE&amp;ab_channel=MicrosoftIsraelR%26DCenter">announced</a> that Windows would be booting with Rust in the kernel soon, &#8220;<em>in probably the next several weeks or months</em>&#8221;. That&#8217;s pretty exciting to Rust fans out there.</p><p>It&#8217;s no secret that Windows has been experimenting with Rust. The love affair started <a href="https://www.zdnet.com/article/microsofts-rust-experiments-are-going-well-but-some-features-are-missing/">years ago</a>. For those of you who don&#8217;t know, Rust is a programming language designed with memory safety as one of its primary goals.</p><p>It achieves this through a combination of approaches. In simplified terms:</p><ol><li><p>Architecturally&#8202;&#8212;&#8202;the language uses a concept of memory ownership where any allocated memory is owned by a single variable (but can be borrowed as read-only).</p></li><li><p>Borrow checker&#8202;&#8212;&#8202;a component that enforces the ownership model outlined above, ensuring that no memory is accessed after deallocation while also ensuring that there are no data races where multiple threads attempt to access the same memory concurrently (not general race conditions).</p></li><li><p>Deterministic lifetimes&#8202;&#8212;&#8202;the lifetime of a variable is known at compile-time. This allows the borrow checker to enforce ownership and borrowing rules and ensures that as soon as allocated memory is no longer needed, it can be automatically freed.</p></li><li><p>Immutable&#8202;&#8212;&#8202;by default, all variables are immutable helping to prevent inadvertent issues with memory safety.</p></li></ol><p>According to MS memory safety accounted for <a href="https://www.zdnet.com/article/microsoft-70-percent-of-all-security-bugs-are-memory-safety-issues/">more than 70%</a> of the security-related updates released over the last decade plus. While MS is never going to completely rewrite 40-plus years of one of the most complex code bases in the world, it does make sense that it would begin experimenting with replacing especially vulnerable code given that Rust was created in part to address this very issue.</p><h4>It&#8217;s already&nbsp;begun</h4><p>The first port they&#8217;ve introduced is to deprecate <em>DWrite </em>(DirectWrite), part of Windows font parsing code, in favor of <em>DWiteCore</em>. <em>DWriteCore</em> is a cross-platform version of DWrite written in Rust that is now the recipient of all new feature development this is currently in use. They&#8217;ve also implemented a portion of <em>Win32k</em> GDI by implementing the <em>Region</em> data type, a core component of the Windows kernel responsible for window management. This component is currently shipping but disabled behind a feature flag, which is due to be removed for insider previews &#8220;shortly&#8221;.</p><h4>So what&#8217;s the problem&nbsp;then?</h4><p>All of the above is absolutely fantastic news for fans of security, Rust, and Windows OS. The potential issue is the <a href="https://foundation.rust-lang.org/">Rust Foundation</a> itself.</p><p>On April 6th, just a week after the announcements at the conference in Israel, the Rust Foundation released a new draft of its trademark policy and invited comments via a <a href="https://docs.google.com/forms/d/e/1FAIpQLSdaM4pdWFsLJ8GHIUFIhepuq0lfTg_b0mJ-hvwPdHa4UTRaAg/viewform">Google form</a> giving a deadline of April 16th. Because it's in a Google form the comments aren&#8217;t publicly visible, but developers took to any and all social media to make their concerns known. And holy smoke, were those concerns voiced. People came out of every corner of the internet to comment, <a href="https://www.youtube.com/watch?v=gutR_LNoZw0&amp;ab_channel=ThePrimeagen">YouTube</a>, <a href="https://lunduke.substack.com/p/the-rust-foundation-goes-to-war-against">SubStack</a>, <a href="https://www.reddit.com/r/rust/comments/12e7tdb/rust_trademark_policy_feedback_form/">Reddit</a>, <a href="https://twitter.com/rustlang/status/1646257904511492103">Twitter</a>, and basically every other forum you can think of. It even reached the point where the Rust community created a <a href="https://crablang.org/">fork</a> of the language.</p><p>For the sake of brevity(-ish), the highlight is this; after a year of deliberation, the Foundation published a final draft of a new trademark policy which essentially states that you cannot use the name &#8220;Rust&#8221; or use the Rust Logo, for any kind of profit or gain, register a domain name without explicit permission from the Rust foundation.</p><p>That&#8217;s right, you can&#8217;t use the name or logo at all without explicit permission in a domain name (for profit or not), or in anything that might make a profit or gain. Anywhere. For any reason. Have a domain name that uses the word &#8220;rust&#8221; in reference to the language? Denied. Your YouTube channel about the Rust language has the word Rust in it? Denied. You want to make a t-shirt promoting rust by using its logo? Nope. Have a non-profit website to teach Rust that has a domain name with the word rust in it? Not anymore.</p><p>And for more fun, this was also part of the new policy:<br>&#8220;<em>Using the [word &#8220;Rust&#8221;] in the name of a tool for use in the Rust toolchain, a software program written in the Rust language, or a software program compatible with Rust software, will most likely require a license.</em>&#8221;</p><p>If you&#8217;re wondering what that means, it means that any software that&#8217;s any part of the Rust ecosystem is banned from using the word rust. That&#8217;s IDE plugins, libraries like <em>openssl-rust</em>, <em>rust-mysql</em>, and every other tool, crate, or library you can think of.</p><h4>What does this&nbsp;mean?</h4><p>Maybe nothing. On the one hand, MS made the announcement at BlueHat IL 2023 prior to the release of the draft trademark policy and I can see this kind of insanely restrictive policy making them extremely nervous about using the language, certainly about promoting their use of it.</p><p>On the other hand, MS is one of the founding members of the Rust Foundation, along with Amazon (who also is promoting the adoption of Rust), among others. It seems like a big pill to swallow that MS wasn&#8217;t aware of this insanely restrictive policy but since they&#8217;re one of the founding members one would assume that they can probably get explicit permission to do whatever they want with the name and logo.</p><p>The day after the close of comments the Foundation issued an <a href="https://foundation.rust-lang.org/news/rust-trademark-policy-draft-revision-next-steps/">apology</a> for its lack of transparency in the process, and that they will carefully consider the feedback.</p><h4>So what&#8217;s my&nbsp;point?</h4><p>I don&#8217;t think MS is going to have any issue with the trademark policy because they are a founding member and I can&#8217;t imagine them not being able to get permission for whatever they want to do regarding the name and logo. What I do see are three possible positions that MS could occupy regarding the trademark policy.</p><ol><li><p>They had no idea what was going on because they didn&#8217;t care or were ignorant. We can throw the ignorant option out immediately by looking at the <a href="https://foundation.rust-lang.org/about/">board of directors</a>.</p></li><li><p>They actively support the new policy</p></li><li><p>It&#8217;s a genuine mistake that the wording was so insanely restrictive and it will be a nothing burger when it's all said and done.</p></li></ol><p>In the first case, they don&#8217;t care, then it's just a community problem and MS may or may not have to deal with some bad press for not pushing back.</p><p>In the second case, you really should start asking yourself, &#8220;Why would MS support this policy?&#8221; But let&#8217;s not stop there, that&#8217;s a valid question to ask of Amazon, Google, Meta, and the other founding members. What&#8217;s in it for them if this trademark policy stands? Who benefits from restricting people from using the word &#8220;Rust&#8221;, or the Rust logo?</p><p>In the third case, this will be something we all laugh about someday. This option I find about as likely as the board members being ignorant of the new policy. First, there was nothing to fix with the original trademark policy and even assuming it need to be refined, there was definitely no reason to suddenly roast everyone over a roaring fire. Second, the corporate member directors are far from being idiots or oblivious, and they are well equipped to understand the community's reaction. The fact that all of them missed out on what the reaction was going to be seems another tough pill to swallow.</p><p>This brings me back to the second case. Who benefits and how? I don&#8217;t see any clear answer to that but I&#8217;ll be very interested to see what the updated policy looks like when it comes out.</p><p>If you made it this far please clap, or subscribe so I&#8217;ll know you found the article interesting. If you want to read more like this or are interested in anything in particular, please comment and let me know. As always, thanks for reading!</p>]]></content:encoded></item><item><title><![CDATA[Working with Text on the CLI]]></title><description><![CDATA[When learning CLI tools, focus on the tasks they perform not the tools themselves.]]></description><link>https://svfearless.substack.com/p/working-with-text-on-the-cli-d798bd32b78b</link><guid isPermaLink="false">https://svfearless.substack.com/p/working-with-text-on-the-cli-d798bd32b78b</guid><dc:creator><![CDATA[SV Fearless]]></dc:creator><pubDate>Sat, 31 Dec 2022 23:58:39 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/ca01c0c8-25d4-4be2-8057-55773b87138b_800x600.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!dQBA!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcdbaddaa-676a-41ed-b97a-a0d45fa99024_800x600.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!dQBA!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcdbaddaa-676a-41ed-b97a-a0d45fa99024_800x600.jpeg 424w, https://substackcdn.com/image/fetch/$s_!dQBA!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcdbaddaa-676a-41ed-b97a-a0d45fa99024_800x600.jpeg 848w, https://substackcdn.com/image/fetch/$s_!dQBA!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcdbaddaa-676a-41ed-b97a-a0d45fa99024_800x600.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!dQBA!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcdbaddaa-676a-41ed-b97a-a0d45fa99024_800x600.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!dQBA!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcdbaddaa-676a-41ed-b97a-a0d45fa99024_800x600.jpeg" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/cdbaddaa-676a-41ed-b97a-a0d45fa99024_800x600.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:null,&quot;width&quot;:null,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Unreadable screen of highlighted and unformatted code on the command line&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Unreadable screen of highlighted and unformatted code on the command line" title="Unreadable screen of highlighted and unformatted code on the command line" srcset="https://substackcdn.com/image/fetch/$s_!dQBA!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcdbaddaa-676a-41ed-b97a-a0d45fa99024_800x600.jpeg 424w, https://substackcdn.com/image/fetch/$s_!dQBA!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcdbaddaa-676a-41ed-b97a-a0d45fa99024_800x600.jpeg 848w, https://substackcdn.com/image/fetch/$s_!dQBA!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcdbaddaa-676a-41ed-b97a-a0d45fa99024_800x600.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!dQBA!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcdbaddaa-676a-41ed-b97a-a0d45fa99024_800x600.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div></div></div></a><figcaption class="image-caption">Photo by <a href="https://unsplash.com/@maxchen2k?utm_source=medium&amp;utm_medium=referral">Max Chen</a> on&nbsp;<a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure></div><h3>Intro</h3><p>Working with the CLI tends to be a very opinionated environment. Everyone has their own preferred tools. Everyone&#8217;s workflow is slightly different depending on personal preferences, the tasks they regularly perform, and even workplace culture. But regardless of preference, culture, or daily tasks, some things remain common.</p><h3>Overview of CLI&nbsp;Usage</h3><p>When using the CLI you&#8217;re always working with text and you always want it to be as convenient as possible. There are also common scenarios that you&#8217;re dealing with on a regular basis. Broadly you can lump these scenarios into working with formatted documents (code, LaTeX, JSON, <em>etc.</em>), working with logs, and interacting with the shell. There is admittedly overlap between the first two scenarios, <em>i.e.</em>, logs are certainly formatted, and some are written in JSON, but we interact with logs differently than we interact with other formatted documents; with logs, we&#8217;re <em>extracting</em> information and they&#8217;re the last thing we want to edit, with formatted documents, editing is almost always involved.</p><p>When we interact with the CLI we&#8217;re dealing with a serial execution environment, which involves blocking actions. If you execute something like <code>tail</code> then unless you do something to interfere, that shell interaction is now blocked. The same is basically true when you run any executable. Until that executable completes, you can&#8217;t interact with that shell session. This means that to work effectively you really need a way to work with multiple interactive shells simultaneously, be that through opening multiple terminals, using an emulator that provides a tabs feature, tmux/screen, <em>etc.</em></p><h3>Working with a Blocking Interface</h3><p>My preferred tool for managing multiple interactive shells is <a href="https://github.com/tmux/tmux">tmux</a>. This is something that gets pretty opinionated pretty quickly so let me outline my reasons for using it.</p><p>First, while tmux is a pretty new tool in terms of the *nix tooling, its popularity has grown enough over the last 15 years that if it doesn&#8217;t come pre-installed, then it&#8217;s easy to install with whatever package manager is available. Second, while it&#8217;s extremely customizable, it&#8217;s also usable out of the box. A vanilla install gives me the ability to manage multiple shell sessions, group screens together, pick up previous shell sessions where I left off, and much more.</p><p>I prefer this over using tab-capable emulators because I&#8217;ll almost always have access to it, whereas customizing iTerm2 to meet my needs means my workflow will be awkward on anything other than a Mac.</p><p>I also don&#8217;t rely on creating a new terminal for each session I need because it&#8217;s annoying to organize, and if I don&#8217;t have a GUI, I can&#8217;t create another terminal anyway. Think, logging into a Linux VM on a cloud provider like GCP or AWS. You can either use a browser to <code>ssh</code> into a terminal session, or you can just <code>ssh</code> from your local machine into a terminal session. If you don&#8217;t have some kind of multiplexor you&#8217;re stuck creating a new emulator for each session you need.</p><p>Regardless of how you solve the problem of working with a blocking UI, be it <a href="https://github.com/tmux/tmux">tmux</a>, <a href="https://iterm2.com/">iTerm2</a>, <a href="https://www.gnu.org/software/screen/">screen</a>, or something else entirely, it&#8217;s something you&#8217;re going to contend with.</p><h3>Interacting with the&nbsp;Shell</h3><p>For the purposes of this article, I&#8217;m not going into which shell you should select as, again, this is a very opinionated topic. My shell of choice has changed over the years as my workflow, needs, and wants have evolved. Also, a good argument can be made for using any shell I can think of with the exceptions of the Thompson or Mashey shells, both called <code>sh</code>. Unless you&#8217;re working with A<a href="https://en.wikipedia.org/wiki/Ancient_UNIX">ncient Unix</a>, you&#8217;ll never encounter them.</p><p>The most common and useful tools used while interacting with the command line follow the <a href="https://en.wikipedia.org/wiki/Unix_philosophy">UNIX philosophy</a>. Love it or hate it, what&#8217;s relevant to this discussion is that any tooling worth its salt will assume that its output will be the input to another tool that it knows nothing about. In other words, the vast majority of tools will use pipes to string multiple commands together to do new things.</p><p>For example, if I want to see all of the lines containing the word error, I can do <code>tail file.log | grep error</code>and if the logfile is live, I can do <code>tail -f file.log | grep error</code> which will only display log lines if they contain the word error.</p><p>The examples above are clearly simplistic and maybe a tad contrived, but the point is that you will have a plethora of tools at your disposal and they can almost all be strung together to accomplish far more than any single one could. They&#8217;re legos, learn how to stack them together and you can save yourself a lot of time and pain. Let&#8217;s see an example that's a little more &#8220;real-world&#8221;.</p><p>If you work with REST APIs at all, you&#8217;re probably familiar with <code>cURL</code> which is used to send an HTTP request and show the response. A common pattern for a PUT command is to return a JSON payload. Unformatted JSON is ugly and difficult to read. We also want to play with OpenAI because that&#8217;s all the rage at the moment. First let's send the curl command so that we can verify the response is what we expect:</p><pre><code>curl https://api.openai.com/v1/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_KEY" \
-d '{"model": "text-davinci-003", "prompt": "Say this is a test", "temperature": 0, "max_tokens": 7}'</code></pre><p>This returned this ugly piece of JSON:</p><pre><code>{"id":"cmpl-6SXlLQAk9smbBI2TuNjNCEdKH6FHW","object":"text_completion","created":1672260987,"model":"text-davinci-003","choices":[{"text":"\n\nThis is indeed a test","index":0,"logprobs":null,"finish_reason":"length"}],"usage":{"prompt_tokens":5,"completion_tokens":7,"total_tokens":12}}</code></pre><p>Let&#8217;s pipe this to <code>jq</code> to pretty-print it:</p><pre><code>curl https://api.openai.com/v1/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer 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" \
-d '{"model": "text-davinci-003", "prompt": "Say this is a test", "temperature": 0, "max_tokens": 7}' | jq '.'</code></pre><p>Which yields this much more readable version seen below:</p><pre><code>% Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   383  100   287  100    96    318    106 --:--:-- --:--:-- --:--:--   425
{
  "id": "cmpl-6SXtWoZY29vj2hIK2PqdsvIe1jUoc",
  "object": "text_completion",
  "created": 1672261494,
  "model": "text-davinci-003",
  "choices": [
    {
      "text": "\n\nThis is indeed a test",
      "index": 0,
      "logprobs": null,
      "finish_reason": "length"
    }
  ],
  "usage": {
    "prompt_tokens": 5,
    "completion_tokens": 7,
    "total_tokens": 12
  }
}</code></pre><p>By adding one simple tool, we&#8217;ve made it possible to read the returned JSON. In addition to pipes, redirections are another tool that makes your life easier. Redirection allows you to send output from STDOUT, which by default is the screen, to a file. A single redirection symbol ( <code>&gt;</code>) will create a new file if one doesn&#8217;t exist, or overwrite an existing file if it does. Using two redirection symbols, <code>&gt;&gt;</code>will append to an existing file rather than overwrite. Now I can simply add <code>&gt;&gt; sample_response.json</code> after the <code>jq</code> command and the JSON results are in a file ready to use in an editor.</p><p>There are more tools out there than you can count, all of which can be used as building blocks to create results that are only limited in usefulness and power by your willingness to understand and use them.</p><h3>Working with&nbsp;Logs</h3><p>When it comes to working with logs, we&#8217;re usually trying to locate a specific part(s) of the log in some manner, <em>e.g.</em>, time a period, a specific message type, a specific identifier (customer number, service ID, <em>etc.</em>), or some combination factors like the ones listed above.</p><p>The other common possibility is that we may be aggregating logs to discover trends. For example, maybe we&#8217;re looking for unauthorized login attempts over the past hour or we want to know how many connection timeouts have occurred on a particular service over the past two weeks.</p><p>Now, analytics and monitoring can obviously be done far more efficiently in a production environment if we use tools and services specifically built for those purposes such as Grafana, Prometheus, Datadog, <em>etc.</em>, but there are occasions when it&#8217;s just easier to do investigations with the raw logs themselves with ad-hoc tools. Sometimes, raw logs are the only source of the data you need for your investigation.</p><p>Logs (at least good ones) will have a consistent format. Many logs are modeled off of the Linux log format which looks like this by default:</p><p><strong>priority, timestamp, hostname, service name, message</strong></p><p>Below is a snippet of the log output by <code>sshd</code> when a login attempt occurs and the user doesn&#8217;t exist. Note that in this case, the priority is not logged. As an aside, you&#8217;ll find that many logs omit the priority from the log messages. You can find more detailed information <a href="https://www.loggly.com/ultimate-guide/linux-logging-basics/">here</a>.</p><p><code>Jul 7 10:51:24 chaves sshd[19537]: Invalid user admin from spongebob.lab.ossec.net</code></p><p>There are a number of common CLI tools that we can use to make studying logs easy.</p><h4>Grep:</h4><p>I think the first tool that comes to mind for most people is <code>grep</code>. Grep provides flexible and powerful search capabilities. The key required to unlock that power is Regular Expressions or RegEx. Grep's power comes from its ability to apply regex patterns to its search. I would also recommend using the <code>-E</code> flag instead of the <code>-e</code> to prevent a lot of extra backslash escapes which will be more error-prone as well as more difficult to read. As an example, the command below will search the syslog file for any entry that contains the text <em>system76</em> regardless of what comes before or after it, as long as the text is followed by a colon and white space (&#8220;: &#8220;). It will also print the line number where it found the reference.</p><pre><code>grep -n -E ".*system76.*: .*" /var/log/syslog </code></pre><p>Full details on the regex it understands and all command flags can be found <a href="https://man7.org/linux/man-pages/man1/grep.1.html">here</a>.</p><h4>Head and&nbsp;Tail:</h4><p>If you just need a quick peek at the start or end of the log, say, to make sure that it started or stopped execution without errors, then you&#8217;ll find <code>head</code> and <code>tail</code> useful commands. They simply display the first N or last N lines of a file. There&#8217;s a convenient flag that instructs <code>tail</code> to continue outputting any data appended to the file as it grows. This is extremely useful in a development environment. You can take a look at the man pages for them to get the full details.</p><h4>Awk and&nbsp;Cut:</h4><p>Since all useful logs follow some kind of consistent format we can take advantage of <code>awk</code> to parse them for specific information. While Awk is its own text-processing language, and far beyond the scope of this article, it can be a very useful tool for working with any document that has consistent formatting, not just logs. Awk is another tool that requires at least a minimal working knowledge of regex to be used effectively. Going back to our previous log example:</p><p><code>Jul 7 10:51:24 chaves sshd[19537]: Invalid user admin from spongebob.lab.ossec.net</code></p><p>The following command will locate the line using regex, and print the username and hostname that originated the failed request. Note that the comma between the 2 fields is required to separate the output:</p><pre><code>awk "/.*Invalid user.*/ { print $8, $10}" /var/log/auth.log</code></pre><p>The <code>cut</code> utility is used to select delimited text and print it to standard output. Note that despite its name, it does not alter the source file's text.</p><p>Using <code>cut</code> you can select text by character or byte position (or range), and by field using an arbitrary delimiter. As an example, we want a list of all accounts currently on the machine for a security audit. We know that we can find a definitive list of accounts for the machine in <code>/etc/passwd</code>. Let&#8217;s say our file looks like this:</p><pre><code>root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin</code></pre><p>If we want to create a file with a list of all of the accounts created on the machine we can do something like this which selects the first field from the file using the colon (:) as the delimiter, and writes the result into the file called account-list.txt:</p><pre><code>cut -d ':' -f 1 /etc/passwd &gt; account-list.txt</code></pre><p>The result would be a file containing this:</p><pre><code>root
daemon
bin
sys
sync
games
man
lp
mail
news
uucp
proxy</code></pre><p>There&#8217;s a lot of information out there for both of these commands but here&#8217;s the man page for both <a href="https://man7.org/linux/man-pages/man1/awk.1p.html">awk</a> and <a href="https://man7.org/linux/man-pages/man1/cut.1.html">cut</a> to get you started.</p><h3>Working with Formatted Documents</h3><p>I&#8217;ve saved this for last because it&#8217;s both a very simple and very complex topic. While all of the tools previously mentioned can certainly be used to work with formatted documents, there are tools designed specifically for editing text.</p><h4>Sed:</h4><p>Sed is a stream editor that allows you to perform insert, delete, and substitution operations based on selected text. A quick example using the input from <code>/etc/passwd</code> used in the <code>cut</code> command above might be to flag all service accounts since we know that all accounts that have <code>/user/sbin/nologin</code> instead of a startup shell are going to be service accounts.</p><p>A word of caution here; we&#8217;re now starting to get into editing, which can be dangerous if you&#8217;re talking about system files like <code>/etc/passwd</code> so I recommend always using the <em>-i</em> flag when using <code>sed</code> on system documents. the <em>-i</em> flag allows you to specify the creation of a backup before any editing occurs. Even better, don&#8217;t edit system files directly, just create a duplicate and perform your edits on that. Then make a backup of the original anyway in case something breaks when you start using the new one.</p><p>The example below will create a backup of the original file called <code>passwd.backup</code>, read the contents of the original file, replace everything between the first and last delimiters (:) with a space, and replace the <code>/usr/sbin/nologin</code> with the text <em>Service account</em>.</p><pre><code>sed -i'.backup' -e 's/:.*:/ /g' -e 's/\/usr\/sbin\/nologin/ Service account/g' cut-test.txt</code></pre><p>After this command executes the file will look like this:</p><pre><code>root /bin/bash
daemon  Service account
bin  Service account
sys  Service account
sync /bin/sync
games  Service account
man  Service account
lp  Service account
mail  Service account
news  Service account
uucp  Service account
proxy  Service account</code></pre><p>As you can see, while I was expecting to see the login shell of any non-service account users, I did miss the <em>sync</em> account, which..well, it&#8217;s an artifact of an earlier era (pre-1993) that was used to safely shutdown systems without needing to know the root password. I don&#8217;t know of any modern systems that still use it. If I&#8217;m wrong, please let me know in the comments. Always happy to learn something new!</p><p>Also, note that if you want to take full advantage of <code>sed</code> you&#8217;ll also want to be comfortable with basic regular expressions.</p><h4>JQ:</h4><p>We already briefly met <code>jq</code> already in the, <em>Interacting with the Shell,</em> section. If you work with JSON chances are you&#8217;re already familiar with <code>jq</code>. It&#8217;s a utility for parsing and formatting JSON documents. The&nbsp;<code>.</code> operator by itself represents the root of the document. You can use the&nbsp;<code>.</code> operator to reference the children of the root document. Given a JSON document called <code>pets.json</code> which looks like this:</p><pre><code>{
  "pets": {
    "cat": {
      "name": "Mr Fuzzy",
      "color": "calico",
      "age": 8
    },
    "dog": {
      "name": "Sir Barky",
      "color": "blonde",
      "age": 12
    }
  }
}</code></pre><p>Then this command, <code>jq&nbsp;. pets.json</code>, will render the entire document in a prettyprinted format which would look identical to the raw document above. This command, <code>jq&nbsp;.pets pets.json</code>, will render the <em>pets</em> field and the child fields of <em>cat</em> and <em>dog</em>. This command, <code>jq&nbsp;.pets.dog</code>, will render just the <em>dog</em> field as seen below:</p><pre><code>{
  "name": "Sir Barky",
  "color": "blonde",
  "age": 12
}</code></pre><p>It also allows you to work with JSON arrays (including array slices), built-in functions for filtering, mapping values, transforming documents, and, you guessed it, regular expressions can be used to match with the <code>test</code> function to determine if an input matches the expressions criteria.</p><p>Again, the entire topic has is too large for this article and there are already tons of articles that dive into <code>jq</code> usage. Here&#8217;s a <a href="https://stedolan.github.io/jq/">link</a> to get you started.</p><p>And since this could continue to go on forever, I&#8217;m going to end it with both the easiest and most difficult tools to recommend. Easy because it&#8217;s a no-brainer that you&#8217;re going to need it, and difficult because opinions are so strong as to be toxic on occasion. Terminal based editors</p><h4>Terminal Editors:</h4><p>Terminal editors a very opinionated topic. Everyone has a go-to favorite. When it comes to editors for the terminal almost everyone thinks of either vim or emacs. While those 2 dominate the terminal editor space, there are a surprising number of editors available for the terminal. Most of them I have never used, at least not in a very long time, but outside of vim and emacs, there are a few worth noting.</p><p>Nano&#8202;&#8212;&#8202;Nano has been part of the GNU ecosystem since 2001. It was created as a free replacement for the Pico editor, which was part of Pine, which was THE email client for UNIX systems at the time. Pico was the editor embedded in Pine used to create and edit emails. Its biggest advantage in my view is that it&#8217;s common to find it on many systems, it&#8217;s small, and it&#8217;s simple. Like a kinder vi.</p><p>Mcedit- Midnight Command is a fairly popular terminal file manager (well, it used to be, but I really don&#8217;t know anymore tbh). Mcedit is the built-in editor. The only reason to bother with this one is if you use MC. I definitely don&#8217;t think it&#8217;s worth installing MC to be able to use Mcedit but if you&#8217;re already a user of MC I think people sometimes forget it&#8217;s there.</p><p>There are <em>many</em> more alternatives out there, but I place a heavy weight on ubiquity and skill transference. If I put my time into learning a tool well, I want to make sure that the tool is available in as many environments as possible and that if possible I can transfer those skills to other tools.</p><h4>Vim and&nbsp;Emacs:</h4><p>The holy wars between vim and emacs are well documented as are the editors themselves, with countless tutorials available so I won&#8217;t go over them here.</p><p>The only comments I have to add about them are that they both meet my criteria of ubiquity and skill transference. They can both be found on virtually any platform by default, and the skills from both have some transference to other tools.</p><p>Regarding skill transference, all shells that I&#8217;ve used in the past 20 years have keybindings for both emacs and vi. This means that I can edit on the interactive shell using the same keystrokes I use for my editor. I can also use emacs or vim key bindings on my favorite GUI editors, <em>e.g.</em>, all JetBrains editors, Eclipse, VS Code, etc. If only Word or Google Docs would do that.</p><p>With regards to vi/vim/nvim specifically, if you can use vim or neovim, you can use vi despite its age. You might miss the fancy bells and whistles the others give you but you&#8217;ll be fine. This is why people refer to them as if they were a singular item. I am starting to separate out neovim in my head from vim. With the announcement that vim will be re-writing its vimscript engine from scratch, rather than follow in neovim&#8217;s footsteps and use an already existing language to create plugins and custom scripts, I think it&#8217;s warranted. While we&#8217;ll always see backward compatibility between nvim and vim, at least for the foreseeable future, I think vim made a mistake and that it will suffer for it. Why reinvent the wheel and spend all the time, effort, and tears creating an entirely new engine from scratch, debugging it, etc., instead of just providing a means for an already existing language that is well-known, well-documented, and well-loved already?</p><p>Personally, I use neovim. My choice comes from two simple facts.</p><ul><li><p>It&#8217;s lighter-weight</p></li><li><p>My first experiences working with emacs were frustrating and the keybindings hurt my hands after extended use.</p></li><li><p>Bonus fact: When I first started trying emacs there were a <em>lot</em> of different versions, and compatibility between them was not guaranteed. That may have contributed to some of my frustrations.</p></li></ul><p>As always, whatever tools you choose to devote your time to learning, do it deliberately and do it well.</p>]]></content:encoded></item><item><title><![CDATA[Command Line Wizardry]]></title><description><![CDATA[There&#8217;s a reason the programmer hunched in front of text-covered screen endures.]]></description><link>https://svfearless.substack.com/p/command-line-wizardry-9839254c57af</link><guid isPermaLink="false">https://svfearless.substack.com/p/command-line-wizardry-9839254c57af</guid><dc:creator><![CDATA[SV Fearless]]></dc:creator><pubDate>Wed, 30 Nov 2022 18:09:25 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/3f66a6e3-ca6f-44ce-a085-9445cfecbe68_800x450.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!o7R_!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7519aa8a-3822-4034-8d14-2cdc0ff8f1d2_800x450.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!o7R_!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7519aa8a-3822-4034-8d14-2cdc0ff8f1d2_800x450.jpeg 424w, https://substackcdn.com/image/fetch/$s_!o7R_!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7519aa8a-3822-4034-8d14-2cdc0ff8f1d2_800x450.jpeg 848w, https://substackcdn.com/image/fetch/$s_!o7R_!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7519aa8a-3822-4034-8d14-2cdc0ff8f1d2_800x450.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!o7R_!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7519aa8a-3822-4034-8d14-2cdc0ff8f1d2_800x450.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!o7R_!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7519aa8a-3822-4034-8d14-2cdc0ff8f1d2_800x450.jpeg" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/7519aa8a-3822-4034-8d14-2cdc0ff8f1d2_800x450.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:null,&quot;width&quot;:null,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!o7R_!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7519aa8a-3822-4034-8d14-2cdc0ff8f1d2_800x450.jpeg 424w, https://substackcdn.com/image/fetch/$s_!o7R_!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7519aa8a-3822-4034-8d14-2cdc0ff8f1d2_800x450.jpeg 848w, https://substackcdn.com/image/fetch/$s_!o7R_!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7519aa8a-3822-4034-8d14-2cdc0ff8f1d2_800x450.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!o7R_!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7519aa8a-3822-4034-8d14-2cdc0ff8f1d2_800x450.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div></div></div></a><figcaption class="image-caption">Photo by <a href="https://unsplash.com/es/@arget?utm_source=medium&amp;utm_medium=referral">Arget</a> on&nbsp;<a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure></div><p>There&#8217;s a reason the stereotype exists</p><p>There&#8217;s a reason that the image of the programmer sitting in front of a terminal emulator using arcane commands is an enduring stereotype. I&#8217;ve written before how the command line is inescapable if you&#8217;re a software developer. In almost any role that requires you to deal with the technical end of the software industry knowing how to use the command line is a big help.</p><p>There is nothing as nerdy-chic as being able to use command-line tools effectively. The bonus is that not only are CLI tools easily portable between environments, from cloud VMs to OS, but they&#8217;re also easily automatable. The biggest barrier to the efficient use of these tools usually comes down to people being intimidated or frustrated by them, both of which can be solved by taking the time to learn them. I won&#8217;t get into a big list of specific tools, there are all kinds of articles that already do that. Instead, I&#8217;d like to focus on what <em>types</em> of tools you really want to have under your belt. Most of these are probably self-evident but I try not to make assumptions.</p><h3>The Terminal</h3><p>The most fundamental part of using the CLI is the terminal emulator, usually just called a terminal. This is your gateway to the CLI. Regardless of what terminal you decide to use, you&#8217;ll likely be using it for a very long time, so try to be deliberate about your choice. Terminals can be as simple or complex as you like. They can offer things like tabs to keep track of multiple sessions, transparency to look nice, and built-in functions like search. I personally prefer a terminal that looks clean. I don&#8217;t want tabs or most of the other fancy stuff. All of those things can be achieved inside the CLI itself.</p><h3>The Shell</h3><p>The shell is the next most basic item that everything else is built on top of. Again, which shell you use is fine as long as you know why you&#8217;re using it. Be deliberate. The shell is going to be the means by which you interact with your tools, filesystem, and everything else. If you don&#8217;t have the time or desire to dig into all of the various shells with their specific pros and cons, my advice is to stick with bash. Bash is ubiquitous being the default shell for most Linux distros, and it&#8217;s available for practically every OS out there.</p><h3>The Package&nbsp;Manager</h3><p>The package manager is an often overlooked part of the CLI ecosystem. You&#8217;re going to have less flexibility around package manager selection because your primary package manager will be dictated by the OS in most cases. While you don&#8217;t get to choose the primary package manager used by the OS, you do get more flexibility in choosing the one that you will use for installing your own tools and software. This is a good idea because it separates the package management of your OS from the package management of your personal tools. If you&#8217;re anything like most developers you&#8217;ll be regularly, if not constantly, installing, updating, removing, downgrading, and in general, creating a big mess in the filesystem. This way you aren&#8217;t accidentally screwing with the libraries that your OS depends on. Maybe it&#8217;s me but I&#8217;ve been bitten too often by adding or removing something that it turns out the OS has a dependency on and things get annoying. I will make this instance the exception to my rule about not recommending any specific tool because there aren&#8217;t a lot of OS-independent, portable options out there outside of <a href="https://brew.sh/">homebrew</a>. The good news is that it&#8217;s available for Mac, Linux, or Windows(using the Windows Subsystem for Linux).</p><h3>The Editor</h3><p>Now that you have a terminal, a shell, and you can install tools, you&#8217;re going to need to start editing things, <em>e.g.</em>, config files. I highly recommend finding a terminal-based editor because once you start seriously using the CLI you&#8217;re going to need it. Is it possible to edit locally with a GUI editor, load it onto my EC2 VM, and use it? Of course! That absolutely works. On the other hand, I would much rather simply open the file in situ, make my change, and move on without ever leaving the VM. I&#8217;ll leave the flame wars to someone else, I don&#8217;t care what editor you use, but I do recommend that it&#8217;s something commonly found on most systems, is powerful enough to meet your needs, and doesn&#8217;t take you out of the terminal. I also recommend that you learn it well as it makes your life much easier in the long run.</p><h3>Everything Else</h3><p>I spent a while trying to come up with more fundamental tools and finally came to a couple of conclusions.</p><ol><li><p>Everything after the editor depends on your personal workflow</p></li><li><p>Your personal workflow won&#8217;t develop without deliberate focus from you.</p></li></ol><h3>Everything after the editor depends on your personal&nbsp;workflow</h3><p>Any other tools that I would consider fundamental will only really apply to me. The tools you use and how you use them change depending on your experience, comfort level, and personal preferences. They will change over the course of your career, what you&#8217;re working on, and your evolving standards. At one point in my career, I would have declared a terminal multiplexer an indispensable fundamental tool. Then my specific work changed, my habits changed, and while I still use one, it&#8217;s not the must-have tool it once was. This can be said about many of the CLI tools that I&#8217;ve used over my career. Formatting tools, file managers, network tools, what you use and how you use it, all of it evolves and changes over time, driven by you and your own growth. The only thing I can say for certain is that no matter what I chose to learn, I never regretted investing the time to learn it and it&#8217;s always paid good dividends across my career.</p><h3>Your personal workflow won&#8217;t develop without deliberate focus from&nbsp;you</h3><p>That&#8217;s a misleading statement. It will grow and develop without deliberate focus from you. The difference is <em>how</em> it will develop. Without deliberate focus from you what will happen is that you&#8217;re leaving your professional growth to the roll of the dice. If you get lucky, you&#8217;ll stumble around and step into a pot of gold. If you aren&#8217;t, you&#8217;ll learn habits that will hamper you for years. The most likely scenario is that you&#8217;ll be a mediocre developer. And yes, this is true without the CLI focus. In my experience, the CLI is an amplifier for intentional learning. Six months of deliberate learning while using the CLI vs six months of flailing around on the CLI is the difference between taking thirty seconds to locate a file, and three seconds to locate a file.</p><h3>Closing Thoughts</h3><p>This may have been a little rambling but if you&#8217;ve made it this far, I appreciate your persistence. While writing this article, I realized that the biggest difference for e personally was deliberate focus and intent. Beyond using the CLI, or professional growth, being intentional in your pursuit of whatever it is you&#8217;re chasing is key. There have been times in my life when I lost that deliberate intent, in my work and my personal goals, and flailed about not really accomplishing much but not really losing ground. Just..stagnating. When I would wake up and notice I realized that I&#8217;d wasted a lot of my time getting nowhere. After regaining my focus I would accomplish more in a month than I had in the past six. Don&#8217;t be me. Keep your focus, be deliberate, and whatever you do, do it with intention.</p><p>If you made it this far, give me a clap or a follow so I know if you found this interesting or useful.</p>]]></content:encoded></item><item><title><![CDATA[Kafka Connect Made Convenient]]></title><description><![CDATA[Working with connectors can be easier with kcctl]]></description><link>https://svfearless.substack.com/p/kafka-connect-made-convenient-e9c94d8a48d6</link><guid isPermaLink="false">https://svfearless.substack.com/p/kafka-connect-made-convenient-e9c94d8a48d6</guid><dc:creator><![CDATA[SV Fearless]]></dc:creator><pubDate>Thu, 25 Aug 2022 22:13:54 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/4f766f18-cbc3-47c1-9108-5d140aa05e84_800x534.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Working with connectors can be easier with kcctl</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!T7rB!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55cdee21-80cb-48c1-b51d-ffdae542caa9_800x534.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!T7rB!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55cdee21-80cb-48c1-b51d-ffdae542caa9_800x534.jpeg 424w, https://substackcdn.com/image/fetch/$s_!T7rB!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55cdee21-80cb-48c1-b51d-ffdae542caa9_800x534.jpeg 848w, https://substackcdn.com/image/fetch/$s_!T7rB!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55cdee21-80cb-48c1-b51d-ffdae542caa9_800x534.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!T7rB!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55cdee21-80cb-48c1-b51d-ffdae542caa9_800x534.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!T7rB!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55cdee21-80cb-48c1-b51d-ffdae542caa9_800x534.jpeg" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/55cdee21-80cb-48c1-b51d-ffdae542caa9_800x534.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:null,&quot;width&quot;:null,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!T7rB!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55cdee21-80cb-48c1-b51d-ffdae542caa9_800x534.jpeg 424w, https://substackcdn.com/image/fetch/$s_!T7rB!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55cdee21-80cb-48c1-b51d-ffdae542caa9_800x534.jpeg 848w, https://substackcdn.com/image/fetch/$s_!T7rB!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55cdee21-80cb-48c1-b51d-ffdae542caa9_800x534.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!T7rB!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F55cdee21-80cb-48c1-b51d-ffdae542caa9_800x534.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div></div></div></a><figcaption class="image-caption">Photo by <a href="https://unsplash.com/@mbaumi?utm_source=medium&amp;utm_medium=referral">Mika Baumeister</a> on&nbsp;<a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure></div><p>This post assumes that you already have the knowledge to get a working Kafka environment, e.g., you have at least a working Kafka cluster with Kafka Connect, or you have a working service hosted somewhere. For simplicities sake, you can sign up for a free trial account at <a href="https://aiven.io/">Aiven</a>. Setting up a Kafka service there is as trivial as a <a href="https://developer.aiven.io/docs/products/kafka/getting-started.html">few mouse clicks</a>.</p><p>I have recently begun working with data pipelines using Kafka to move data between various sinks and sources. For example, you might use a MySQL DB as a source of data and use Kafka to propagate that data into S3 for long-term storage, sending Kafka metrics to Influx for use by operations in tracking the server's health. I&#8217;m sure you can come up with scenarios that would make sense to you personally without much help. All of these sources and sinks are most conveniently tied together using <a href="https://kafka.apache.org/documentation/#connect">Kafka Connect</a>.</p><p>Kafka Connect relies on a <a href="https://kafka.apache.org/documentation.html#connect_rest">REST API</a> for the configuration of connectors. I love REST API&#8217;s. They can be convenient to use, allow a clean separation of architectural boundaries, and are conveniently agnostic when it comes to interfacing. However, when we&#8217;re talking about ease of development, I personally don&#8217;t like them, primarily because I either have to construct URIs by hand for every call or come up with some way of avoiding that task like pulling up the closest command from shell command history and tweaking it. Nothing insanely painful but it would be much easier if I had a CLI tool.</p><p>This is where <a href="https://github.com/kcctl/kcctl">kcctl</a> comes in. This tool provides a CLI wrapper around the REST API which allows you to interact with Kafka Connect more conveniently. It takes the responses and prints them in an easily readable format to the terminal. The installation instructions for kcctl can be found at the GitHub repo link above. I will caveat the installation instructions with one item; as of the time of this writing, the brew install method was not working on my Mac M1 laptop. I suspect this is another M1 issue because installing with homebrew works fine on my Linux laptop. My suggestion for anyone having difficulty on a Mac M1 machine would be to either build from the source or try the precompiled pre-release version. You can find a link to the precompiled release in the repo README file.</p><p>Once the kcctl is installed, you set the server context for the tool. The context handles the server URL and credentials for your cluster. There are two ways to do this shown below.</p><pre><code>kcctl config set-context my-cluster --cluster http://localhost:8083 --username myusername --password mypassword</code></pre><pre><code>kcctl config set-context 
--cluster=https://user-name:password@localhost:443 my-context</code></pre><p>Both the commands above will set the URL and credentials of my cluster, as well as a name for the context, my-context in this case. The context name is required because kcctl remembers multiple contexts and needs a way to reference them. The sharp-eyed will notice that the context name is not a positional argument. To address the potential security concerns of storing passwords in the shell history, you can instead set them via a configuration file. The contexts you provide are stored in a dotfile in JSON format. You can find it in your home directory, in ~/.kcctl, which you can use to set sensitive information outside of the shell. One caveat on contexts, the tool is still young so there&#8217;s no convenient way of removing a context at the moment, you just have to remove them from the dotfile but the JSON format makes it fairly intuitive to do.</p><p>We can verify the cluster and any credentials using the <code>kcctl info</code> command which will return some basic information about the Kafka Connect cluster. The version number is the version of Connect worker serving the API (and its git commit) and the cluster ID is the ID of the Kafka cluster its connected to.</p><pre><code>URL:               https://&lt;USERNAME&gt;:&lt;PASSWORD&gt;@localhost:443
Version:           3.2.0
Commit:            54454035f8b22b67
Kafka Cluster ID:  tArO_G21TQyIPS4A3BE1nw</code></pre><p>Now that I&#8217;ve confirmed I&#8217;m connected, we can get a list of available plugins with the get plugins command.</p><pre><code>kcctl get plugins</code></pre><pre><code>TYPE   CLASS                                                 VERSION
source com.couchbase.connect.kafka.CouchbaseSourceConnector  4.1.6
sink   com.couchbase.connect.kafka.CouchbaseSinkConnector    4.1.6
...</code></pre><p>Let&#8217;s say I have a Postrgres DB created that I&#8217;d like to tie to Kafka as a data source. First, I&#8217;ll need to set up my configuration information.</p><pre><code>{
  "name": "debezium-connector",
  "config": {
    "database.server.name": "demo-pg",
    "database.hostname": "demo-pg.host.com",
    "connector.class": "io.debezium.connector.postgresql.PostgresConnector",
    "name": "debezium-connector",
    "database.port": "28577",
    "database.user": "USER",
    "database.password": "PASSWORD",
    "database.dbname": "DB-NAME",     "database.history.kafka.topic": "DB-NAME-schema",
    "database.history.kafka.bootstrap.servers": "demo-kafka.host.com:28579"
  }
}</code></pre><p>We then use the <code>apply</code> command to apply the configuration, but before we do that, we should check the validity of the configuration just to be safe. We can do this by using the <code>--dry-run</code> flag with the command. If all goes well, we should see the following:</p><pre><code>kcctl apply --dry-run --file register_debezium_connector.json
The configuration is valid!</code></pre><p>Now we can apply the configuration to register our new connector. We literally just remove the <code>dry-run</code> flag.</p><pre><code>kcctl apply --file register_debezium_connector.json
Created connector debezium-connector</code></pre><p>We can check on the connector by executing <code>kkcctl get connectors</code> which will return a response that looks like the following.</p><pre><code>NAME                 TYPE     STATE     TASKS
 test-connector       source   RUNNING   0: RUNNING
 debezium-connector   source   RUNNING   0: RUNNING</code></pre><p>If the connecter were failing the command would report that as well. We can simulate a failure by simply shutting off Postgres.</p><pre><code>kcctl get connectors</code></pre><pre><code>NAME                 TYPE     STATE     TASKS
 test-connector       source   RUNNING   0: RUNNING
 debezium-connector   source   RUNNING   0: FAILED</code></pre><p>If we use the describe command we can get a lot of details about the connector as well as the failure.</p><pre><code>Name:       debezium-connector
Type:       source
State:      RUNNING
Worker ID:  demo-kafka-2.aiven.local:3000
Config:
  connector.class:                                    io.debezium.connector.postgresql.PostgresConnector
  incrementing.column.name:                           id
...
Tasks:
  0:
    State:      FAILED
    Worker ID:  demo-kafka-2.aiven.local:3000
    Trace:      io.debezium.DebeziumException: Couldn't obtain encoding for database star_trek
        at io.debezium.connector.postgresql.connection.PostgresConnection.getDatabaseCharset(PostgresConnection.java:476)
        at io.debezium.connector.postgresql.PostgresConnectorTask.start(PostgresConnectorTask.java:75)
        at io.debezium.connector.common.BaseSourceTask.start(BaseSourceTask.java:130)
        at io.debezium.connector.common.BaseSourceTask.startIfNeededAndPossible(BaseSourceTask.java:207)
        at io.debezium.connector.common.BaseSourceTask.poll(BaseSourceTask.java:148)
        at org.apache.kafka.connect.runtime.WorkerSourceTask.poll(WorkerSourceTask.java:305)
        at org.apache.kafka.connect.runtime.WorkerSourceTask.execute(WorkerSourceTask.java:249)
        at org.apache.kafka.connect.runtime.WorkerTask.doRun(WorkerTask.java:188)
        at org.apache.kafka.connect.runtime.WorkerTask.run(WorkerTask.java:243)
        at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)
        at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
        at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
        at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
        at java.base/java.lang.Thread.run(Thread.java:829)
      Caused by: org.postgresql.util.PSQLException: Connection to demo-pg-cratliff1-demo.aivencloud.com:28577 refused. Check that the hostname and port are correct and that the postm
aster is accepting TCP/IP connections.
...</code></pre><p>There was a great deal of detail left out but you get the picture. I much prefer working with Kafka Connect in this fashion. If you have a need or desire to work with Kafka Connect and your primary interface is the CLI, I would urge you to take a few moments and look over the repo.</p>]]></content:encoded></item><item><title><![CDATA[Docker Desktop Has Been Hiding Things From You]]></title><description><![CDATA[Now that Docker has decided to begin charging a license fee for the use of Docker Desktop in large companies and/or commercial use, many&#8230;]]></description><link>https://svfearless.substack.com/p/docker-desktop-has-been-hiding-things-from-you-cf5e0dae8f44</link><guid isPermaLink="false">https://svfearless.substack.com/p/docker-desktop-has-been-hiding-things-from-you-cf5e0dae8f44</guid><dc:creator><![CDATA[SV Fearless]]></dc:creator><pubDate>Fri, 10 Dec 2021 16:26:03 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/3c52837e-c566-47ef-a3df-e83fc4f82006_800x533.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!DqtB!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff6dc07d9-305b-4258-9572-9b89b39342ac_800x533.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!DqtB!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff6dc07d9-305b-4258-9572-9b89b39342ac_800x533.jpeg 424w, https://substackcdn.com/image/fetch/$s_!DqtB!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff6dc07d9-305b-4258-9572-9b89b39342ac_800x533.jpeg 848w, https://substackcdn.com/image/fetch/$s_!DqtB!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff6dc07d9-305b-4258-9572-9b89b39342ac_800x533.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!DqtB!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff6dc07d9-305b-4258-9572-9b89b39342ac_800x533.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!DqtB!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff6dc07d9-305b-4258-9572-9b89b39342ac_800x533.jpeg" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f6dc07d9-305b-4258-9572-9b89b39342ac_800x533.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:null,&quot;width&quot;:null,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!DqtB!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff6dc07d9-305b-4258-9572-9b89b39342ac_800x533.jpeg 424w, https://substackcdn.com/image/fetch/$s_!DqtB!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff6dc07d9-305b-4258-9572-9b89b39342ac_800x533.jpeg 848w, https://substackcdn.com/image/fetch/$s_!DqtB!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff6dc07d9-305b-4258-9572-9b89b39342ac_800x533.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!DqtB!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff6dc07d9-305b-4258-9572-9b89b39342ac_800x533.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div></div></div></a></figure></div><p>Now that Docker has decided to begin charging a license fee for the use of Docker Desktop in large companies and/or commercial use, many people are scrambling to find open source alternatives.</p><p>This article isn&#8217;t about the available alternatives or my opinions on them (although I&#8217;ll do that as well). It's about clearing up some docker misconceptions that I&#8217;ve run into lately.</p><p>The qualifier &#8220;many people&#8221; refers to those who use Docker Desktop regularly. Anyone that falls into that category will also fall into one of two other categories; Windows users or Mac users. This is because Linux doesn&#8217;t have a Docker Desktop application. This fact brings us to what Docker Desktop has been hiding from you. Docker requires a Linux kernel to run. That means that to run a docker container in Windows or macOS you need a VM running a Linux kernel, which in turn runs docker.</p><p>The primary reason Docker Desktop (referred to from here on as DD) was created was to make your life easy by removing the need for you to care about managing a VM. If the default VM configuration is good enough for your needs it&#8217;s entirely possible that you may not even know that you&#8217;ve been running a VM this entire time. Given the original release date was about 8 years ago, it predates many developers' careers and they&#8217;ve never known life without it.</p><p>Now that people are reaching for alternatives the above situation has led to some confusion and a lot of taking reevaluation of how docker works. I am not going into detail on WSL+Windows and if you know enough to have opinions on that, you probably don&#8217;t need this article. So let's get the basics cleared up.</p><ol><li><p>Docker requires a Linux kernel to run, version 3.1 or higher, specifically</p></li><li><p>Windows and macOS require a VM running Linux to run docker</p></li><li><p>DD manages the VM and the mapping of host machine resources to the container for you so that the VM to docker mapping is invisible to the user.</p></li></ol><p>It&#8217;s a lack of awareness of this VM intermediary that has caused the most confusion that I&#8217;ve seen. After uninstalling DD then becomes a rabbit hole of finding an alternative. Once we&#8217;ve decided on one, we go down the rabbit hole of installing said alternative as well as a VM to run the alternative in. And while it may come as a surprise that you need to install a VM, at least with Windows this is made fairly clear in the installation instructions that I&#8217;ve seen. With macOS, most people are going to turn to a tool called <a href="https://brew.sh/">homebrew</a> to install their chosen alternative. Homebrew installs typically install all of their dependencies as well, so you still may not realize that it installed a VM when you installed <a href="https://github.com/abiosoft/colima">colima</a>, for example.</p><p>Once you&#8217;ve got everything installed and set up you are now going to have to set up the VM as appropriate for your particular needs. This also includes understanding that if I need to mount a volume in a docker container to the host filesystem, I will first need to configure file sharing between the host machine and the VM, and then configure sharing between the VM and the docker container.</p><p>My rambling point is simply this; be aware that DD is no longer hiding how docker works from you and understand that you&#8217;ll need to take a few extra steps to use docker without DD. Because of all of this, my advice is to continue to use DD unless there is a compelling reason not to (or you just want to play with things under the hood), but if you feel a need to ditch DD then be aware of the extra effort you&#8217;ll need to put in.</p><p>I use colima on my MacBook because it provides me the closest to a drop-in replacement for the magic that DD performs. I can quickly and easily change my VM configuration directly from the CLI when needed, while not forcing me to install kubernetes if I don&#8217;t need it (I&#8217;m looking at you minikube), it provides decent performance, and makes dealing with port mapping and volume mounting easy.</p>]]></content:encoded></item><item><title><![CDATA[Why I switched to Pop!_OS]]></title><description><![CDATA[A short tale of distro jumping over 23 years]]></description><link>https://svfearless.substack.com/p/why-i-switched-to-pop-os-6ba26abf6c6a</link><guid isPermaLink="false">https://svfearless.substack.com/p/why-i-switched-to-pop-os-6ba26abf6c6a</guid><dc:creator><![CDATA[SV Fearless]]></dc:creator><pubDate>Sat, 26 Dec 2020 21:04:03 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/3ad9405b-1590-463f-8b20-3be41e68ef1f_800x450.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!FTra!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F44eb1a15-f5ee-4fc5-bc37-3e3ebbf63f9c_800x450.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!FTra!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F44eb1a15-f5ee-4fc5-bc37-3e3ebbf63f9c_800x450.png 424w, https://substackcdn.com/image/fetch/$s_!FTra!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F44eb1a15-f5ee-4fc5-bc37-3e3ebbf63f9c_800x450.png 848w, https://substackcdn.com/image/fetch/$s_!FTra!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F44eb1a15-f5ee-4fc5-bc37-3e3ebbf63f9c_800x450.png 1272w, https://substackcdn.com/image/fetch/$s_!FTra!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F44eb1a15-f5ee-4fc5-bc37-3e3ebbf63f9c_800x450.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!FTra!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F44eb1a15-f5ee-4fc5-bc37-3e3ebbf63f9c_800x450.png" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/44eb1a15-f5ee-4fc5-bc37-3e3ebbf63f9c_800x450.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:null,&quot;width&quot;:null,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!FTra!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F44eb1a15-f5ee-4fc5-bc37-3e3ebbf63f9c_800x450.png 424w, https://substackcdn.com/image/fetch/$s_!FTra!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F44eb1a15-f5ee-4fc5-bc37-3e3ebbf63f9c_800x450.png 848w, https://substackcdn.com/image/fetch/$s_!FTra!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F44eb1a15-f5ee-4fc5-bc37-3e3ebbf63f9c_800x450.png 1272w, https://substackcdn.com/image/fetch/$s_!FTra!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F44eb1a15-f5ee-4fc5-bc37-3e3ebbf63f9c_800x450.png 1456w" sizes="100vw" fetchpriority="high"></picture><div></div></div></a><figcaption class="image-caption">Mort1mer (the giant robot) is just a fun&nbsp;bonus</figcaption></figure></div><p><strong>TL;DR</strong></p><p>If you just want to see my take on what I like, jump down to the last 5 paragraphs.</p><p><strong>The story of my distro hopping</strong></p><p>In 1997 I had just exited from the military and started working at my first job as a professional developer. There I met a systems administrator who introduced me to Linux for the first time. The distro was Slackware, distributed at the time by Walnut Creek CDROM as, if memory serves, a set of 5 or 6 CDs.</p><p>Since that time, my mentality has shift from, &#8220;Linux is a <em>really</em> cool toy I can tinker with on weekends and evenings&#8221; to, &#8220;Linux is an inextricable part of my job and I can&#8217;t be spending hours or days puzzling out why some setting isn&#8217;t working as expected, or why my tool chain isn&#8217;t working right out of the box&#8221;.</p><p>In other words, my needs and requirements have evolved over the years. The same thing happened with hardware. I used to love to build all my hardware on my own, but the more valuable that my time became, the less patience I had with trying to correctly match hardware and assemble machines.</p><p>Fortunately, Linux distributions were evolving as well. Slackware was truly great as a toy or to test out ideas but it took so much tinkering for me to get it to work the way I wanted that I never considered using it as a work tool. A couple of years after i was introduced to Slackware and was wondering how I could use it as a working tool, another friend of mine introduced me to Red Hat. This thing was light years beyond Slackware, it even had a <em>graphical installer! A</em>lthough it still couldn&#8217;t replace Windows it was a major step forward for me.</p><p>This cycle repeated over the years, sticking with a distro that allowed me to do more and more work in Linux until something so definitively superior would come onto the scene that I had to switch. This switch would improve my corresponding user experience, workflow, and personal satisfaction each time.</p><p>For the past 5 years or so, Ubuntu has been my go-to Linux distribution. It was the best combination of user friendly without getting in the way of work. Up until a few months ago.</p><p>I was in the market for a new laptop and a colleague of mine told me about his preferred <a href="https://system76.com/">vendor</a> for buying laptops. They make higher end Linux laptops and workstations. This vendor also supports a version of Linux that it tailors to it&#8217;s machines. He personally did not use this OS but highly recommended their machines (as do I).</p><p>I read a little bit about Pop!_OS on their website and was intrigued by what I saw (file system encryption by default for example) but didn&#8217;t really want to deal with an unknown distro, so I just went with Ubuntu (which you may choose as an alternative distribution) and forgot about it.</p><p>The laptop came, and I set about customizing it to me needs. I&#8217;d gotten a model with a dedicated Nvidia graphics card so that I could take advantage of <a href="https://en.wikipedia.org/wiki/CUDA">CUDA</a> for ML projects. If you&#8217;ve ever had to set up Linux with an Nvidia you&#8217;ve almost certainly had to deal with setting up proprietary drivers and tweaking the X configuration to get things working. This is where I started hitting my first frustration. There were a couple of other tiny things that were irritating me but the point here is that I was experiencing my normal frustrations at dealing with the hardware tweaks that Linux occasionally needs for anything that isn&#8217;t out of the box.</p><p>Eventually I threw up my hands and told myself, I&#8217;m already moving slower than I wanted, I might as well play with a new distribution anyway. So I contact support and asked if there was an upgrade path from Ubuntu to Pop which didn&#8217;t require losing all of the work that I&#8217;d already put into customizing my tools. The answer was yes, they gave me the instructions, and I was off to the races. It took all of about 4 days to realize I should have just had them ship it with Pop from the start.</p><p>Out of the box, you&#8217;re going to get a few things, like a version optimized for Nvidia or Intel/AMD. The respective versions are optimized for the specific GPU you are using. I didn&#8217;t need to make one tweak to have my Nvidia card set up correctly. Even getting the CUDA toolkit installed was a simple apt install which worked without a hitch.</p><p>Setup is straightforward and easy. File system encryption is just a choice during install (the default is yes). The install itself isn&#8217;t bloated. The interface is fairly clean. And while not a driving force, it is hands down the easiest Linux OS I&#8217;ve ever tried to play a game on. They release updates every 6 months in sync with Ubuntu on which it is based.</p><p>In a very pleasant surprise, customer support has been truly outstanding. They will answer every question no matter how detailed or simple. Interactions with them were a dialog with someone who actually understands Linux and the issues you&#8217;re likely to experience. When the laptop shipped they had even taken the time to correctly set up the SSD read/write configuration optimized. And it&#8217;s all open source so you can create a bootable USB to experiment with and see how it works out for you.</p><p>There were a small list of things that miraculously fixed themselves after upgrading to Pop! OS without intervention on my part but just as an example, if I left Chrome open overnight and the laptop went to sleep, it wouldn&#8217;t wake up. I tried disabling all plugins, ensuring I had the latest version, downgrading to previous versions, you name it, the only solution I found was simply killing the browser before the laptop went to sleep. This just vanished when I went to Pop. I still have no idea what the issue was.</p><p>Pop just stays out of my way when I don&#8217;t need it, it&#8217;s clean, things just work out of the box, it&#8217;s not bloated with things I don&#8217;t care about, it&#8217;s still super-configurable, and while default encryption may not seem like much it&#8217;s definitely important to me. Also, I <em>really</em> like the giant robot mascots.</p>]]></content:encoded></item><item><title><![CDATA[A Brief Introduction to Watercolor]]></title><description><![CDATA[Why is watercolor so different from the forms of paint?]]></description><link>https://svfearless.substack.com/p/a-brief-introduction-to-watercolor-dff712cd0ab2</link><guid isPermaLink="false">https://svfearless.substack.com/p/a-brief-introduction-to-watercolor-dff712cd0ab2</guid><dc:creator><![CDATA[SV Fearless]]></dc:creator><pubDate>Sun, 13 Dec 2020 16:11:24 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/86aa88cd-7faa-4a07-a3de-edb671a2faca_800x406.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Why is watercolor so different from the forms of paint?</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!DrgM!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d3ff1ab-e4b1-4193-82f8-20b4d7ab08b2_800x406.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!DrgM!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d3ff1ab-e4b1-4193-82f8-20b4d7ab08b2_800x406.png 424w, https://substackcdn.com/image/fetch/$s_!DrgM!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d3ff1ab-e4b1-4193-82f8-20b4d7ab08b2_800x406.png 848w, https://substackcdn.com/image/fetch/$s_!DrgM!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d3ff1ab-e4b1-4193-82f8-20b4d7ab08b2_800x406.png 1272w, https://substackcdn.com/image/fetch/$s_!DrgM!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d3ff1ab-e4b1-4193-82f8-20b4d7ab08b2_800x406.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!DrgM!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d3ff1ab-e4b1-4193-82f8-20b4d7ab08b2_800x406.png" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2d3ff1ab-e4b1-4193-82f8-20b4d7ab08b2_800x406.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:null,&quot;width&quot;:null,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!DrgM!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d3ff1ab-e4b1-4193-82f8-20b4d7ab08b2_800x406.png 424w, https://substackcdn.com/image/fetch/$s_!DrgM!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d3ff1ab-e4b1-4193-82f8-20b4d7ab08b2_800x406.png 848w, https://substackcdn.com/image/fetch/$s_!DrgM!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d3ff1ab-e4b1-4193-82f8-20b4d7ab08b2_800x406.png 1272w, https://substackcdn.com/image/fetch/$s_!DrgM!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d3ff1ab-e4b1-4193-82f8-20b4d7ab08b2_800x406.png 1456w" sizes="100vw" fetchpriority="high"></picture><div></div></div></a><figcaption class="image-caption">Oil painting on the left, watercolor on the&nbsp;right</figcaption></figure></div><p>I have heard anecdotal stories from the a number of people who experiment with creating art in multiple mediums that aside from physical vs digital, the most difficult switch to make is between watercolor, and other paints. Accomplished oil and acrylic painters initially have a hard time using watercolor, and vice-versa. On the surface these two mediums don&#8217;t seem so different, so let&#8217;s take a quick look at how these mediums are different.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!fvUc!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1599b5a8-0534-4bdf-9b3b-0ecd3af2c372_640x627.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!fvUc!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1599b5a8-0534-4bdf-9b3b-0ecd3af2c372_640x627.jpeg 424w, https://substackcdn.com/image/fetch/$s_!fvUc!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1599b5a8-0534-4bdf-9b3b-0ecd3af2c372_640x627.jpeg 848w, https://substackcdn.com/image/fetch/$s_!fvUc!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1599b5a8-0534-4bdf-9b3b-0ecd3af2c372_640x627.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!fvUc!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1599b5a8-0534-4bdf-9b3b-0ecd3af2c372_640x627.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!fvUc!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1599b5a8-0534-4bdf-9b3b-0ecd3af2c372_640x627.jpeg" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/1599b5a8-0534-4bdf-9b3b-0ecd3af2c372_640x627.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:null,&quot;width&quot;:null,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!fvUc!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1599b5a8-0534-4bdf-9b3b-0ecd3af2c372_640x627.jpeg 424w, https://substackcdn.com/image/fetch/$s_!fvUc!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1599b5a8-0534-4bdf-9b3b-0ecd3af2c372_640x627.jpeg 848w, https://substackcdn.com/image/fetch/$s_!fvUc!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1599b5a8-0534-4bdf-9b3b-0ecd3af2c372_640x627.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!fvUc!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1599b5a8-0534-4bdf-9b3b-0ecd3af2c372_640x627.jpeg 1456w" sizes="100vw"></picture><div></div></div></a><figcaption class="image-caption">Windsor &amp; Newton oil pain&nbsp;set</figcaption></figure></div><p>There are 5 types of paints generally used in art. Oil, acrylic, encaustic, gouache, and watercolor. Of these 5 paint types, 4 share a common physical property; they are all opaque. This means that the colors that you see when looking at a painting done with those four are created because those paints will absorb the entire spectrum of light <em>except</em> the color that you see, which gets reflected back to your eyes. You can mix the paints themselves to come up with different colors to apply directly to the canvas.</p><p>Because of this opacity, paintings typically start with dark colors, and lighter colors are applied as the painting gets closer to completion. This makes opaque paints more forgiving, since, you can generally paint over mistakes. In particular, oil paints also dry incredibly slowly. We&#8217;re talking about 6 months to a year before completely dry. This can make oil very forgiving because you can work on a project for months without losing the capacity to make subtle changes to the paint that&#8217;s already been applied. Contrary to what many people may intuitively think, this makes them a good medium to use while learning.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!thmw!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb71ee4eb-7508-4557-bb0a-26cf8623d000_800x600.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!thmw!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb71ee4eb-7508-4557-bb0a-26cf8623d000_800x600.jpeg 424w, https://substackcdn.com/image/fetch/$s_!thmw!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb71ee4eb-7508-4557-bb0a-26cf8623d000_800x600.jpeg 848w, https://substackcdn.com/image/fetch/$s_!thmw!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb71ee4eb-7508-4557-bb0a-26cf8623d000_800x600.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!thmw!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb71ee4eb-7508-4557-bb0a-26cf8623d000_800x600.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!thmw!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb71ee4eb-7508-4557-bb0a-26cf8623d000_800x600.jpeg" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b71ee4eb-7508-4557-bb0a-26cf8623d000_800x600.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:null,&quot;width&quot;:null,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!thmw!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb71ee4eb-7508-4557-bb0a-26cf8623d000_800x600.jpeg 424w, https://substackcdn.com/image/fetch/$s_!thmw!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb71ee4eb-7508-4557-bb0a-26cf8623d000_800x600.jpeg 848w, https://substackcdn.com/image/fetch/$s_!thmw!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb71ee4eb-7508-4557-bb0a-26cf8623d000_800x600.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!thmw!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb71ee4eb-7508-4557-bb0a-26cf8623d000_800x600.jpeg 1456w" sizes="100vw"></picture><div></div></div></a><figcaption class="image-caption">Windsor &amp; Newton watercolor set</figcaption></figure></div><p>Watercolor paint, on the other hand, is a different beast altogether. Watercolor is translucent, meaning that the color you see when viewing a watercolor painting, is the light reflected from the surface <em>beneath</em> the watercolor after its been refracted through the pigment of the paint. This means that the surface the paint is applied to is going to be an influence in what the finished piece looks like so selection of paper, and paper color, has an impact. This also means that darker colors are created by applying layers of paint until the desired color is reached. This also means that blending paint colors is typically done directly on the painting. If you blend the wrong colors, recovery can be difficult because each additional application of paint darkens the color, while simply applying more water to wash the paint runs the risk of saturating the paper and making it difficult to reapply paint in that area. This also means that the lighter colors need to be considered first and gotten correct the first time because each new layer of paint applied will darken the final product.</p><p>Ironically, this means that watercolor can be much less forgiving than the other paints. I personally think that the reason most peoples intuitive reaction is that watercolor is &#8220;easy&#8221;, &#8220;childish&#8221;, or &#8220;not real art&#8221;, is based on the fact that watercolor is what people have the most exposure. They get that exposure in art class when they&#8217;re just starting school. Schools choose that medium not because it&#8217;s easy to teach but because watercolor is the cheapest of the 5 types of paint, by definition it&#8217;s water soluble meaning it&#8217;s easy to clean up any mess, and lastly the kids typically aren&#8217;t going to get frustrated that their art isn&#8217;t exactly the way they want it. Children are much less concerned with &#8220;getting it right&#8221; than adults are. They&#8217;re just enjoying the act of creative expression.</p><p>So we can see that there is definitely a reason for the rumors that watercolor is a difficult medium to learn. I also mentioned briefly what I&#8217;ve personally observed as the mainstream reaction to watercolor as an art medium. I&#8217;d like to take one last moment to point out that watercolor is the oldest paint that we know of. Natural pigments were mixed with water, or even saliva, to create large cave murals by prehistoric man. The <a href="https://www.bradshawfoundation.com/lascaux/">Lascaux Cave paintings</a> date between 15,000 to 9,000 BCE.</p><p>Just because something is simple in concept, doesn&#8217;t mean that you should think it&#8217;s irrelevant.</p>]]></content:encoded></item><item><title><![CDATA[The Unavoidable Command Line]]></title><description><![CDATA[Why developers need it]]></description><link>https://svfearless.substack.com/p/the-unavoidable-command-line-9ed3cd5587b8</link><guid isPermaLink="false">https://svfearless.substack.com/p/the-unavoidable-command-line-9ed3cd5587b8</guid><dc:creator><![CDATA[SV Fearless]]></dc:creator><pubDate>Wed, 09 Dec 2020 22:18:37 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/923235de-6176-468f-9380-87d97ecfb8e4_800x450.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!ktL1!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb678f2d0-4611-4364-bf8f-58dd2eef2a29_800x450.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ktL1!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb678f2d0-4611-4364-bf8f-58dd2eef2a29_800x450.png 424w, https://substackcdn.com/image/fetch/$s_!ktL1!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb678f2d0-4611-4364-bf8f-58dd2eef2a29_800x450.png 848w, https://substackcdn.com/image/fetch/$s_!ktL1!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb678f2d0-4611-4364-bf8f-58dd2eef2a29_800x450.png 1272w, https://substackcdn.com/image/fetch/$s_!ktL1!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb678f2d0-4611-4364-bf8f-58dd2eef2a29_800x450.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!ktL1!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb678f2d0-4611-4364-bf8f-58dd2eef2a29_800x450.png" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b678f2d0-4611-4364-bf8f-58dd2eef2a29_800x450.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:null,&quot;width&quot;:null,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!ktL1!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb678f2d0-4611-4364-bf8f-58dd2eef2a29_800x450.png 424w, https://substackcdn.com/image/fetch/$s_!ktL1!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb678f2d0-4611-4364-bf8f-58dd2eef2a29_800x450.png 848w, https://substackcdn.com/image/fetch/$s_!ktL1!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb678f2d0-4611-4364-bf8f-58dd2eef2a29_800x450.png 1272w, https://substackcdn.com/image/fetch/$s_!ktL1!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb678f2d0-4611-4364-bf8f-58dd2eef2a29_800x450.png 1456w" sizes="100vw" fetchpriority="high"></picture><div></div></div></a><figcaption class="image-caption">Looking at something like this should never be intimidating&#8202;&#8212;&#8202;Image by&nbsp;Author</figcaption></figure></div><p>Over the years I have noticed an interesting phenomenon when it comes to developers and the use of the command-line interface (CLI). In most mainstream media, the depictions of &#8220;hardcore&#8221; developers involve the subject sitting in front of one or more screens, surrounded by, and working with, massive amounts of text. There can be graphs, images, and all sorts of interesting eye candy on the screens but the meat of what the developer is doing is clearly some insane amount of text, usually scrolling by far faster than most people could process.</p><p>In the real world of development, at least in the mainstream, this is not the case. I know and have worked with (and still do), many very capable developers who avoid using the terminal at all costs. They&#8217;ll engage with it only when it&#8217;s unavoidable and get out as soon as possible. I feel confident in saying that a large number of developers today feel uncomfortable, and in some cases, intimidated, frustrated, lost, or all of the above.</p><p>To be sure, there are always exceptions. The outliers, that actively avoid graphical interfaces, eschewing modern IDEs for a terminal text editor. But that&#8217;s exactly what they are; outliers. The reality is that every person who works as a developer for any length of time has to deal with the command line to some degree. It&#8217;s simply unavoidable.</p><p>There&#8217;s a social aspect that&#8217;s often overlooked as well. Developers are still people, regardless of stereotypes or personal inclinations. Age discrimination in the software industry is a serious topic, and it does exist. I&#8217;ve been on the receiving end of it once or twice. But judgment based on perceived skill with the CLI is a thing. Now, to be clear, I&#8217;m not saying that age discrimination is a trivial subject or that judgment over CLI skills is on par with that kind of social issue. One of the reasons for that is because, in mainstream conversation with anyone not involved with software development, it rarely comes up, and when it does I&#8217;ve usually seen it handled like this: &#8220;That&#8217;s only a thing in the movies, real devs don&#8217;t work that way. In reality it&#8217;s not that exciting&#8221;, and the conversation moves on. However, I have seen developers in conversation go from exuberant to silent when topics involving knowledge of the CLI come up. It&#8217;s almost as if they have a sudden bout of impostor syndrome because they aren&#8217;t the CLI genius that pop culture demands superstar developers must be.</p><p>Having said all of that, the CLI is just another tool in your kit as a developer. The only reason that GUI-based IDEs aren&#8217;t intimidating is that it&#8217;s easy to poke around menu options, and icons representing certain tasks tend to be the same regardless of what the IDE is, <em>e.g.</em>, &#8220;Run program&#8221; is almost universally represented by some variation of an arrow. Most people don&#8217;t worry too much about being judged for not knowing a particular tool, be it an IDE, a web application, or an office app, if they&#8217;ve never used it extensively. CLIs should be regarded in the same light. Actually, it&#8217;s much more difficult to master than an IDE or spreadsheet. It&#8217;s more like learning a new language. It&#8217;s so incredibly expressive, and has so many little bits, that no one really &#8220;masters&#8221; it, not completely. The same thing can also be said for GUIs but the big difference here, again, is the perception, or maybe expectation, is a better word. While people understand that there is a difference between the skill levels of someone that opens IntelliJ the first time, and someone who has used IntelliJ for years, no one judges them for that difference.</p><p>This brings me back to the point of this article. Using the CLI is unavoidable, and frankly, it&#8217;s becoming less avoidable as the year's pass. Since sometime roughly around 2010, the use of the terminal has become more commonplace. This interestingly coincides with the rise of cloud computing. At that time, AWS had launched EC2 a few years earlier and it was starting to become extremely popular. Microsoft launched Azure, Rackspace and NASA launched OpenStack, and Google introduced Google Compute Engine. Over the past decade, the mainstream use of all of these cloud entities has continued to grow at a breakneck pace. The thing that all of them have in common? You guessed it, command-line use.</p><p>The CLI has undergone a resurgence over the past decade. There are simply things that can&#8217;t be done in a convenient or timely fashion on those platforms without the use of the CLI, and the existence of tools like gcloud, AWS CLI, and Azure CLI, make it clear that the providers understand the need. Even before all of this, containers like docker require at least some use of the CLI to be productive. There are countless articles extolling the virtues of the CLI ad nauseam, I&#8217;m not going to preach to you that the command line is better. Go find one of them if that&#8217;s what you&#8217;re interested in. I&#8217;m simply saying unless your career as a developer is extremely short, or extremely specialized, you&#8217;re going to deal with the command line. Having said all of that, let me be clear; I&#8217;m speaking in the context of greenfield development. If I&#8217;m talking about automating a CI/CD pipeline, I&#8217;m probably using API calls.</p><p>Like articles espousing the superiority of ad nauseam the CLI, there are articles ad nauseam that tell you how to learn your way around a terminal interface. I don&#8217;t know you, your learning preferences, and IMO that&#8217;s a topic better suited to a book anyway, so I&#8217;m not going to speak to that. What I do want to tell you is that regardless of your personal level of knowledge, the tools available through the terminal are virtually infinite, and much like the universe, they continue expanding all the time. From the shell that you&#8217;re using to the commands you have access to, it&#8217;s all larger than you think. There are certain things that I personally feel are relevant regardless of what I&#8217;m trying to learn, or the medium I&#8217;m using to learn it. One of those things is, that truly learning something new, or improving on what you already know, requires a deliberate effort. For me, one of the things that means is, if I don&#8217;t know how to accomplish it with the tool I&#8217;m currently using, do I know that tool well enough? I&#8217;ll give you a concrete example from something I did this weekend that most developers have probably experienced in some form.</p><p>I had a set of personal goals I wanted to complete this weekend because I wanted to play with some technology I&#8217;d never worked with before, specifically Redis. I had two GCP compute engine instances set up that I wanted to use because I don&#8217;t have the hardware to run meaningful performance testing on my laptop. I wanted to install Redis on both instances, populate instance 1 DB with some data from memtier-benchmark, and have that information replicated on instance 2. I also wanted to write a rudimentary client that I could use to insert data into instance 1, and read that data from instance 2, simply to demonstrate the replication worked remotely and get a feel for the libraries. How did I access these instances you ask? I used <a href="https://www.ssh.com/ssh/">SSH</a> to connect. Now I&#8217;m firmly rooted in the CLI universe, running <a href="https://tiswww.case.edu/php/chet/bash/bashtop.html">bash</a> as my shell. I downloaded and installed <a href="https://redis.io/download">Redis OSS</a> on each instance. How did I download it, you may ask? I used <a href="https://www.gnu.org/software/wget/">wget</a>. Compiling Redis OSS on the first instance took more than a few seconds so I wanted to start compiling it on the second GCP instance while the first instance was still compiling, but if I dropped my connection to the first instance, I would have killed the job and aborted the compile. So just killing the connection and logging into the second instance to start the compile would have been pointless. My solution? I used <a href="https://github.com/tmux/tmux/wiki">tmux</a> to create a second session in my terminal window and used the new session to connect to the second instance. There are a number of ways to accomplish that same task, I was simply already using tmux so that was the laziest way and I could easily keep an eye on progress. Once they had compiled, the redis.conf configuration file had to be configured. I used <a href="https://www.vim.org/">vim</a> for that. Now I had to run memtier-benchmark, the command-line tool for Redis perf testing, to load some data in and get a basic sense of expected performance. At this point, I needed to inspect the contents of both DBs to make sure that the contents had replicated as expected across instances. The way to do this was using the redis-cli command-line tool. Only one problem, I had run the servers from the command line. They were both sitting there throwing out logs to my terminal window. I could have just opened 2 more tmux sessions and had 4 ssh sessions going, 2 running a server, and 2 available for interacting with the command line. I had two problems with this; one, I have a peeve about too many windows open, it bugs me, and having 4 panes in a tmux session is a bit extreme for me to care about for more than a few minutes, and two, I knew linux had job control commands but for the life of me, the only thing I could remember was that I should have started the server using `./redis-server &amp;` to start it as a background task. After a minute of googling to refresh my memory, I used the bash command `Ctrl+z` to suspend the process and get the command line back, then used the bash shell command `bg` to start the suspended process as a background job. This freed up my terminal to use start redis-cli to query the DB, and did not require me to open a new terminal.</p><p>If I had been uncomfortable with the CLI, at best it would have taken me significantly more than the 5-ish hours to complete all the tasks I had set out for myself. At worst, I may not have gotten any of my goals completed at all. I get that the terminal can be intimidating, there&#8217;s a lot of stuff to get familiar with, but if you look back at the afternoon I just described, you might see that terms &#8220;terminal&#8221;, or &#8220;command line&#8221;, hide a lot. The terminal is just the text interface I&#8217;m using to interact with the shell. The shell is just the interface that I&#8217;m using to interact with the OS. Tools like ssh, and tmux, are just the interface I&#8217;m using to interact with both the shell and operating system. We&#8217;re programmers, and to make good software we break things down into parts that can be easily composed. So to learn it break it down. I like to do it by deciding what my current issue is and asking if I can&#8217;t do it, is that because I need another tool or don&#8217;t know this one well enough? Is it my knowledge (or lack thereof) that makes this painful, or am I using the wrong tool for the job? What tool do I want to learn, what&#8217;s most beneficial? The shell? A terminal editor? I obviously can&#8217;t answer those questions for you. What I can say is that I think you should ask those questions of yourself and think about a good answer.</p>]]></content:encoded></item></channel></rss>