{"id":1536,"date":"2019-08-06T09:18:43","date_gmt":"2019-08-06T01:18:43","guid":{"rendered":"https:\/\/www.strongd.net\/?p=1536"},"modified":"2019-08-06T09:18:43","modified_gmt":"2019-08-06T01:18:43","slug":"one-program-written-in-python-go-and-rust","status":"publish","type":"post","link":"https:\/\/www.strongd.net\/?p=1536","title":{"rendered":"One Program Written in Python, Go, and Rust"},"content":{"rendered":"<p><img decoding=\"async\" src=\"https:\/\/www.nicolas-hahn.com\/images\/program-in-python-go-rust\/python-go-rust.png\" alt=\"Python, Go, Rust mascots\" \/><\/p>\n<p><strong>Update (2019-07-04):<\/strong>\u00a0Some kind folks have suggested changes on the implementations to make them more idiomatic, so the code here may differ from what\u2019s currently in the repos.<\/p>\n<hr \/>\n<p><em>This is a subjective, primarily developer-ergonomics-based comparison of the three languages from the perspective of a Python developer, but you can skip the prose and go to\u00a0<a href=\"https:\/\/www.nicolas-hahn.com\/python\/go\/rust\/programming\/2019\/07\/01\/program-in-python-go-rust\/#code-samples\">the code samples<\/a>,\u00a0<a href=\"https:\/\/www.nicolas-hahn.com\/python\/go\/rust\/programming\/2019\/07\/01\/program-in-python-go-rust\/#performance\">the performance comparison<\/a>\u00a0if you want some hard numbers,\u00a0<a href=\"https:\/\/www.nicolas-hahn.com\/python\/go\/rust\/programming\/2019\/07\/01\/program-in-python-go-rust\/#the-takeaway\">the takeaway<\/a>\u00a0for the tl;dr, or the\u00a0<a href=\"https:\/\/github.com\/nicolashahn\/diffimg\">Python<\/a>,\u00a0<a href=\"https:\/\/github.com\/nicolashahn\/diffimg-go\">Go<\/a>, and\u00a0<a href=\"https:\/\/github.com\/nicolashahn\/diffimg-rs\">Rust<\/a>\u00a0<code class=\"highlighter-rouge\">diffimg<\/code>\u00a0implementations.<\/em><\/p>\n<p>A few years ago, I was tasked with rewriting an image processing service. To tell whether my new service was creating the same output as the old given an image and one or more transforms (resize, make a circular crop, change formats, etc.), I had to inspect the images myself. Clearly I needed to automate this, but I could find no existing Python library that simply told me how different two images were on a per-pixel basis. Hence\u00a0<a href=\"https:\/\/github.com\/nicolashahn\/diffimg\">diffimg<\/a>, which can give you a difference ratio\/percentage, or generate a diff image (check out the readme to see an example).<\/p>\n<p>The initial implementation was in Python (the language I\u2019m most comfortable in), with the heavy lifting done by\u00a0<a href=\"https:\/\/pillow.readthedocs.io\/en\/stable\/\">Pillow<\/a>. It\u2019s usable as a library or a command line tool. The actual\u00a0<a href=\"https:\/\/github.com\/nicolashahn\/diffimg\/blob\/master\/diffimg\/diff.py\">meat<\/a>of the program is very small, only a few dozen lines, thanks to Pillow. Not a lot of effort went into building this tool (<a href=\"https:\/\/xkcd.com\/353\/\">xkcd was right<\/a>, there\u2019s a Python module for nearly everything), but it\u2019s at least been useful for a few dozen people other than myself.<\/p>\n<p>A few months ago, I joined a company that had several services written in Go, and I needed to get up to speed quickly on the language. Writing\u00a0<a href=\"https:\/\/github.com\/nicolashahn\/diffimg-go\">diffimg-go<\/a>\u00a0seemed like an fun and possibly even useful way to do this. Here are a few points of interest that came out of the experience, along with some that came up while using it at work:<\/p>\n<h2 id=\"comparing-python-and-go\">Comparing Python and Go<\/h2>\n<p>(Again, the code:\u00a0<a href=\"https:\/\/github.com\/nicolashahn\/diffimg\">diffimg<\/a>\u00a0(python) and\u00a0<a href=\"https:\/\/github.com\/nicolashahn\/diffimg-go\">diffimg-go<\/a>)<\/p>\n<ul>\n<li><strong>Standard Library<\/strong>: Go comes with a decent\u00a0<a href=\"https:\/\/golang.org\/pkg\/image\/\">image<\/a>\u00a0standard library module, as well as a command line\u00a0<a href=\"https:\/\/golang.org\/pkg\/flag\/\">flag<\/a>\u00a0parsing library. I didn\u2019t need to look for any external dependencies; the\u00a0<code class=\"highlighter-rouge\">diffimg-go<\/code>\u00a0implementation has none, where the Python implementation uses the fairly heavy third party module (ironically) named Pillow. Go\u2019s standard library in general is more structured and well thought out, while Python\u2019s is organically evolved, created by many authors over years, with many differing conventions. The Go standard library\u2019s consistency makes it easier to predict how any given module will function, and the source code is extremely well documented.\n<ul>\n<li>One downside of using the standard image library is that it does not automatically detect if the image has an alpha channel; pixel values have four channels (RGBA) for all image types. The\u00a0<code class=\"highlighter-rouge\">diffimg-go<\/code>\u00a0implementation therefore requires the user to indicate whether or not they want to use the alpha channel. This small inconvenience wasn\u2019t worth finding a third party library to fix.<\/li>\n<li>One big upside is that there\u2019s enough in the standard library that you don\u2019t need a web framework like Django. It\u2019s possible to build a real, usable web service in Go without any dependencies. Python\u2019s claim is that it\u2019s batteries-included, but Go does it better, in my opinion.<\/li>\n<\/ul>\n<\/li>\n<li><strong>Static Type System<\/strong>: I\u2019ve used statically typed languages in the past, but my programming for the past few years has mostly been in Python. The experience was somewhat annoying at first, it felt as though it was simply slowing me down and forcing me to be excessively explicit whereas Python would just let me do what I wanted, even if I got it wrong occasionally. Somewhat like giving instructions to someone who always stops you to ask you to clarify what you mean, versus someone who always nods along and seems to understand you, though you\u2019re not always sure they\u2019re absorbing everything. It will decrease the amount of type-related bugs for free, but I\u2019ve found that I still need to spend nearly the same amount of time writing tests.\n<ul>\n<li>One of the common complaints of Go is that it does not have user-implementable generic types. While this is not a must-have feature for building a large, extensible application, it certainly slows development speed.\u00a0<a href=\"https:\/\/appliedgo.net\/generics\/\">Alternative patterns<\/a>\u00a0have been suggested, but none of them are as effective as having real generic types.<\/li>\n<li>One upside of the static type system is that it reading through an unfamiliar codebase is easier and faster. Good use of types imbues a lot of extra information that is lost with a dynamic type system.<\/li>\n<\/ul>\n<\/li>\n<li><strong>Interfaces and Structs<\/strong>: Go uses interfaces and structs where Python would use classes. This was probably the most interesting difference to me, as it forced me to differentiate the concept of a type that defines behavior versus a type that holds information. Python and other \u201ctraditionally object-oriented\u201d languages would encourage you to mash these together, but there are pros and cons to both paradigms:\n<ul>\n<li>Go heavily encourages\u00a0<a href=\"https:\/\/en.wikipedia.org\/wiki\/Composition_over_inheritance\">composition over inheritance<\/a>. While it has\u00a0<a href=\"https:\/\/golang.org\/doc\/effective_go.html#embedding\">inheritance via embedding<\/a>, without classes, it\u2019s not as easy to forward both data and methods. I generally agree that composition is the better default pattern to reach for, but I\u2019m not an absolutist and some situations are a better fit for inheritance, so I\u2019d prefer not to have the language make this decision for me.<\/li>\n<li>Divorcing implementations for interfaces means you need to write similar code several times if you have many types that are similar to each other. Because of the lack of generic types, there are situations in Go where I wouldn\u2019t be able to reuse code, though I would in Python.<\/li>\n<li>However, because Go is statically typed, the compiler\/linter will tell you when you\u2019re writing code that would have caused a runtime error in Python when you try to access a method or attribute that may not exist. Python linters can get a bit of this functionality, but because of the language\u2019s dynamicity, the linter can\u2019t know exactly what methods\/attributes will exist until runtime. Statically defined interfaces and structs are the only way to know what\u2019s available at compile time and during development, making Go that compiles more trustworthy than Python that runs.<\/li>\n<\/ul>\n<\/li>\n<li><strong>No Optional Arguments<\/strong>: Go only has\u00a0<a href=\"https:\/\/gobyexample.com\/variadic-functions\">variadic functions<\/a>\u00a0which are similar to Python\u2019s keyword arguments, but less useful, since the arguments need to be of the same type. I found keyword arguments to be something I really missed, mainly for how much easier refactoring is if you can just throw a\u00a0<code class=\"highlighter-rouge\">kwarg<\/code>\u00a0of any type onto whatever function needs it without having to rewrite every one of its calls. I use this feature quite often in at work, it\u2019s saved me a lot of time over the years. Not having the feature made my implementation for how to handle whether or not the diff image should be created based on the command line flags somewhat clumsy.<\/li>\n<li><strong>Verbosity<\/strong>: Go is a bit more verbose (though not Java verbose). Part of that is because type system does not have generics, but mainly the fact that the language itself is very small and not heavily loaded with features (you only get\u00a0<a href=\"https:\/\/tour.golang.org\/flowcontrol\/1\">one looping construct!<\/a>). I missed having Python\u2019s list comprehensions and other functional programming features. If you\u2019re comfortable with Python, you can go through the\u00a0<a href=\"https:\/\/tour.golang.org\/welcome\/1\">Tour of Go<\/a>\u00a0in a day or two, and you\u2019ll have been exposed to the entirety of the language.<\/li>\n<li><strong>Error Handling<\/strong>: Python has exceptions, whereas Go propagates errors by returning tuples:\u00a0<code class=\"highlighter-rouge\">value, error<\/code>\u00a0from functions wherever something may go wrong. Python lets you catch errors at any point in the call stack as opposed to requiring you to manually pass them back up over and over again. This again results in brevity and code that isn\u2019t littered with Go\u2019s infamous\u00a0<code class=\"highlighter-rouge\">if err != nil<\/code>\u00a0pattern, though you do need to be aware of what possible exceptions can be thrown by a function and all(!) of its internal calls (using\u00a0<code class=\"highlighter-rouge\">except Exception:<\/code>\u00a0is a usually-bad-practice workaround for this). Good docstrings and tests can help here, which you should be writing in either language. Go\u2019s system is definitely safer. You\u2019re still allowed to shoot yourself in the foot by ignoring the\u00a0<code class=\"highlighter-rouge\">err<\/code>\u00a0value, but the system makes it obvious that this is a bad idea.<\/li>\n<li><strong>Third Party Modules<\/strong>: Prior to\u00a0<a href=\"https:\/\/blog.golang.org\/modules2019\">Go modules<\/a>, Go\u2019s package manager would just throw all downloaded packages into\u00a0<code class=\"highlighter-rouge\">$GOPATH\/src<\/code>\u00a0instead of the project\u2019s directory (like most other languages). The path for these modules inside\u00a0<code class=\"highlighter-rouge\">$GOPATH<\/code>\u00a0would also be built from the URL where the package is hosted, so your import would look something like\u00a0<code class=\"highlighter-rouge\">import \"github.com\/someuser\/somepackage\"<\/code>. Embedding\u00a0<code class=\"highlighter-rouge\">github.com<\/code>inside the source code of almost all Go codebases seems like a strange choice. In any case, Go now allows the conventional way of doing things, but Go modules are still new so this quirk will remain common in wild Go code for some time.<\/li>\n<li><strong>Asynchronicity<\/strong>: Goroutines are a very convenient way to fire off asynchronous tasks. Before\u00a0<code class=\"highlighter-rouge\">async\/await<\/code>, Python\u2019s asynchronous solutions were somewhat hairy. Unfortunately I haven\u2019t written much real-world async code in Python or Go, and the simplicity of\u00a0<code class=\"highlighter-rouge\">diffimg<\/code>\u00a0didn\u2019t seem to lend itself to the added overhead of asynchronicity, so I don\u2019t have too much to say here, though I do like Go\u2019s\u00a0<a href=\"https:\/\/gobyexample.com\/channels\">channels<\/a>\u00a0as a way to handle multiple async tasks. My understanding is that for performance, Go still has the upper hand here as goroutines can make use of full multiprocessor parallelism, where Python\u2019s basic\u00a0<code class=\"highlighter-rouge\">async\/await<\/code>\u00a0is still stuck on one processor, so mainly useful for I\/O bound tasks.<\/li>\n<li><strong>Debugging<\/strong>: Python wins.\u00a0<code class=\"highlighter-rouge\">pdb<\/code>\u00a0(and more sophisticated options like\u00a0<a href=\"https:\/\/pypi.org\/project\/ipdb\/\">ipdb<\/a>\u00a0are available) is extremely flexible, once you\u2019ve entered the REPL, you\u2019re able to write whatever code you want.\u00a0<a href=\"https:\/\/github.com\/go-delve\/delve\">Delve<\/a>\u00a0is a good debugger, but it\u2019s not the same as dropping straight into an interpreter, the full power of the language at your fingertips.<\/li>\n<\/ul>\n<h3 id=\"go-summary\">Go Summary<\/h3>\n<p>My initial impression of Go is that because its ability to abstract is (purposely) limited, it\u2019s not as\u00a0<em>fun<\/em>\u00a0a language as Python is. Python has more features and thus more ways of doing something, and it can be a lot of fun to find the fastest, most readable, or \u201ccleverest\u201d solution. Go actively tries to stop you from being \u201cclever.\u201d I would go as far as saying that Go\u2019s strength is that it\u2019s\u00a0<em>not<\/em>\u00a0clever.<\/p>\n<p>Its minimalism and lack of freedom are constraining as a single developer just trying to materialize an idea. However, this weakness becomes its strength when the project scales to dozens or hundreds of developers &#8211; because everyone\u2019s working with the same small toolset of language features, it\u2019s more likely to be uniform and thus understandable by others. It\u2019s still very possible to write bad Go, but it\u2019s more difficult to create monstrosities that more \u201cpowerful\u201d languages will let you produce.<\/p>\n<p>After using it for a while, it makes sense to me why a company like Google would want a language like this. New engineers are being introduced to enormous codebases constantly, and in a messier\/more powerful language and under the pressure of deadlines, complexity could be introduced faster than it can be removed. The best way to prevent that is with a language that has less capacity for it.<\/p>\n<p>With that said, I\u2019m happy to work on a Go codebase in the context of a large application with a diverse and ever-growing team. In fact, I think I\u2019d prefer it. I just have no desire to use it for my own personal projects.<\/p>\n<h2 id=\"enter-rust\">Enter Rust<\/h2>\n<p>A few weeks ago, I decided to give an honest go at learning Rust. I had attempted to do so before but found the type system and borrow checker confusing and without enough context for why all these constraints were being forced on me, cumbersome for the tasks I was trying to do. However, since then, I\u2019ve learned a bit more about what happens with memory during the execution of a program. I also started with\u00a0<a href=\"https:\/\/doc.rust-lang.org\/book\/\">the book<\/a>\u00a0instead of just attempting to dive in headfirst. This was massively helpful, and probably the best introduction to any programming language I\u2019ve ever experienced.<\/p>\n<p>After I had gone through the first dozen or so chapters of the book, I felt confident enough to try another implementation of\u00a0<code class=\"highlighter-rouge\">diffimg<\/code>\u00a0(at this point, I had about as much experience with Rust as I\u2019d had with Go when I wrote\u00a0<code class=\"highlighter-rouge\">diffimg-go<\/code>). It took me a bit longer to write than the Go implementation, which itself took longer than Python. I think this would be true even taking into account my greater comfort with Python &#8211; there\u2019s just more to write in both languages.<\/p>\n<p>Some of the things that I took notice of when writing\u00a0<a href=\"https:\/\/github.com\/nicolashahn\/diffimg-rs\">diffimg-rs<\/a>:<\/p>\n<ul>\n<li><strong>Type System<\/strong>: I was comfortable with the more basic static type system of Go by now, but Rust\u2019s is significantly more powerful (and complicated). Generic types, enumerated types, traits, reference types, lifetimes are all additional concepts that I had to learn on top of Go\u2019s much simpler interfaces and structs. Additionally, Rust uses its type system to implement features that other languages don\u2019t use the type system for (example: the\u00a0<a href=\"https:\/\/doc.rust-lang.org\/std\/result\/\">Result<\/a>\u00a0type, which I\u2019ll talk about soon). Luckily, the compiler\/linter is extremely helpful in telling you what you\u2019re doing wrong, and often even tells you exactly how to fix it. Despite this, I\u2019ve spent significantly more time than I did learning Go\u2019s type system and I\u2019m still not comfortable with all the features yet.\n<ul>\n<li>There was one place where because of the type system, the implementation of the imaging library I was using\u00a0<a href=\"https:\/\/github.com\/nicolashahn\/diffimg-rs\/blob\/e9dd3f0331b3e32d2f62241b4d576d1da3d3cd42\/src\/lib.rs#L105\">would have led to an uncomfortable amount of code repetition.<\/a>\u00a0I only ended up matching the two most important enum types, but matching the others would lead another half dozen or so lines of nearly identical code. At this scale it\u2019s not an issue, but it rubs me the wrong way. Maybe it\u2019s a good candidate for using macros, which I still need to experiment with.\n<div class=\"language-rust highlighter-rouge\">\n<div class=\"highlight\">\n<pre class=\"highlight\"><code><span class=\"k\">let<\/span> <span class=\"k\">mut<\/span> <span class=\"n\">diff<\/span> <span class=\"o\">=<\/span> <span class=\"k\">match<\/span> <span class=\"n\">image1<\/span><span class=\"nf\">.color<\/span><span class=\"p\">()<\/span> <span class=\"p\">{<\/span>\r\n    <span class=\"nn\">image<\/span><span class=\"p\">::<\/span><span class=\"nn\">ColorType<\/span><span class=\"p\">::<\/span><span class=\"nf\">RGB<\/span><span class=\"p\">(<\/span><span class=\"n\">_<\/span><span class=\"p\">)<\/span> <span class=\"k\">=&gt;<\/span> <span class=\"nn\">image<\/span><span class=\"p\">::<\/span><span class=\"nn\">DynamicImage<\/span><span class=\"p\">::<\/span><span class=\"nf\">new_rgb8<\/span><span class=\"p\">(<\/span><span class=\"n\">w<\/span><span class=\"p\">,<\/span> <span class=\"n\">h<\/span><span class=\"p\">),<\/span>\r\n    <span class=\"nn\">image<\/span><span class=\"p\">::<\/span><span class=\"nn\">ColorType<\/span><span class=\"p\">::<\/span><span class=\"nf\">RGBA<\/span><span class=\"p\">(<\/span><span class=\"n\">_<\/span><span class=\"p\">)<\/span> <span class=\"k\">=&gt;<\/span> <span class=\"nn\">image<\/span><span class=\"p\">::<\/span><span class=\"nn\">DynamicImage<\/span><span class=\"p\">::<\/span><span class=\"nf\">new_rgba8<\/span><span class=\"p\">(<\/span><span class=\"n\">w<\/span><span class=\"p\">,<\/span> <span class=\"n\">h<\/span><span class=\"p\">),<\/span>\r\n    <span class=\"c\">\/\/ keep going for all 7 types?<\/span>\r\n    <span class=\"n\">_<\/span> <span class=\"k\">=&gt;<\/span> <span class=\"k\">return<\/span> <span class=\"nf\">Err<\/span><span class=\"p\">(<\/span>\r\n        <span class=\"nd\">format!<\/span><span class=\"p\">(<\/span><span class=\"s\">\"color mode {:?} not yet supported\"<\/span><span class=\"p\">,<\/span> <span class=\"n\">image1<\/span><span class=\"nf\">.color<\/span><span class=\"p\">())<\/span>\r\n    <span class=\"p\">),<\/span>\r\n<span class=\"p\">};<\/span>\r\n<\/code><\/pre>\n<\/div>\n<\/div>\n<\/li>\n<\/ul>\n<\/li>\n<li><strong>Manual Memory Management<\/strong>: Python and Go pick up your trash for you. C lets you litter everywhere, but throws a fit when it steps on your banana peel. Rust slaps you and demands that you clean up after yourself. This stung at first, since I\u2019m spoiled and usually have my languages pick up after me, moreso even than moving from a dynamic to a statically typed language. Again, the compiler tries to help you as much as is possible, but there\u2019s still a good amount of studying you\u2019ll need to do to understand what\u2019s really going on.\n<ul>\n<li>One nice part about having such direct access to the memory (and the functional programming features of Rust) is that it simplified the\u00a0<a href=\"https:\/\/github.com\/nicolashahn\/diffimg-rs\/blob\/623fb06272f696da9673ccc0cb7ea5bd55582b49\/src\/lib.rs#L80\">difference ratio calculation<\/a>because I could simply map over the raw byte arrays instead of having to index each pixel by coordinate.<\/li>\n<\/ul>\n<\/li>\n<li><strong>Functional Features<\/strong>: Rust strongly encourages a functional approach: it has a FP-friendly type system like Haskell, immutable types, closures, iterators, pattern matching, and more, but also allows imperative code. It\u2019s similar to writing OCaml (interestingly, the original Rust compiler\u00a0<a href=\"https:\/\/github.com\/rust-lang\/rust\/tree\/ef75860a0a72f79f97216f8aaa5b388d98da6480\/src\/boot\">was written in OCaml<\/a>). Because of this, code is more concise than you\u2019d expect for a language that competes with C.<\/li>\n<li><strong>Error Handling<\/strong>: Instead of the exception model that Python uses or the tuple returns that Go uses for error handling, Rust makes use of its enumerated types:\u00a0<code class=\"highlighter-rouge\">Result<\/code>returns either\u00a0<code class=\"highlighter-rouge\">Ok(value)<\/code>\u00a0or\u00a0<code class=\"highlighter-rouge\">Err(error)<\/code>. This is closer to Go\u2019s way if you squint, but is a bit more explicit and leverages the type system. There\u2019s also syntactic sugar for checking a statement for an\u00a0<code class=\"highlighter-rouge\">Err<\/code>\u00a0and returning early:\u00a0<a href=\"https:\/\/doc.rust-lang.org\/stable\/edition-guide\/rust-2018\/error-handling-and-panics\/the-question-mark-operator-for-easier-error-handling.html\">the\u00a0<code class=\"highlighter-rouge\">?<\/code>\u00a0operator<\/a>\u00a0(Go could use something like this, IMO).<\/li>\n<li><strong>Asynchronicity<\/strong>: Async\/await hasn\u2019t quite landed for Rust yet, but the final syntax has\u00a0<a href=\"https:\/\/boats.gitlab.io\/blog\/post\/await-decision-ii\/\">recently been agreed upon<\/a>. Rust also has some basic threading features in the standard library that seem a bit easier to use than Python\u2019s, but I haven\u2019t spent much time with it. Go still seems to have the best offerings here.<\/li>\n<li><strong>Tooling<\/strong>:\u00a0<code class=\"highlighter-rouge\">rustup<\/code>\u00a0and\u00a0<code class=\"highlighter-rouge\">cargo<\/code>\u00a0are extremely polished implementations of a language version manager and package\/module manager, respectively. Everything \u201cjust works.\u201d I especially love the autogenerated docs. The Python options for these are somewhat organic and finicky, and as I mentioned before, Go has a strange way of managing modules, though aside from that, its tooling is in a much better state than Python\u2019s.<\/li>\n<li><strong>Editor Plugins<\/strong>: My\u00a0<code class=\"highlighter-rouge\">.vimrc<\/code>\u00a0is embarrassingly large, with at least three dozen plugins. I have some plugins for linting, autocompleting, and formatting both Python and Go, but the Rust plugins were easier to set up, more helpful, and more consistent compared to the other two languages. The\u00a0<a href=\"https:\/\/github.com\/rust-lang\/rust.vim\">rust.vim<\/a>\u00a0and\u00a0<a href=\"https:\/\/github.com\/prabirshrestha\/vim-lsp\">vim-lsp<\/a>\u00a0plugins (along with\u00a0<a href=\"https:\/\/github.com\/rust-lang\/rls\">the Rust Language Server<\/a>) were all I needed to get an extremely powerful configuration. I haven\u2019t tested out other editors with Rust but with the excellent editor-agnostic tooling that Rust comes with, I\u2019d expect them to be just as helpful. The setup provides the best go-to-definition I\u2019ve ever used. It works perfectly on local, standard library, and third-party code out of the box.<\/li>\n<li><strong>Debugging<\/strong>: I haven\u2019t tried out a debugger with Rust yet (since the type system and<code class=\"highlighter-rouge\">println!<\/code>\u00a0take you pretty far), but you can use\u00a0<code class=\"highlighter-rouge\">rust-gdb<\/code>\u00a0and\u00a0<code class=\"highlighter-rouge\">rust-lldb<\/code>, wrappers around the\u00a0<code class=\"highlighter-rouge\">gdb<\/code>\u00a0and\u00a0<code class=\"highlighter-rouge\">lldb<\/code>\u00a0debuggers that are installed with the initial\u00a0<code class=\"highlighter-rouge\">rustup<\/code>. The experience should be predictable if you\u2019ve used those debuggers before with C. As mentioned previously, the compiler error messages are extremely helpful.<\/li>\n<\/ul>\n<h3 id=\"rust-summary\">Rust Summary<\/h3>\n<p>I definitely wouldn\u2019t recommend attempting to write Rust without at least going through the first few chapters of the book, even if you\u2019re already familiar with C and memory management. With Go and Python, as long as you have some experience with another modern imperative programming language, they\u2019re not difficult to just start writing, referring to the docs when necessary. Rust is a large language. Python also has a lot of features, but they\u2019re mostly opt-in. You can get a lot done just by understanding a few primitive data structures and some builtin functions. With Rust, you really need to understand the complexity inherent to the type system and borrow checker, or you\u2019re going to be getting tangled up a lot.<\/p>\n<p>As far as how I feel when I write Rust, it\u2019s a lot of fun, like Python. Its breadth of features makes it very expressive. While the compiler stops you a lot, it\u2019s also very helpful, and its suggestions on how to solve your borrowing\/typing problems usually work. The tooling as I\u2019ve mentioned is the best I\u2019ve encountered for any language and doesn\u2019t bring me a lot of headaches like some other languages I\u2019ve used. I really like using the language and will continue to look for opportunities to do so, where the performance of Python isn\u2019t good enough.<\/p>\n<h2 id=\"code-samples\"><a href=\"https:\/\/www.nicolas-hahn.com\/python\/go\/rust\/programming\/2019\/07\/01\/program-in-python-go-rust\/#code-samples\">Code Samples<\/a><\/h2>\n<p>I\u2019ve extracted the chunks of each\u00a0<code class=\"highlighter-rouge\">diffimg<\/code>\u00a0which calculate the difference ratio. To summarize how it works for Python, this takes the diff image generated by Pillow, sums the values of all channels of all pixels, and returns the ratio produced by dividing the maximum possible value (a pure white image of the same size) by this sum.<\/p>\n<p><strong>Python<\/strong>:<\/p>\n<div class=\"language-python highlighter-rouge\">\n<div class=\"highlight\">\n<pre class=\"highlight\"><code>\r\n<span class=\"n\">diff_img<\/span> <span class=\"o\">=<\/span> <span class=\"n\">ImageChops<\/span><span class=\"o\">.<\/span><span class=\"n\">difference<\/span><span class=\"p\">(<\/span><span class=\"n\">im1<\/span><span class=\"p\">,<\/span> <span class=\"n\">im2<\/span><span class=\"p\">)<\/span>\r\n<span class=\"n\">stat<\/span> <span class=\"o\">=<\/span> <span class=\"n\">ImageStat<\/span><span class=\"o\">.<\/span><span class=\"n\">Stat<\/span><span class=\"p\">(<\/span><span class=\"n\">diff_img<\/span><span class=\"p\">)<\/span>\r\n<span class=\"n\">sum_channel_values<\/span> <span class=\"o\">=<\/span> <span class=\"nb\">sum<\/span><span class=\"p\">(<\/span><span class=\"n\">stat<\/span><span class=\"o\">.<\/span><span class=\"n\">mean<\/span><span class=\"p\">)<\/span>\r\n<span class=\"n\">max_all_channels<\/span> <span class=\"o\">=<\/span> <span class=\"nb\">len<\/span><span class=\"p\">(<\/span><span class=\"n\">stat<\/span><span class=\"o\">.<\/span><span class=\"n\">mean<\/span><span class=\"p\">)<\/span> <span class=\"o\">*<\/span> <span class=\"mi\">255<\/span>\r\n<span class=\"n\">diff_ratio<\/span> <span class=\"o\">=<\/span> <span class=\"n\">sum_channel_values<\/span> <span class=\"o\">\/<\/span> <span class=\"n\">max_all_channels<\/span>\r\n\r\n<\/code><\/pre>\n<\/div>\n<\/div>\n<p>For Go and Rust, the method is a little different: Instead of creating a diff image, we just loop over both input images and keep a running sum of the differences of each pixel. In Go, we\u2019re indexing into each image by coordinate\u2026<\/p>\n<p><strong>Go<\/strong>:<\/p>\n<div class=\"language-go highlighter-rouge\">\n<div class=\"highlight\">\n<pre class=\"highlight\"><code>\r\n<span class=\"k\">func<\/span> <span class=\"n\">GetRatio<\/span><span class=\"p\">(<\/span><span class=\"n\">im1<\/span><span class=\"p\">,<\/span> <span class=\"n\">im2<\/span> <span class=\"n\">image<\/span><span class=\"o\">.<\/span><span class=\"n\">Image<\/span><span class=\"p\">,<\/span> <span class=\"n\">ignoreAlpha<\/span> <span class=\"kt\">bool<\/span><span class=\"p\">)<\/span> <span class=\"kt\">float64<\/span> <span class=\"p\">{<\/span>\r\n  <span class=\"k\">var<\/span> <span class=\"n\">sum<\/span> <span class=\"kt\">uint64<\/span>\r\n  <span class=\"n\">width<\/span><span class=\"p\">,<\/span> <span class=\"n\">height<\/span> <span class=\"o\">:=<\/span> <span class=\"n\">getWidthAndHeight<\/span><span class=\"p\">(<\/span><span class=\"n\">im1<\/span><span class=\"p\">)<\/span>\r\n  <span class=\"k\">for<\/span> <span class=\"n\">y<\/span> <span class=\"o\">:=<\/span> <span class=\"m\">0<\/span><span class=\"p\">;<\/span> <span class=\"n\">y<\/span> <span class=\"o\">&lt;<\/span> <span class=\"n\">height<\/span><span class=\"p\">;<\/span> <span class=\"n\">y<\/span><span class=\"o\">++<\/span> <span class=\"p\">{<\/span>\r\n    <span class=\"k\">for<\/span> <span class=\"n\">x<\/span> <span class=\"o\">:=<\/span> <span class=\"m\">0<\/span><span class=\"p\">;<\/span> <span class=\"n\">x<\/span> <span class=\"o\">&lt;<\/span> <span class=\"n\">width<\/span><span class=\"p\">;<\/span> <span class=\"n\">x<\/span><span class=\"o\">++<\/span> <span class=\"p\">{<\/span>\r\n      <span class=\"n\">sum<\/span> <span class=\"o\">+=<\/span> <span class=\"kt\">uint64<\/span><span class=\"p\">(<\/span><span class=\"n\">sumPixelDiff<\/span><span class=\"p\">(<\/span><span class=\"n\">im1<\/span><span class=\"p\">,<\/span> <span class=\"n\">im2<\/span><span class=\"p\">,<\/span> <span class=\"n\">x<\/span><span class=\"p\">,<\/span> <span class=\"n\">y<\/span><span class=\"p\">,<\/span> <span class=\"n\">ignoreAlpha<\/span><span class=\"p\">))<\/span>\r\n    <span class=\"p\">}<\/span>\r\n  <span class=\"p\">}<\/span>\r\n  <span class=\"k\">var<\/span> <span class=\"n\">numChannels<\/span> <span class=\"o\">=<\/span> <span class=\"m\">4<\/span>\r\n  <span class=\"k\">if<\/span> <span class=\"n\">ignoreAlpha<\/span> <span class=\"p\">{<\/span>\r\n    <span class=\"n\">numChannels<\/span> <span class=\"o\">=<\/span> <span class=\"m\">3<\/span>\r\n  <span class=\"p\">}<\/span>\r\n  <span class=\"n\">totalPixVals<\/span> <span class=\"o\">:=<\/span> <span class=\"p\">(<\/span><span class=\"n\">height<\/span> <span class=\"o\">*<\/span> <span class=\"n\">width<\/span><span class=\"p\">)<\/span> <span class=\"o\">*<\/span> <span class=\"p\">(<\/span><span class=\"n\">maxChannelVal<\/span> <span class=\"o\">*<\/span> <span class=\"n\">numChannels<\/span><span class=\"p\">)<\/span>\r\n  <span class=\"k\">return<\/span> <span class=\"kt\">float64<\/span><span class=\"p\">(<\/span><span class=\"n\">sum<\/span><span class=\"p\">)<\/span> <span class=\"o\">\/<\/span> <span class=\"kt\">float64<\/span><span class=\"p\">(<\/span><span class=\"n\">totalPixVals<\/span><span class=\"p\">)<\/span>\r\n<span class=\"p\">}<\/span>\r\n\r\n<\/code><\/pre>\n<\/div>\n<\/div>\n<p>\u2026 but in Rust, we\u2019re treating the images as what they really are in memory, a series of bytes that we can just zip together and consume.<\/p>\n<p><strong>Rust<\/strong>:<\/p>\n<div class=\"language-rust highlighter-rouge\">\n<div class=\"highlight\">\n<pre class=\"highlight\"><code>\r\n<span class=\"k\">pub<\/span> <span class=\"k\">fn<\/span> <span class=\"nf\">calculate_diff<\/span><span class=\"p\">(<\/span>\r\n    <span class=\"n\">image1<\/span><span class=\"p\">:<\/span> <span class=\"n\">DynamicImage<\/span><span class=\"p\">,<\/span>\r\n    <span class=\"n\">image2<\/span><span class=\"p\">:<\/span> <span class=\"n\">DynamicImage<\/span>\r\n  <span class=\"p\">)<\/span> <span class=\"k\">-&gt;<\/span> <span class=\"nb\">f64<\/span> <span class=\"p\">{<\/span>\r\n  <span class=\"k\">let<\/span> <span class=\"n\">max_val<\/span> <span class=\"o\">=<\/span> <span class=\"nn\">u64<\/span><span class=\"p\">::<\/span><span class=\"nf\">pow<\/span><span class=\"p\">(<\/span><span class=\"mi\">2<\/span><span class=\"p\">,<\/span> <span class=\"mi\">8<\/span><span class=\"p\">)<\/span> <span class=\"err\">-<\/span> <span class=\"mi\">1<\/span><span class=\"p\">;<\/span>\r\n  <span class=\"k\">let<\/span> <span class=\"k\">mut<\/span> <span class=\"n\">diffsum<\/span><span class=\"p\">:<\/span> <span class=\"nb\">u64<\/span> <span class=\"o\">=<\/span> <span class=\"mi\">0<\/span><span class=\"p\">;<\/span>\r\n  <span class=\"k\">for<\/span> <span class=\"p\">(<\/span><span class=\"o\">&amp;<\/span><span class=\"n\">p1<\/span><span class=\"p\">,<\/span> <span class=\"o\">&amp;<\/span><span class=\"n\">p2<\/span><span class=\"p\">)<\/span> <span class=\"n\">in<\/span> <span class=\"n\">image1<\/span>\r\n      <span class=\"nf\">.raw_pixels<\/span><span class=\"p\">()<\/span>\r\n      <span class=\"nf\">.iter<\/span><span class=\"p\">()<\/span>\r\n      <span class=\"nf\">.zip<\/span><span class=\"p\">(<\/span><span class=\"n\">image2<\/span><span class=\"nf\">.raw_pixels<\/span><span class=\"p\">()<\/span><span class=\"nf\">.iter<\/span><span class=\"p\">())<\/span> <span class=\"p\">{<\/span>\r\n    <span class=\"n\">diffsum<\/span> <span class=\"o\">+=<\/span> <span class=\"nn\">u64<\/span><span class=\"p\">::<\/span><span class=\"nf\">from<\/span><span class=\"p\">(<\/span><span class=\"nf\">abs_diff<\/span><span class=\"p\">(<\/span><span class=\"n\">p1<\/span><span class=\"p\">,<\/span> <span class=\"n\">p2<\/span><span class=\"p\">));<\/span>\r\n  <span class=\"p\">}<\/span>\r\n  <span class=\"k\">let<\/span> <span class=\"n\">total_possible<\/span> <span class=\"o\">=<\/span> <span class=\"n\">max_val<\/span> <span class=\"o\">*<\/span> <span class=\"n\">image1<\/span><span class=\"nf\">.raw_pixels<\/span><span class=\"p\">()<\/span><span class=\"nf\">.len<\/span><span class=\"p\">()<\/span> <span class=\"k\">as<\/span> <span class=\"nb\">u64<\/span><span class=\"p\">;<\/span>\r\n  <span class=\"k\">let<\/span> <span class=\"n\">ratio<\/span> <span class=\"o\">=<\/span> <span class=\"n\">diffsum<\/span> <span class=\"k\">as<\/span> <span class=\"nb\">f64<\/span> <span class=\"err\">\/<\/span> <span class=\"n\">total_possible<\/span> <span class=\"k\">as<\/span> <span class=\"nb\">f64<\/span><span class=\"p\">;<\/span>\r\n\r\n  <span class=\"n\">ratio<\/span>\r\n<span class=\"p\">}<\/span>\r\n\r\n<\/code><\/pre>\n<\/div>\n<\/div>\n<p>Some things to take note of in these examples:<\/p>\n<ul>\n<li>Python has the least code by far. Obviously, it\u2019s leaning heavily on features of the image library it\u2019s using, but this is indicative of the general experience of using Python. In many cases, a lot of the work has been done for you because the ecosystem is so developed that there are mature pre-existing solutions for everything.<\/li>\n<li>There\u2019s type conversion in the Go and Rust examples. In each block there are three numerical types being used:\u00a0<code class=\"highlighter-rouge\">uint8<\/code>\/<code class=\"highlighter-rouge\">u8<\/code>\u00a0for the pixel channel values (the type is inferred in both Go and Rust, so you don\u2019t see any explicit mention of either type),<code class=\"highlighter-rouge\">uint64<\/code>\/<code class=\"highlighter-rouge\">u64<\/code>\u00a0for the sum, and\u00a0<code class=\"highlighter-rouge\">float64<\/code>\/<code class=\"highlighter-rouge\">f64<\/code>\u00a0for the final ratio. For Go and Rust, there was time spent getting the types to line up, whereas Python converts everything implicitly.<\/li>\n<li>The Go implementation\u2019s style is very imperative, but also explicit and understandable (minus the\u00a0<code class=\"highlighter-rouge\">ignoreAlpha<\/code>\u00a0part I mentioned earlier), even to those unaccustomed to the language. The Python example is fairly clear as well, once you understand what\u00a0<code class=\"highlighter-rouge\">ImageStat<\/code>\u00a0is doing. Rust is definitely murkier to those unfamiliar with the language:\n<ul>\n<li><code class=\"highlighter-rouge\">.raw_pixels()<\/code>\u00a0gets the image as a vector of unsigned 8-bit integers.<\/li>\n<li><code class=\"highlighter-rouge\">.iter()<\/code>\u00a0creates an iterator for that vector. Vectors by default are not iterable.<\/li>\n<li><code class=\"highlighter-rouge\">.zip()<\/code>\u00a0you may be familiar with, it takes two iterators and produces one, with each element being a tuple: (element from first vector, element from second vector).<\/li>\n<li>We need a\u00a0<code class=\"highlighter-rouge\">mut<\/code>\u00a0in our\u00a0<code class=\"highlighter-rouge\">diffsum<\/code>\u00a0declaration because by default, variables are immutable.<\/li>\n<li>If you\u2019re familiar with C you can probably figure out why we have the\u00a0<code class=\"highlighter-rouge\">&amp;<\/code>s in\u00a0<code class=\"highlighter-rouge\">for (&amp;p1, &amp;p2)<\/code>: The iterator produces references to the pixel values, but\u00a0<code class=\"highlighter-rouge\">abs_diff()<\/code>\u00a0takes the values themselves. Go supports pointers (<a href=\"https:\/\/spf13.com\/post\/go-pointers-vs-references\/\">which are not quite the same as references<\/a>), but they\u2019re not as commonly used as references are in Rust.<\/li>\n<li>The last statement in a function is used as the return value if there isn\u2019t a line-ending\u00a0<code class=\"highlighter-rouge\">;<\/code>. A few other functional languages do this as well.<\/li>\n<\/ul>\n<p>This snippet gives you some insight into how much language-specific knowledge you\u2019ll need to pick up to be effective in Rust.<\/li>\n<\/ul>\n<h2 id=\"performance\"><a href=\"https:\/\/www.nicolas-hahn.com\/python\/go\/rust\/programming\/2019\/07\/01\/program-in-python-go-rust\/#performance\">Performance<\/a><\/h2>\n<p>Now for something resembling a scientific comparison. I first generated three random images of different sizes: 1&#215;1, 2000&#215;2000, and 10,000&#215;10,000. Then I measured each (language, image size) combination\u2019s performance 10 times for each\u00a0<code class=\"highlighter-rouge\">diffimg<\/code>\u00a0ratio calculation and averaged them, using the values given by the\u00a0<code class=\"highlighter-rouge\">real<\/code>\u00a0values from the\u00a0<code class=\"highlighter-rouge\">time<\/code>command.\u00a0<code class=\"highlighter-rouge\">diffimg-rs<\/code>\u00a0was built using\u00a0<code class=\"highlighter-rouge\">--release<\/code>,\u00a0<code class=\"highlighter-rouge\">diffimg-go<\/code>\u00a0with just\u00a0<code class=\"highlighter-rouge\">go build<\/code>, and the Python\u00a0<code class=\"highlighter-rouge\">diffimg<\/code>\u00a0invoked with\u00a0<code class=\"highlighter-rouge\">python3 -m diffimg<\/code>. The results, on a 2015 Macbook Pro:<\/p>\n<table>\n<thead>\n<tr>\n<th>Image size:<\/th>\n<th>1&#215;1<\/th>\n<th>2000&#215;2000<\/th>\n<th>10,000&#215;10,000<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Rust<\/td>\n<td>0.001s<\/td>\n<td>0.490s<\/td>\n<td>5.871s<\/td>\n<\/tr>\n<tr>\n<td>Go<\/td>\n<td>0.002s\u00a0<strong>(2x)<\/strong><\/td>\n<td>0.756s\u00a0<strong>(1.54x)<\/strong><\/td>\n<td>14.060s\u00a0<strong>(2.39x)<\/strong><\/td>\n<\/tr>\n<tr>\n<td>Python<\/td>\n<td>0.095s\u00a0<strong>(95x)<\/strong><\/td>\n<td>1.419s\u00a0<strong>(2.90x)<\/strong><\/td>\n<td>28.751s\u00a0<strong>(4.89x)<\/strong><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>I\u2019m losing a lot of precision because\u00a0<code class=\"highlighter-rouge\">time<\/code>\u00a0only goes down to 10ms resolution (one more digit is shown here because of the averaging). The task only requires a very specific type of calculation as well, so a different or more complex one could have very different numbers. Despite these caveats, we can still learn something from the data.<\/p>\n<p>With the 1&#215;1 image, virtually all the time is spent in setup, not ratio calculation. Rust wins, despite using two third-party libraries (<a href=\"https:\/\/github.com\/clap-rs\/clap\">clap<\/a>\u00a0and\u00a0<a href=\"https:\/\/github.com\/image-rs\/image\">image<\/a>) and Go only using the standard library. I\u2019m not surprised Python\u2019s startup is as slow as it is, since importing a large library (Pillow) is one of its steps, and even just\u00a0<code class=\"highlighter-rouge\">time python -c ''<\/code>\u00a0takes 0.030s.<\/p>\n<p>At 2000&#215;2000, the gap narrows for both Go and Python compared to Rust, presumably because less of the overall time is spent in setup compared to calculation. However, at 10,000&#215;10,000, Rust is more performant in comparison, which I would guess is due to its compiler\u2019s optimizations producing the smallest block of machine code that is looped through 100,000,000 times, dwarfing the setup time. Never needing to pause for garbage collection could also be a factor.<\/p>\n<p>The Python implementation definitely has room for improvement, because as efficient as Pillow is, we\u2019re still creating a diff image in memory (traversing both input images) and\u00a0<em>then<\/em>\u00a0adding up each of its pixel\u2019s channel values. A more direct approach like the Go and Rust implementations would probably be marginally faster. However, a\u00a0<em>pure<\/em>\u00a0Python implementation would be wildly slower, since Pillow does its main work in C. Because the other two are pure language implementations, this isn\u2019t really a fair comparison, though in some ways it is, because Python has an absurd amount of libraries available to you that are performant thanks to C extensions (and Python and C have a very tight relationship in general).<\/p>\n<p>I should also mention the binary sizes: Rust\u2019s is 2.1mb with the\u00a0<code class=\"highlighter-rouge\">--release<\/code>\u00a0build, and Go\u2019s is comparable at 2.5mb. Python doesn\u2019t create binaries, but\u00a0<code class=\"highlighter-rouge\">.pyc<\/code>\u00a0files are\u00a0<em>sort of<\/em>comparable, and\u00a0<code class=\"highlighter-rouge\">diffimg<\/code>\u2019s\u00a0<code class=\"highlighter-rouge\">.pyc<\/code>\u00a0files are about 3kb in total. Its source code is also only about 3kb, but including the Pillow dependency, it weighs in at 24mb(!). Again, not a fair comparison because I\u2019m using a third party imaging library, but it should be mentioned.<\/p>\n<h2 id=\"the-takeaway\"><a href=\"https:\/\/www.nicolas-hahn.com\/python\/go\/rust\/programming\/2019\/07\/01\/program-in-python-go-rust\/#the-takeaway\">The Takeaway<\/a><\/h2>\n<p>Obviously, these are three very different languages fulfilling different niches. I\u2019ve heard Go and Rust often mentioned together, but I think Go and Python are the two more similar\/competing languages. They\u2019re both good for writing server-side application logic (what I spend most of my time doing at work). Comparing just native code performance, Go blows Python away, but many of Python\u2019s libraries that require speed are wrappers around fast C implementations &#8211; in practice, it\u2019s more complicated than a naive comparison. Writing a C extension for Python doesn\u2019t really count as Python anymore (and then you\u2019ll need to know C), but the option is open to you.<\/p>\n<p>For your backend server needs, Python has proven itself to be \u201cfast enough\u201d for most applications, though if you need more performance, Go has it. Rust even more so, but you pay for it with development time. Go is not far off from Python in this regard, though it certainly is slower to develop, primarily due to its small feature set. Rust is very fully featured, but managing memory will always take more time than having the language do it, and this outweighs having to deal with Go\u2019s minimality.<\/p>\n<p>It should also be mentioned that there are many, many Python developers in the world, some with literally decades of experience. It will likely never be hard to find more people with language experience to add to your backend team if you choose Python. However, Go developers are not particularly rare, and can easily be created because the language is so easy to learn. Rust developers are both rarer and harder to make since the language takes longer to internalize.<\/p>\n<p>With respect to the type systems: static type systems make it easier to write more correct code, but it\u2019s not a panacea. You still need to write comprehensive tests no matter the language you use. It requires a bit more discipline, but I\u2019ve found that the code I write in Python is not necessarily more error prone than Go as long as I\u2019m able to write a good suite of tests. That said, I much prefer Rust\u2019s type system to Go\u2019s: it supports generics, pattern matching, handles errors, and just does more for you in general.<\/p>\n<p>In the end, this comparison is a bit silly, because though the use cases of these languages overlap, they occupy very different niches. Python is high on the development-speed, low on the performance scale, while Rust is the opposite, and Go is in the middle. I enjoy writing Python and Rust more than Go (<a href=\"https:\/\/insights.stackoverflow.com\/survey\/2019#technology-_-most-loved-dreaded-and-wanted-languages\">this may be unsurprising<\/a>), though I\u2019ll continue to use Go at work happily (along with Python) since it really is a great language for building stable and maintainable applications with many contributors from many backgrounds. Its inflexibility and minimalism which makes it less enjoyable to use (for me) becomes its strength here. If I had to choose the language for the backend of a new web application, it would be Go.<\/p>\n<p>I\u2019m pretty satisfied with the range of programming tasks that are covered by these three languages &#8211; there\u2019s virtually no project that one of them wouldn\u2019t be a great choice for.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Update (2019-07-04):\u00a0Some kind folks have suggested changes on the implementations to make them more idiomatic, so the code here may differ from what\u2019s currently in the repos. This is a subjective, primarily developer-ergonomics-based comparison of the three languages from the perspective of a Python developer, but you can skip the prose and go to\u00a0the code &hellip; <a href=\"https:\/\/www.strongd.net\/?p=1536\" class=\"more-link\">Continue reading <span class=\"screen-reader-text\">One Program Written in Python, Go, and Rust<\/span><\/a><\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[221,235],"tags":[],"class_list":["post-1536","post","type-post","status-publish","format-standard","hentry","category-ai","category-python"],"_links":{"self":[{"href":"https:\/\/www.strongd.net\/index.php?rest_route=\/wp\/v2\/posts\/1536","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.strongd.net\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.strongd.net\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.strongd.net\/index.php?rest_route=\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/www.strongd.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=1536"}],"version-history":[{"count":1,"href":"https:\/\/www.strongd.net\/index.php?rest_route=\/wp\/v2\/posts\/1536\/revisions"}],"predecessor-version":[{"id":1537,"href":"https:\/\/www.strongd.net\/index.php?rest_route=\/wp\/v2\/posts\/1536\/revisions\/1537"}],"wp:attachment":[{"href":"https:\/\/www.strongd.net\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=1536"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.strongd.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=1536"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.strongd.net\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=1536"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}