<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="https://www.w3.org/2005/Atom">
  <channel>
    <title>Bob Gardner</title>
    <description>Bob Gardner&apos;s personal website.</description>
    <link>https://www.bob-gardner.com/</link>
    <atom:link href="https://www.bob-gardner.com/feed.xml" rel="self" type="application/rss+xml" />
    <pubDate>Mon, 29 Dec 2025 02:23:11 +0000</pubDate>
    <lastBuildDate>Mon, 29 Dec 2025 02:23:11 +0000</lastBuildDate>
    <generator>Jekyll v3.10.0</generator>
    
      <item>
        <title>C++ Function Try Block</title>
        <description>&lt;p&gt;Most of my code reviews are for C++ code and every so often, a commit or review
comment reminds me of a nuanced C++ feature I need to study more closely.
Recently, it was a teammate leaning on the &lt;a href=&quot;https://en.cppreference.com/w/cpp/language/try.html#Function_try_block&quot;&gt;function try
block&lt;/a&gt;, a feature I usually avoid because it behaves
differently depending on where you use it.&lt;/p&gt;

&lt;p&gt;A function try block lets you wrap an entire constructor, destructor, or free
function in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;try { ... } catch { ... }&lt;/code&gt;, giving you access to parameters when an
exception is thrown before the body even runs. On paper that sounds simple, but
the rules change just enough between constructors, destructors, and regular
functions that it’s easy to shoot yourself in the foot.&lt;/p&gt;

&lt;p&gt;This post is the field notes from that review: when the feature helps, the traps
I bumped into, and why I still reach for it only when there’s no cleaner option.&lt;/p&gt;

&lt;!-- more --&gt;

&lt;h2 id=&quot;constructors&quot;&gt;Constructors&lt;/h2&gt;

&lt;p&gt;The main selling point of the function try block is that it allows you to
handle exceptions thrown during the initialization of member variables in a
constructor that otherwise could not be caught by the class author.&lt;/p&gt;

&lt;div class=&quot;language-cpp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;my_class&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
 &lt;span class=&quot;nl&quot;&gt;public:&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;my_class&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;str_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// A catch block here would not catch exceptions thrown by the member&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// initializer list, in this example, the std::string copy constructor.&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;exception&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// You can log, modify the exception, throw a different exception, or abort&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// the program, but you cannot use a return statement (compiler error).&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// The constructor parameters are available here, which is especially useful&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// for logging.&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;// implicit &quot;throw;&quot; here&lt;/span&gt;

 &lt;span class=&quot;nl&quot;&gt;private:&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;str_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In real production code, I’ve seen this feature used for logging. Another valid
use case is to throw a different exception, e.g. at an API boundary.&lt;/p&gt;

&lt;p&gt;The implicit &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;throw;&lt;/code&gt; at the end of the catch block is a bit of a gotcha. It
makes sense if you consider that the constructor is expected to fully initialize
the object, and an exception thrown during construction would leave the object
in a partially constructed state.&lt;/p&gt;

&lt;h2 id=&quot;destructors&quot;&gt;Destructors&lt;/h2&gt;

&lt;p&gt;The implicit behavior varies for constructors, destructors, and regular
functions, and to me, makes it too complicated to generally recommend using
outside of constructors and macros (more on that later).&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Function Type&lt;/th&gt;
      &lt;th&gt;Implicit Behavior&lt;/th&gt;
      &lt;th&gt;Explicit Return Statement Allowed&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Constructor&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;throw;&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;No (illegal, compiler error)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Destructor&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;throw;&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;Yes&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Void-returning function&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;return;&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;Yes&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Non-void returning function&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;return;&lt;/code&gt;, (undefined behavior, may generate compiler warning)&lt;/td&gt;
      &lt;td&gt;Yes&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;One of the other use cases I thought of for the function try block is to make it
easier to implement a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;noexcept&lt;/code&gt; function without having to indent the entire
function body. And for regular functions, this &lt;strong&gt;is&lt;/strong&gt; the implicit behavior if
the end of the catch block is reached. But for destructors, where the
consequence of not catching an exception is aborting the program, the implicit
behavior of the function try block is to rethrow the exception, just like
constructors. Though unlike constructors, you can use an explicit &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;return;&lt;/code&gt;
statement to suppress it, which makes sense because the object is already fully
destructed and the caller can’t continue to use it (unlike a constructor).
Maybe the reason that destructors implicitly &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;throw;&lt;/code&gt; is because the function
doesn’t have a return type and can’t signal that it violated its postcondition,
requiring the catch handler to explicitly spell out via &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;return;&lt;/code&gt; that it
handled the exception.&lt;/p&gt;

&lt;p&gt;Example:&lt;/p&gt;

&lt;div class=&quot;language-cpp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;function_try_dtor&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
 &lt;span class=&quot;nl&quot;&gt;public:&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;~&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;function_try_dtor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;noexcept&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(...)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// handle exception&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;While Clang recognizes this as correctly implementing the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;noexcept&lt;/code&gt;
specification, MSVC still emits a warning:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;warning C4297: &apos;function_try_dtor::~function_try_dtor&apos;: function assumed not to throw an exception but does
&amp;lt;source&amp;gt;(7): note: destructor or deallocator has a (possibly implicit) non-throwing exception specification
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Because of this, I recommend implementing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;noexcept&lt;/code&gt; destructors with normal
try-catch blocks inside the destructor body.&lt;/p&gt;

&lt;p&gt;To wrap up constructors and destructors, another gotcha is that accessing member
variables in the catch handler is undefined behavior. In practice, Clang emits a
warning, but MSVC does not.&lt;/p&gt;

&lt;h2 id=&quot;regular-functions&quot;&gt;Regular Functions&lt;/h2&gt;

&lt;p&gt;For regular functions, the implicit behavior if the end of the catch block is
reached is to return, which is useful for implementing a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;noexcept&lt;/code&gt; function.
But, if the function has a non-void return type, it’s undefined behavior if the
catch block ends without an explicit return or throwing an exception. Clang and
MSVC both emit a warning for this case.&lt;/p&gt;

&lt;p&gt;I have seen a few functions that use function try blocks to handle exceptions,
e.g. by logging and suppressing the exception. In my own code, I prefer to just
indent the entire function body and use a normal try-catch block, or introduce
a separate helper function to avoid mixing logic and error handling in the same
function.&lt;/p&gt;

&lt;div class=&quot;language-cpp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;private_api&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;

&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;function_try&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;private_api&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;exception&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// handle exception&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;inner_try&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;private_api&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;exception&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// handle exception&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;For non-void functions, I think it also makes it clearer that the function with
the try-catch block in the function body is still responsible for returning a
value.&lt;/p&gt;

&lt;p&gt;Which do you find more readable?&lt;/p&gt;

&lt;p&gt;Where I’ve seen the benefits of this feature outweigh the complexity is for
handling exceptions consistently at an API boundary. For example:&lt;/p&gt;

&lt;div class=&quot;language-cpp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;[[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;noreturn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]]&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;rethrow_current_exception&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;throw&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;exception&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// log, modify, throw a different exception, or abort the program&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;cp&quot;&gt;#define FOO_API_BEGIN try
#define FOO_API_END catch (...) { rethrow_current_exception(); }
&lt;/span&gt;
&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;foo_api&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;FOO_API_BEGIN&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;FOO_API_END&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If these macros were instead used within the function body, the entire function
would need to be indented, which seems unnecessary, or they would immediately
call into a helper function, which adds more reading/debugging overhead and
potential for bugs. A drawback of these macros is that contributors need to be
educated on their use, but an advantage is that it’s clearer to see that
functions using these macros are handling exceptions consistently.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;I generally avoid using the function try block because of the complexity and the
potential for undefined behavior. I think it’s more readable to use a normal
try-catch block, but will use it for constructors and macros when it’s the best
tool for the job.&lt;/p&gt;

&lt;p&gt;I hope you’ve found this post a useful supplement to the
&lt;a href=&quot;https://en.cppreference.com/w/cpp/language/try.html#Function_try_block&quot;&gt;cppreference page&lt;/a&gt; for summarizing the differences in
implicit behavior and allowed explicit behavior, as well as practical use cases
and gotchas.&lt;/p&gt;

&lt;h2 id=&quot;appendix-rust-approach&quot;&gt;Appendix: Rust Approach&lt;/h2&gt;

&lt;p&gt;I use Rust for hobby projects and I often find it interesting to compare how
Rust addresses these problems. In Rust, there aren’t constructors, just regular
functions that return a new instance of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;struct&lt;/code&gt; or a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Result&lt;/code&gt; with an
error. The fields of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;struct&lt;/code&gt; are initialized in the function body and the
function must explicitly handle any errors.&lt;/p&gt;

&lt;div class=&quot;language-rust highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;MyStruct&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;num&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;i32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;impl&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;MyStruct&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Result&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;Self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Box&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;dyn&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nn&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;num&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;.parse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;Ok&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;Self&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;num&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Here &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MyStruct::new&lt;/code&gt; propagates the error from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;str.parse&lt;/code&gt; to the caller via
the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;?&lt;/code&gt; operator. Values are moved into the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;struct&lt;/code&gt; fields and unlike C++
move is implemented by the compiler, so it’s not possible to write a throwing
move.&lt;/p&gt;

&lt;p&gt;That just leaves destructors. In Rust, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Drop&lt;/code&gt; trait can be used to add
custom code within the destructor.&lt;/p&gt;

&lt;div class=&quot;language-rust highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;trait&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Drop&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// Required method&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;drop&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;mut&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;As you can see, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;drop&lt;/code&gt; method can’t return anything, so there’s no way to
signal that the destructor failed. As a convention, implementors of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Drop::drop&lt;/code&gt;
suppress any errors that occur, but callers interested in manually handling the
error can explicitly call a separate method that returns a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Result&lt;/code&gt;. An example
of this in the standard library is
&lt;a href=&quot;https://doc.rust-lang.org/stable/std/fs/struct.File.html&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;std::fs::File&lt;/code&gt;&lt;/a&gt; and
its &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sync_all&lt;/code&gt; method. And compared to C++, implementations of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Drop::drop&lt;/code&gt; can
still reference the fields of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;struct&lt;/code&gt; without undefined behavior. It’s
important to note that panicking in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;drop()&lt;/code&gt;
&lt;a href=&quot;https://doc.rust-lang.org/std/ops/trait.Drop.html#panics&quot;&gt;can lead to surprising behavior&lt;/a&gt;,
so it’s generally avoided. Another gotcha is that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Drop::drop&lt;/code&gt; is synchronous
and today async cleanup doesn’t have a standard solution (&lt;a href=&quot;https://without.boats/blog/asynchronous-clean-up/&quot;&gt;1&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Compared to C++, I find Rust’s approach to this problem more consistent and
easier to reason about because it’s more explicit and more consistent across
different types of functions.&lt;/p&gt;

</description>
        <pubDate>Wed, 06 Mar 2024 00:00:00 +0000</pubDate>
        <link>https://www.bob-gardner.com/2024/03/06/cpp-function-try/</link>
        <guid isPermaLink="true">https://www.bob-gardner.com/2024/03/06/cpp-function-try/</guid>
        
        <category>cpp</category>
        
        
      </item>
    
      <item>
        <title>Memory Jogger Update</title>
        <description>&lt;p&gt;&lt;a href=&quot;https://github.com/rgardner/memory-jogger&quot;&gt;Memory Jogger&lt;/a&gt; is a program I wrote
to help me find interesting articles and videos I had saved in
&lt;a href=&quot;https://getpocket.com&quot;&gt;Pocket&lt;/a&gt;. I wrote about it previously &lt;a href=&quot;/2020/07/12/announcing-memory-jogger/&quot;&gt;in this post&lt;/a&gt;. It started as a program to
send me daily email digests of items I had previously saved that were related to
current events (via Google Trends data).&lt;/p&gt;

&lt;!-- more --&gt;

&lt;p&gt;Mostly it was to help me prioritize reading some of the thousands of unread
items that I’ve saved over the years.  But I also thought that it would make
current events more meaningful by reading historical context and commentary.
Sometimes it worked as you’d expect, for example, when Hamilton came out on
Disney+, it reminded me of the original New York Times article from when the
play came out in 2015. Unique words like “Pokemon” also returned relevant
results. But other times, the articles and videos in the daily email digest were
completely unrelated to the Google Trends results.&lt;/p&gt;

&lt;p&gt;This is mostly expected. I used the
&lt;a href=&quot;https://en.wikipedia.org/wiki/Tf%E2%80%93idf&quot;&gt;tf-idf&lt;/a&gt; algorithm I learned about
in my first internship to implement search. tf-idf helps when search terms
contain a mix of common and unique words, e.g. “Pokemon” is rare, but “The” is
very common, so “The Pokemon Game” would weight articles that contain Pokemon
higher than articles just about games (assuming “Pokemon” is less common than
“game” in the document collection).&lt;/p&gt;

&lt;p&gt;But the problem wasn’t the search relevance, it was how I use email. I try as
hard as possible to keep inbox zero, using Gmail inbox sections to triage emails
based on “Needs Action/Reply”, “Awaiting Reply”, “Scheduled”, and the rarely
used “Delegated.” But where do these email digests fit in? They don’t! I don’t
need a longer to-do list. Instead, I want “pull”, not “push”. Review these items
when I have time, but without the guilt and annoyance of push notifications and
“unread” messages. I thought for surfacing “trending” articles an email digest
was the right UI, but I didn’t think about how these emails would actually fit
into my life.&lt;/p&gt;

&lt;p&gt;For a positive surprise, I found I clicked on the irrelevant items as often as
the relevant ones. Even if the articles in the digest were completely unrelated
to what was trending at the time (same words, but different context), it was
still fun to read items I had saved years ago. Or, I knew I didn’t care about
that item and could just archive or delete it.&lt;/p&gt;

&lt;p&gt;So, in 2021, I’ve been using Memory Jogger completely differently. Instead of an
email digest that sends me notifications and adds to my to-do list, I wrote an
interactive program to just return random items from my Pocket, with commands to
quickly archive/delete/skip items. This interactive program interacts with the
email digest program via subprocesses and sharing a SQLite DB.&lt;/p&gt;

&lt;p&gt;Sometimes, I don’t even use the interactive program. I just load the database in
&lt;a href=&quot;https://sqlitebrowser.org/&quot;&gt;DB Browser for SQLite&lt;/a&gt; and then execute the
subprocesses manually, e.g.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;memory_jogger saved-items archive &lt;span class=&quot;nt&quot;&gt;--item-id&lt;/span&gt; &amp;lt;&lt;span class=&quot;nb&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;or even better, create an alias and then just run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mja &amp;lt;id&amp;gt;&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mjd &amp;lt;id&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I’ve been slowly cleaning up my Pocket and have enjoyed reading the articles and
watching the videos. No notifications, no “unread count”, no search algorithm,
just sync, SQLite, and RNG to guide me.&lt;/p&gt;
</description>
        <pubDate>Wed, 20 Oct 2021 00:00:00 +0000</pubDate>
        <link>https://www.bob-gardner.com/2021/10/20/memory-jogger-update/</link>
        <guid isPermaLink="true">https://www.bob-gardner.com/2021/10/20/memory-jogger-update/</guid>
        
        <category>personal</category>
        
        <category>tech</category>
        
        
      </item>
    
      <item>
        <title>Announcing Memory Jogger, relevant email digest for Pocket</title>
        <description>&lt;p&gt;I am excited to announce that I recently open sourced &lt;a href=&quot;https://github.com/rgardner/memory-jogger&quot;&gt;Memory
Jogger&lt;/a&gt;, an app that I built and
have been using for the past few months that emails me unread articles and
videos I’ve saved.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/memory_jogger_sample_email.png&quot; alt=&quot;Memory Jogger Sample Email Digest&quot; /&gt;&lt;/p&gt;

&lt;!-- more --&gt;

&lt;p&gt;From the &lt;a href=&quot;https://github.com/rgardner/memory-jogger/tree/7431e5339158dd250481a95f457f6a545fefae75#memory-jogger&quot;&gt;README&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Finds items from your &lt;a href=&quot;https://getpocket.com/&quot;&gt;Pocket&lt;/a&gt; library that are relevant to
trending news. I have thousands of unread Pocket items and Memory Jogger
enables me to find new meaning in articles and videos I saved years ago. I
deployed Memory Jogger to &lt;a href=&quot;https://www.heroku.com/&quot;&gt;Heroku&lt;/a&gt; and set up a
daily job to email me unread Pocket items based on &lt;a href=&quot;https://trends.google.com/trends/&quot;&gt;Google
Trends&lt;/a&gt; results from the past two days. Memory Jogger is
written in &lt;a href=&quot;https://www.rust-lang.org/&quot;&gt;Rust&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I love &lt;a href=&quot;https://getpocket.com/&quot;&gt;Pocket&lt;/a&gt; and have used it daily since 2013. I typically read
news sites or forums such as &lt;a href=&quot;https://news.ycombinator.com/&quot;&gt;Hacker News&lt;/a&gt; or
&lt;a href=&quot;https://www.reddit.com/&quot;&gt;Reddit&lt;/a&gt;, and the original articles and comment
sections frequently link to tons of interesting discussions or additional
information that I don’t have time for now but want to save for later. As of
the time of this writing, I have 5,575 unread items :) I sometimes search these
items on-demand, e.g. when researching something for work or fun, but other
times I wished there was a way to automatically surface relevant items as an
alternative to browsing Hacker News / Reddit. So I built Memory Jogger!&lt;/p&gt;

&lt;h2 id=&quot;product-decisions&quot;&gt;Product Decisions&lt;/h2&gt;

&lt;p&gt;I’ve had this idea on the back burner for years, but when I started this
project in February 2020, I still had questions about how I wanted to use an
app like this. I made a couple of key decisions in the beginning:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;email digest as the primary interaction model&lt;/li&gt;
  &lt;li&gt;hosted web service with simple UI, available for multiple users to sign up&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://trends.google.com/trends/&quot;&gt;Google Trends&lt;/a&gt; for determining what is “relevant”&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.heroku.com/&quot;&gt;Heroku&lt;/a&gt; and &lt;a href=&quot;https://www.postgresql.org/&quot;&gt;PostgreSQL&lt;/a&gt; database
backend&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.rust-lang.org/&quot;&gt;Rust&lt;/a&gt; for the implementation language&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I ended up revisiting some of those decisions later as well as making a few
other ones:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;multi-user capable, but no hosting for other people&lt;/li&gt;
  &lt;li&gt;web UI deprioritized given CLI is good enough for a single user&lt;/li&gt;
  &lt;li&gt;prioritize better local experience and
&lt;a href=&quot;https://sqlite.org/index.html&quot;&gt;SQLite&lt;/a&gt; support&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I quickly settled on a daily email digest that would be coordinated by a
program deployed on a server. I didn’t want to rely on my laptop being on as
I don’t use it daily. I’ve used Heroku for years for various side projects
(e.g. &lt;a href=&quot;https://github.com/rgardner/dancingtogether&quot;&gt;Dancing Together&lt;/a&gt;) so I knew what I was getting into
:)&lt;/p&gt;

&lt;p&gt;For determining what is “relevant,” I chose &lt;a href=&quot;https://trends.google.com/trends/&quot;&gt;Google Trends&lt;/a&gt;,
which tracks the most searched terms on Google over time. This is an area that
can be improved in the future (e.g. Twitter trends, topics you are following,
etc.), but Google Trends has been good enough for now. At first, I thought
about only returning random results, but my partner, Nicole, thought I may
enjoy reading timely articles more. That turned out to be a great decision!
And the funny thing is, because of false positives, some results are still
random, hilariously so.&lt;/p&gt;

&lt;p&gt;For personal projects, I try to be as agile as possible to stay motivated, so
here’s how I practiced that for Memory Jogger:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Investigate Google Trends API. There is no official API, but I found
&lt;a href=&quot;https://github.com/pat310/google-trends-api&quot;&gt;https://github.com/pat310/google-trends-api&lt;/a&gt; which has several hundred GitHub
stars and was easy enough to read. I downloaded it locally and played
around with it to figure out the HTTP request/responses and tried them out in
&lt;a href=&quot;https://www.postman.com/&quot;&gt;Postman&lt;/a&gt;. Google Trends will work :)&lt;/li&gt;
  &lt;li&gt;Create a Rust program to query Google Trends. At this step, I discovered
Google Trends prepends all HTTP response bodies with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;)]}&apos;,&lt;/code&gt; (probably to
make parsing harder for web scrapers?).&lt;/li&gt;
  &lt;li&gt;Add initial Pocket support. At this point, the program can query Google
Trends and use the search parameter on Pocket’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;retrieve&lt;/code&gt; API to find
relevant items. Yay! The proof-of-concept is complete.&lt;/li&gt;
  &lt;li&gt;Add initial CI. Now I submit changes via Pull Requests and use GitHub
Actions to build the program and run &lt;a href=&quot;https://github.com/rust-lang/rust-clippy&quot;&gt;Clippy&lt;/a&gt; on it.&lt;/li&gt;
  &lt;li&gt;Add frontend and deploy the app to Heroku. I chose &lt;a href=&quot;https://github.com/actix/actix-web&quot;&gt;actix-web&lt;/a&gt;
because I had recently used it for another personal project and wanted to
explore the actor concurrency paradigm more. The first server program just
had a single endpoint to proxy Google Trends requests so I didn’t have to
worry about authentication/authorization.&lt;/li&gt;
  &lt;li&gt;Improve email output based on experience reading the emails.&lt;/li&gt;
  &lt;li&gt;Start a project to improve relevant results. I started syncing Pocket items
to the database to enable custom text search. I learned about
&lt;a href=&quot;https://en.wikipedia.org/wiki/Tf%E2%80%93idf&quot;&gt;tf-idf&lt;/a&gt; during a college internship and was excited to try it out.&lt;/li&gt;
  &lt;li&gt;More email output improvements (add trend link, fallback URL because there
isn’t a stable URL to the Pocket Web interface for a given item)&lt;/li&gt;
  &lt;li&gt;Decide to prioritize running locally, so start cleaning up codebase (remove
web service, remove JS prototype code, simplify codebase)&lt;/li&gt;
  &lt;li&gt;Add SQLite support&lt;/li&gt;
  &lt;li&gt;Prepare to open source, writing docs and improving UI/UX as I go&lt;/li&gt;
  &lt;li&gt;Add integration tests that exercise everything but sending emails&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Because I prioritized the substantial part of the application first – syncing
Pocket items to the database, implementing text search, sending emails – I had
an MVP for myself quickly (within a few days). After using the MVP daily, I
prioritized improving search and tweaking the email/command line output, so
the app kept getting more useful for me.&lt;/p&gt;

&lt;p&gt;For example, at first, I relied on Pocket’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;retrieve&lt;/code&gt; API which includes a
search parameter. That API is limited to searching item titles and URLs and I
wanted to try other search algorithms to improve results, so I started
downloading saved items to the database. I used simple term-frequency search
on article titles and URLs to mimic the Pocket API. From there, I started
including article excerpts (found by Pocket) to the term frequency searching.&lt;/p&gt;

&lt;p&gt;After using it successfully for a few months, I decided against supporting
other users. While I was excited to share this project with other people, I
didn’t want to be responsible for their Pocket data. With that in mind, I
updated my priorities to make Memory Jogger easier to run locally by adding
support for &lt;a href=&quot;https://sqlite.org/index.html&quot;&gt;SQLite&lt;/a&gt; and making the email
portion of the app optional (e.g. to use OS notifications).&lt;/p&gt;

&lt;h2 id=&quot;notable-implementation-details&quot;&gt;Notable Implementation Details&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;The PostgreSQL and SQLite parts of the codebase “look” very similar, but use
different types. The implementations can probably be converged using generics
to some extent, but they’re not identical. For example, SQLite doesn’t support
a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RETURNING&lt;/code&gt; clause, so you need to perform a load immediately after
insertion (in the same transaction), whereas PostgreSQL supports returning
the inserted item.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;thank-you&quot;&gt;Thank You&lt;/h2&gt;

&lt;p&gt;Thank you to the contributors to &lt;a href=&quot;https://diesel.rs/&quot;&gt;diesel&lt;/a&gt;, &lt;a href=&quot;https://github.com/seanmonstar/reqwest&quot;&gt;reqwest&lt;/a&gt;,
&lt;a href=&quot;https://github.com/TeXitoi/structopt&quot;&gt;structopt&lt;/a&gt;, and &lt;a href=&quot;https://github.com/actions-rs&quot;&gt;actions-rs&lt;/a&gt;, which were the major
libraries that Memory Jogger relies on. In particular, each one has awesome
documentation which has made them simple to integrate.&lt;/p&gt;

</description>
        <pubDate>Sun, 12 Jul 2020 00:00:00 +0000</pubDate>
        <link>https://www.bob-gardner.com/2020/07/12/announcing-memory-jogger/</link>
        <guid isPermaLink="true">https://www.bob-gardner.com/2020/07/12/announcing-memory-jogger/</guid>
        
        <category>personal</category>
        
        <category>tech</category>
        
        
      </item>
    
      <item>
        <title>Photoshop 2020 Launch</title>
        <description>&lt;p&gt;I am excited to announce that Photoshop 2020 has shipped and is my first
desktop release since I joined the team in July! When you launch the
Photoshop 2020 app one of the initial loading screen lists the Photoshop team
members and, if the entropy is just right, you’ll see my name listed (it
randomizes the non-leadership team members).&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/photoshop_2020_splash_my_name.png&quot; alt=&quot;Photoshop 2020 Splash Screen&quot; /&gt;&lt;/p&gt;

&lt;!-- more --&gt;

&lt;p&gt;Alternatively, the Photoshop team members can be listed via &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Photoshop &amp;gt; About Photoshop...&lt;/code&gt; on macOS.&lt;/p&gt;

&lt;p&gt;Because of my work on the Photoshop Imaging Engine (PIE) team, I also have
contributed changes to the new &lt;a href=&quot;https://apps.apple.com/us/app/adobe-fresco-draw-and-paint/id1458660369&quot;&gt;Adobe
Fresco&lt;/a&gt;
app that recently launched on iPad and UWP. Notably I contributed
locale-aware, case-insensitive, digits-as-numbers sorting to the cloud
documents organizer, among a number of bug fixes. It’s an exciting time to be
contributing to the Photoshop ecosystem!&lt;/p&gt;
</description>
        <pubDate>Mon, 06 Jan 2020 00:00:00 +0000</pubDate>
        <link>https://www.bob-gardner.com/2020/01/06/photoshop-2020-launch/</link>
        <guid isPermaLink="true">https://www.bob-gardner.com/2020/01/06/photoshop-2020-launch/</guid>
        
        <category>work</category>
        
        
      </item>
    
      <item>
        <title>Web Scraping with Jupyter Notebooks</title>
        <description>&lt;p&gt;Last year I worked on a web scraper to automatically download &lt;a href=&quot;https://creativemarket.com/&quot;&gt;Creative
Market&lt;/a&gt;’s Free Goods of the Week. Each week,
Creative Market sends an email to subscribers to download these 6 free
assets. Subscribers then log in, go to
&lt;a href=&quot;https://creativemarket.com/free-goods&quot;&gt;https://creativemarket.com/free-goods&lt;/a&gt;, and then click one or both of the
“Sync to Dropbox” or “Free Download” buttons. My partner was manually doing
this each day and when I was looking for a software project to work on, this
seemed like a good candidate for automation that would remove a small, but
minor inconvenience of hers.&lt;/p&gt;

&lt;!-- more --&gt;

&lt;p&gt;The GitHub repo is here: &lt;a href=&quot;https://github.com/rgardner/design-asset-utils&quot;&gt;https://github.com/rgardner/design-asset-utils&lt;/a&gt;. It
has been archived because Creative Market has since disabled scrapers from
accessing their site. Their whole website (not just the log in page), now
requires passing a Google reCAPTCHA test if it suspects an automated system
is being used. For what it’s worth, their &lt;a href=&quot;https://creativemarket.com/terms&quot;&gt;terms of
service&lt;/a&gt; seem to allow automated systems as
long as they are using the service fairly (e.g. no resharing assets, use rate
limiting, and no malware). But I knew they could break web scraping anyways.
c’est la vie. While this project is dead, I thought the technique could be
valuable to others, hence the blog post.&lt;/p&gt;

&lt;h2 id=&quot;web-scraping&quot;&gt;Web Scraping&lt;/h2&gt;

&lt;p&gt;I chose Python and Jupyter Notebooks because I wanted an interactive
development experience when working on the web scraper. I had worked on web
scrapers in the past (e.g. &lt;a href=&quot;https://github.com/rgardner/citi-bike-notifier&quot;&gt;https://github.com/rgardner/citi-bike-notifier&lt;/a&gt;)
and I found the development experience painful.&lt;/p&gt;

&lt;p&gt;First, when the website UI changes, you need to be able to see the contents
of the new page to be able to diagnose and fix the scraping code. For
example, an error like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;selenium.common.exceptions.NoSuchElementException: Message: no such element: Unable to locate element: {&quot;method&quot;:&quot;id&quot;,&quot;selector&quot;:&quot;input-username&quot;}&lt;/code&gt; doesn’t tell you if the element
has simply been renamed or if you’ve been redirected to a completely
different page.&lt;/p&gt;

&lt;p&gt;Second, to reduce the dev inner loop, you want to be able to cache the state
of the website to avoid re-logging in every time, or slowly clicking buttons
in the correct order. So you run the script under a debugger, make the fix,
and then re-run the script from the beginning. But with Jupyter Notebooks,
you can edit and continue, edit the same cell repeatedly, or execute the
cells out of order. Jupyter Notebooks can also display images inline, which
makes it easy to take and display a screenshot when a scraping exception
occurs.&lt;/p&gt;

&lt;p&gt;My Jupyter notebook is here:
&lt;a href=&quot;https://github.com/rgardner/design-asset-utils/blob/master/creative-market/downloader.ipynb&quot;&gt;downloader.ipynb&lt;/a&gt;.
It uses environment variables to receive input (following the &lt;a href=&quot;https://12factor.net/&quot;&gt;12 Factor
Methodology&lt;/a&gt;), enables verbose logging to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;stdout&lt;/code&gt;,
which displays after each cell in the notebook, and defines a function
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;log_error&lt;/code&gt; to display a screenshot.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-python3&quot;&gt;def log_error(driver):
    display(Image(driver.get_screenshot_as_png()))
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;I use it like this:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-python3&quot;&gt;try:
    driver.login(CREATIVE_MARKET_USERNAME, CREATIVE_MARKET_PASSWORD)
except WebDriverException:
    log_error(driver)
    raise
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Note the screenshot does not display when running from the command line like so:&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;jupyter nbconvert &lt;span class=&quot;nt&quot;&gt;--execute&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--stdout&lt;/span&gt; downloader.ipynb
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;For this project, I put some web scraping code that I intended to share with
a &lt;a href=&quot;https://github.com/rgardner/design-asset-utils/blob/ea1ebd330c07a2a1fcadf12181bb2b2695e04bc3/creative-market/checker.py&quot;&gt;test
script&lt;/a&gt;
into a common Python module called
&lt;a href=&quot;https://github.com/rgardner/design-asset-utils/blob/ea1ebd330c07a2a1fcadf12181bb2b2695e04bc3/creative-market/creative_market.py&quot;&gt;creative_market.py&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;putting-it-all-together&quot;&gt;Putting it all together&lt;/h2&gt;

&lt;p&gt;To run the notebook non-interactively, use:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-python3&quot;&gt;jupyter nbconvert --execute --stdout downloader.ipynb
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This app was deployed to Heroku and only needs to run once per week. Heroku
doesn’t have an easy way of scheduling jobs, so I wrote a module called
&lt;a href=&quot;https://github.com/rgardner/design-asset-utils/blob/ea1ebd330c07a2a1fcadf12181bb2b2695e04bc3/creative-market/clock.py&quot;&gt;clock.py&lt;/a&gt;
which uses &lt;a href=&quot;https://apscheduler.readthedocs.io/en/stable/&quot;&gt;APScheduler&lt;/a&gt; to
schedule the downloader to run every Monday:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-python3&quot;&gt;import subprocess

from apscheduler.schedulers.blocking import BlockingScheduler

SCHEDULER = BlockingScheduler()


@SCHEDULER.scheduled_job(&apos;cron&apos;, day_of_week=&apos;mon&apos;, hour=17)
def scheduled_download():
    print(&apos;Scheduling downloader...&apos;)
    return subprocess.run(
        [&apos;jupyter&apos;, &apos;nbconvert&apos;, &apos;--execute&apos;, &apos;--stdout&apos;, &apos;downloader.ipynb&apos;])
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;I also wrote a &lt;a href=&quot;https://github.com/rgardner/design-asset-utils/blob/ea1ebd330c07a2a1fcadf12181bb2b2695e04bc3/creative-market/checker.py&quot;&gt;test
script&lt;/a&gt;
to scrape Creative Market and confirm that the links were successfully
clicked, if not, notifying me via email. That is scheduled to run a few
minutes after.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;The full project is here: &lt;a href=&quot;https://github.com/rgardner/design-asset-utils&quot;&gt;https://github.com/rgardner/design-asset-utils&lt;/a&gt;. I
hope you find it useful!&lt;/p&gt;
</description>
        <pubDate>Sat, 04 Jan 2020 00:00:00 +0000</pubDate>
        <link>https://www.bob-gardner.com/2020/01/04/jupyter-web-scraping/</link>
        <guid isPermaLink="true">https://www.bob-gardner.com/2020/01/04/jupyter-web-scraping/</guid>
        
        <category>python</category>
        
        
      </item>
    
      <item>
        <title>Windows 10 Launch</title>
        <description>&lt;p&gt;Last week I attended the awesome Windows &amp;amp; Devices Group scavenger hunt at
Seattle’s EMP Museum. One of the exhibits there featured the amazing work of
Chuck Jones, an animator, cartoon artist, and more from Spokane Washington. I
grew up watching Bugs Bunny, Daffy Duck, and Road Runner cartoons on Boomerang,
and it was pretty cool to learn more about the man behind them. At the exhibit,
there was one of his quotes on the wall:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;All great endeavors are 90% hard work and 10% love and only the love should
show&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I’ve been running Windows 10 since day 1 of my internship, through the good
builds and the bad (builds are just versions of the OS). It’s come a long
way in the two months I’ve been here and I’m very excited for its road ahead.&lt;/p&gt;

&lt;p&gt;Windows 10 releases today. My hope is that only the love shows.&lt;/p&gt;
</description>
        <pubDate>Wed, 29 Jul 2015 00:00:00 +0000</pubDate>
        <link>https://www.bob-gardner.com/2015/07/29/microsoft-windows-10-launch/</link>
        <guid isPermaLink="true">https://www.bob-gardner.com/2015/07/29/microsoft-windows-10-launch/</guid>
        
        <category>Microsoft</category>
        
        <category>Windows</category>
        
        
      </item>
    
      <item>
        <title>Raspberry Pi Console Cable</title>
        <description>&lt;p&gt;Where have you been all my life.&lt;/p&gt;

&lt;p&gt;That’s how this cable makes me feel. Let me show you a magical cable that
allows you to log into the Raspberry Pi shell from your computer, be it
Windows, Mac, or Linux. If you are connecting to a Raspberry Pi 1 model, then
this is all you need. For the Raspberry Pi 2, you will need an external power
supply to power the Raspberry Pi, as the console cable does not supply enough
power.&lt;/p&gt;

&lt;!-- more --&gt;

&lt;p&gt;&lt;img src=&quot;https://learn.adafruit.com/system/assets/assets/000/003/119/original/learn_raspberry_pi_console_cable.jpg?1396791620&quot; alt=&quot;Raspberry Pi Console
Cable&quot; /&gt;&lt;/p&gt;

&lt;p&gt;That’s it. Four pins on one side, a standard USB 2.0 plug on the other end.
After at most 5 minutes of software installation, you can now connect the
console cable to your computer and Raspberry Pi and voilà, you can now log in
to your Raspberry Pi on your laptop.&lt;/p&gt;

&lt;p&gt;I won’t go into much detail here on how to get this amazing setup working with
your hardware, the &lt;a href=&quot;https://learn.adafruit.com/adafruits-raspberry-pi-lesson-5-using-a-console-cable/overview&quot;&gt;Adafruit
tutorial&lt;/a&gt;
on this topic does a much better job.  But what I will say, is that after
installing the drivers and connecting the console cable to my Raspberry Pi and
laptop, I only have to run the command:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;screen /dev/cu.usbserial 115200
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;to connect to my Raspberry Pi from my Mac.&lt;/p&gt;

&lt;p&gt;After a semester and a half of pain in my energy research because of networking
issues and having to find the rest of the computer - keyboard, mouse, display,
and cables - this little cable is a godsend.&lt;/p&gt;

&lt;p&gt;Here are some cases where the cable is already helping me:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;I set up a new Raspberry Pi without a keyboard and monitor.&lt;/li&gt;
  &lt;li&gt;I misconfigured the network interface on the Raspberry Pi and couldn’t
connect via SSH, so console cable to the rescue!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I am a big fan of Adafruit, so &lt;a href=&quot;https://www.adafruit.com/products/954&quot;&gt;here’s a link to buy it from
them&lt;/a&gt;. You can also find similar cables
across the net, but YMMV. While you don’t need this cable to use the Raspberry
Pi, it allows me to fix problems faster and get on with my research.&lt;/p&gt;
</description>
        <pubDate>Wed, 10 Jun 2015 00:00:00 +0000</pubDate>
        <link>https://www.bob-gardner.com/2015/06/10/raspberry-pi-console-cable/</link>
        <guid isPermaLink="true">https://www.bob-gardner.com/2015/06/10/raspberry-pi-console-cable/</guid>
        
        <category>nikola</category>
        
        <category>raspberry-pi</category>
        
        
      </item>
    
      <item>
        <title>Microsoft 2015 Summer Internship</title>
        <description>&lt;p&gt;&lt;img src=&quot;/assets/microsoft-logo.png&quot; alt=&quot;Microsoft 2014 Logo&quot; title=&quot;Microsoft&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I am very excited to announce that I will be joining Microsoft’s Core Group
this summer. I do not know what specific team I will be working with, but I
hope to work in their Internet of Things or Windows OS groups.
&lt;!-- more --&gt;&lt;/p&gt;

&lt;p&gt;It’s been a very exciting year for Microsoft. Just this week they open sourced
the &lt;a href=&quot;https://github.com/dotnet/coreclr&quot;&gt;.Net Core runtime&lt;/a&gt;, which has builds
passing on both Windows and &lt;strong&gt;Linux&lt;/strong&gt;, with &lt;strong&gt;Mac OS X&lt;/strong&gt; support coming later
this year. They also announced a partnership with the Raspberry Pi Foundation
to allow you to run Windows 10 on the Raspberry Pi &lt;strong&gt;for free!&lt;/strong&gt; To my
understanding, you can develop applications on Windows desktop and then deploy
them to Raspberry Pi’s. As someone going through the process of developing
&lt;a href=&quot;https://github.com/project-nikola&quot;&gt;Python applications&lt;/a&gt; on a Mac, testing them
on Windows, and deploying them to a Raspberry Pi running Raspbian, I can tell
you that an easy deployment system sounds very attractive.&lt;/p&gt;
</description>
        <pubDate>Sat, 07 Feb 2015 00:00:00 +0000</pubDate>
        <link>https://www.bob-gardner.com/2015/02/07/ms/</link>
        <guid isPermaLink="true">https://www.bob-gardner.com/2015/02/07/ms/</guid>
        
        <category>personal</category>
        
        
      </item>
    
      <item>
        <title>Why Technology and Liberal Arts?</title>
        <description>&lt;p&gt;Through secondary school I loved all of the subjects. But inevitably, there
were days where homework was not what I felt like doing. On these days, I found
myself using homework from one subject to avoid the homework from another. Math
would trump Spanish, and Spanish would pummel English. While eventually it all
was all complete, I enjoyed watching as my interests became apparent in my
decisions.
&lt;!-- more --&gt;&lt;/p&gt;

&lt;p&gt;Despite avoiding my English homework, I really did enjoy the class. I loved
reading books like &lt;em&gt;The Kite Runner&lt;/em&gt;, &lt;em&gt;Reservation Blues&lt;/em&gt;, and &lt;em&gt;We&lt;/em&gt;. Speaking
of which, if you haven’t read &lt;em&gt;We&lt;/em&gt; by Yevgeny Zamyatin, I highly recommend it.
It’s a dystopian future novel commenting on the Russian revolutions of the
early twentieth century (&lt;em&gt;We&lt;/em&gt; predated &lt;em&gt;1984&lt;/em&gt;, &lt;em&gt;Brave New World&lt;/em&gt;, more well
known books in the genre).&lt;/p&gt;

&lt;p&gt;I feel fortunate to have enjoyed my classes and my schooling in general. And
beyond just school, I am most grateful that I love &lt;strong&gt;learning&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;When it was time for me to start deciding what my academic focus would be in
college, I knew the most important factor would be that the subject would
continue to evolve and generate more information for me to learn.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Always be learning&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Ideally, work would mix constrained and unconstrained problems,
forcing me to think creatively both in a small problem space and when
confronted with a blank canvas. So I found computer science and economics.&lt;/p&gt;

&lt;p&gt;Found isn’t the right word; these were the two fields that I spent the most
time outside of school learning about. They also shaped some of the electives I
took, Introduction to Programming and Introduction to Computer Science, and
what I would focus on when I had more flexible homework assignments.&lt;/p&gt;

&lt;p&gt;Learning is at the heart of computer science and economics. As the fields grow,
researchers discover more applications of their work affecting many other
fields. For me, I find both of these fields the most interesting when they are
applied to other research areas - but especially so when applied to each other.&lt;/p&gt;

&lt;p&gt;This past January, I read a collection of essays on open source software
development entitled &lt;a href=&quot;http://www.catb.org/esr/writings/cathedral-bazaar/&quot;&gt;&lt;em&gt;The Cathedral and the
Bazaar&lt;/em&gt;&lt;/a&gt; by Eric S. Raymond
(ESR). Named after the essay of the same name, his essays contrast two models
of software development: proprietary (cathedral) and open source (bazaar). In
many of his essays, ESR dives into the economics of software. Software is
fundamentally different than most other products sold on the market. Software
can have a high development cost, but will cost close to nothing to reproduce.
Some software is even incredibly cheap to develop. Think of the number of
weekend projects that make it into the App Store, &lt;a href=&quot;https://developer.apple.com/appstore/resources/approval/guidelines.html&quot;&gt;which Apple is cracking down
on&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Economics is all about making decisions while facing constraints. Money, time,
memory, clock cycles, economic growth, latency, storage costs - don’t forget
about gravity! Economic modeling starts with generating realistic constraints
that ultimately determine the accuracy of your predictions.&lt;/p&gt;

&lt;p&gt;Economics and computer science are not actually that different from each other.&lt;/p&gt;

&lt;p&gt;The best thing about both of these fields is that they change the way you
think. Tyler Cowen explains in &lt;em&gt;An Economist Gets Lunch&lt;/em&gt; why food quality in
the US decreased during prohibition and why great barbecue restaurants also
serve excellent breakfasts. In &lt;em&gt;The UNIX Philosophy&lt;/em&gt;, Mike Gancarz draws upon
Ken Thompson and others to distill &lt;a href=&quot;https://en.wikipedia.org/wiki/Unix_philosophy#Mike_Gancarz:_The_UNIX_Philosophy&quot;&gt;nine essential key
points&lt;/a&gt;
for adherents to the Unix philosophy.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;ol&gt;
    &lt;li&gt;Small is beautiful.&lt;/li&gt;
    &lt;li&gt;Make each program do one thing well.&lt;/li&gt;
    &lt;li&gt;Build a prototype as soon as possible.&lt;/li&gt;
    &lt;li&gt;Choose portability over efficiency.&lt;/li&gt;
    &lt;li&gt;Store data in flat text files.&lt;/li&gt;
    &lt;li&gt;Use software leverage to your advantage.&lt;/li&gt;
    &lt;li&gt;Use shell scripts to increase leverage and portability.&lt;/li&gt;
    &lt;li&gt;Avoid captive user interfaces.&lt;/li&gt;
    &lt;li&gt;Make every program a filter.&lt;/li&gt;
  &lt;/ol&gt;
&lt;/blockquote&gt;

&lt;p&gt;Each of these precepts influences the way I approach problems, both in software
development and in my day-to-day life.&lt;/p&gt;

&lt;p&gt;This is the power of economics and computer science: both affect the way I
think about and approach problems.&lt;/p&gt;

&lt;p&gt;To close, here are three key points from the &lt;a href=&quot;http://www.catb.org/esr/faqs/hacker-howto.html&quot;&gt;How To Become a
Hacker&lt;/a&gt; appendix to ESR’s
&lt;em&gt;Cathedral&lt;/em&gt;. I argue that these ideas are ingrained in economists and computer
scientists alike. They are also what excite me most about my future in both of
these fields.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;ul&gt;
    &lt;li&gt;The World Is Full of Fascinating Problems Waiting To Be Solved&lt;/li&gt;
    &lt;li&gt;Nobody Should Ever Have To Solve A Problem Twice&lt;/li&gt;
    &lt;li&gt;Boredom And Drudgery Are Evil&lt;/li&gt;
  &lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;em&gt;Always be learning.&lt;/em&gt;&lt;/p&gt;
</description>
        <pubDate>Thu, 31 Jul 2014 00:00:00 +0000</pubDate>
        <link>https://www.bob-gardner.com/2014/07/31/why-technology-and-liberal-arts/</link>
        <guid isPermaLink="true">https://www.bob-gardner.com/2014/07/31/why-technology-and-liberal-arts/</guid>
        
        <category>personal</category>
        
        
      </item>
    
      <item>
        <title>Meatless January</title>
        <description>&lt;p&gt;During January 2014, I will not be eating meat. More specifically, I will be a
lacto-ovo-pescetarian, meaning I will still consume dairy, eggs, and fish. When
I first indicated interest in this at the end of 2013, I received mixed
reactions. Some strongly for, others neutral, and a surprising amount of people
who were strongly against. I want to begin this discussion by explaining why I
am doing this and will conclude with an explanation for the responses I
received.
&lt;!-- more --&gt;&lt;/p&gt;

&lt;h2 id=&quot;why-am-i-doing-this&quot;&gt;Why am I doing this?&lt;/h2&gt;

&lt;p&gt;I want to eat better. I do not consume an unhealthy amount of steak, chicken,
and bacon. Rather, I resist experimenting with other foods because of the
comfort meats provide me.&lt;/p&gt;

&lt;p&gt;Many of us have seen the videos of turkeys being mistreated. Cows that never
leave the factory, chickens and pigs so large that they can no longer walk.
This hasn’t been enough of a deterrent for me to stop eating these animals.
It’s not because I don’t care about the way these animals are treated, but
rather I don’t have faith in the ‘organic’ and ‘grass-fed’ labels on these
foods.&lt;/p&gt;

&lt;h2 id=&quot;reactions&quot;&gt;Reactions&lt;/h2&gt;

&lt;p&gt;From vegetarians, all of the people I spoke to understand my reasoning and
supported my choice. They stressed the importance of meal planning and getting
all of the required vitamins and minerals. All understood my hesitation for a
larger commitment.&lt;/p&gt;

&lt;p&gt;From my friends who were upset with my choice, there were two common concerns:
(1) my health, and (2) future indoctrination from vegetarians. For my health
concerns, I shared my eating plans and the foods that would help to make up for
the lost vitamins and minerals. For the second camp, I explained that I would
not pressure others nor would I look down upon my meat-eating counterparts.
While most of the vegetarians I know have never chastised me for my eating
choices, there have been at least two in my life that have become upset with me
or my friends when dining out or discussing vegetarianism. It is this vocal
minority that gives a bad rap to vegetarians.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;Will I continue this into February and beyond? I’m not sure. At the moment, I
am liking its effect on my body. I am eating more fish, eggs, and fruit, the
chief goals of this experiment. The occasional headaches remind me when I need
to eat more protein and the other important amino acids that meats would
normally provide me. As I do more research and adapt my diet to make up for
these lost nutrients, it becomes easier to think about continuing this meatless
streak. If anything, just to learn more about my own body.&lt;/p&gt;
</description>
        <pubDate>Wed, 08 Jan 2014 00:00:00 +0000</pubDate>
        <link>https://www.bob-gardner.com/2014/01/08/meatless-january/</link>
        <guid isPermaLink="true">https://www.bob-gardner.com/2014/01/08/meatless-january/</guid>
        
        <category>personal</category>
        
        
      </item>
    
  </channel>
</rss>
