speice.io/assets/js/1803684d.1d950fa0.js

1 line
21 KiB
JavaScript

"use strict";(self.webpackChunkspeice_io=self.webpackChunkspeice_io||[]).push([["7624"],{53570:function(e,t,n){n.r(t),n.d(t,{assets:function(){return l},contentTitle:function(){return a},default:function(){return d},frontMatter:function(){return i},metadata:function(){return s},toc:function(){return h}});var s=n(10510),o=n(85893),r=n(50065);let i={slug:"2016/10/rustic-repodcasting",title:"A Rustic re-podcasting server",date:new Date("2016-10-22T12:00:00.000Z"),authors:["bspeice"],tags:[]},a=void 0,l={authorsImageUrls:[void 0]},h=[{value:"The Setup",id:"the-setup",level:2},{value:"Issue 1: Strings",id:"issue-1-strings",level:2},{value:"Issue 2: Fighting with the borrow checker",id:"issue-2-fighting-with-the-borrow-checker",level:2},{value:"Conclusion",id:"conclusion",level:2}];function c(e){let t={a:"a",blockquote:"blockquote",code:"code",em:"em",h2:"h2",li:"li",ol:"ol",p:"p",pre:"pre",strong:"strong",...(0,r.a)(),...e.components};return(0,o.jsxs)(o.Fragment,{children:[(0,o.jsx)(t.p,{children:"Learning Rust by fire (it sounds better than learning by corrosion)"}),"\n",(0,o.jsxs)(t.p,{children:["I listen to a lot of Drum and Bass music, because it's beautiful music. And\nthere's a particular site, ",(0,o.jsx)(t.a,{href:"http://bassdrive.com/",children:"Bassdrive.com"})," that hosts\na lot of great content. Specifically, the\n",(0,o.jsx)(t.a,{href:"http://archives.bassdrivearchive.com/",children:"archives"})," section of the site has a\nlist of the past shows that you can download and listen to. The issue is, it's\njust a ",(0,o.jsx)(t.a,{href:"http://archives.bassdrivearchive.com/6%20-%20Saturday/Electronic%20Warfare%20-%20The%20Overfiend/",children:"giant list of links to download"}),". I'd really like\nthis in a podcast format to take with me on the road, etc."]}),"\n",(0,o.jsxs)(t.p,{children:["So I wrote the ",(0,o.jsx)(t.a,{href:"https://github.com/bspeice/elektricity",children:"elektricity"})," web\napplication to actually accomplish all that. Whenever you request a feed, it\ngoes out to Bassdrive, processes all the links on a page, and serves up some\nfresh, tasty RSS to satisfy your ears. I hosted it on Heroku using the free\ntier because it's really not resource-intensive at all."]}),"\n",(0,o.jsxs)(t.p,{children:[(0,o.jsx)(t.strong,{children:"The issue so far"})," is that I keep running out of free tier hours during a\nmonth because my podcasting application likes to have a server scan for new\nepisodes constantly. Not sure why it's doing that, but I don't have a whole\nlot of control over it. It's a phenomenal application otherwise."]}),"\n",(0,o.jsxs)(t.p,{children:[(0,o.jsx)(t.strong,{children:"My (over-engineered) solution"}),": Re-write the application using the\n",(0,o.jsx)(t.a,{href:"https://www.rust-lang.org/en-US/",children:"Rust"})," programming language. I'd like to run\nthis on a small hacker board I own, and doing this in Rust would allow me to\neasily cross-compile it. Plus, I've been very interested in the Rust language\nfor a while and this would be a great opportunity to really learn it well.\nThe code is available ",(0,o.jsx)(t.a,{href:"https://github.com/bspeice/nutone",children:"here"})," as development\nprogresses."]}),"\n",(0,o.jsx)(t.h2,{id:"the-setup",children:"The Setup"}),"\n",(0,o.jsxs)(t.p,{children:["We'll be using the ",(0,o.jsx)(t.a,{href:"http://ironframework.io/",children:"iron"})," library to handle the\nserver, and ",(0,o.jsx)(t.a,{href:"http://hyper.rs/",children:"hyper"})," to fetch the data we need from elsewhere\non the interwebs. ",(0,o.jsx)(t.a,{href:"http://doc.servo.org/html5ever/index.html",children:"HTML5Ever"})," allows\nus to ingest the content that will be coming from Bassdrive, and finally,\noutput is done with ",(0,o.jsx)(t.a,{href:"http://sunng87.github.io/handlebars-rust/handlebars/index.html",children:"handlebars-rust"}),"."]}),"\n",(0,o.jsx)(t.p,{children:"It will ultimately be interesting to see how much more work must be done to\nactually get this working over another language like Python. Coming from a\ndynamic state of mind it's super easy to just chain stuff together, ship it out,\nand call it a day. I think I'm going to end up getting much dirtier trying to\nwrite all of this out."}),"\n",(0,o.jsx)(t.h2,{id:"issue-1-strings",children:"Issue 1: Strings"}),"\n",(0,o.jsxs)(t.p,{children:["Strings in Rust are hard. I acknowledge Python can get away with some things\nthat make strings super easy (and Python 3 has gotten better at cracking down\non some bad cases, ",(0,o.jsx)(t.code,{children:"str <-> bytes"})," specifically), but Rust is hard."]}),"\n",(0,o.jsxs)(t.p,{children:["Let's take for example the ",(0,o.jsx)(t.code,{children:"404"})," error handler I'm trying to write. The result\nshould be incredibly simple: All I want is to echo back\n",(0,o.jsx)(t.code,{children:"Didn't find URL: <url>"}),". Shouldn't be that hard right? In Python I'd just do\nsomething like:"]}),"\n",(0,o.jsx)(t.pre,{children:(0,o.jsx)(t.code,{className:"language-python",children:'def echo_handler(request):\n return "You\'re visiting: {}".format(request.uri)\n'})}),"\n",(0,o.jsx)(t.p,{children:"And we'd call it a day. Rust isn't so simple. Let's start with the trivial\nexamples people post online:"}),"\n",(0,o.jsx)(t.pre,{children:(0,o.jsx)(t.code,{className:"language-rust",children:'fn hello_world(req: &mut Request) -> IronResult<Response> {\n Ok(Response::with((status::Ok, "You found the server!")))\n}\n'})}),"\n",(0,o.jsxs)(t.p,{children:["Doesn't look too bad right? In fact, it's essentially the same as the Python\nversion! All we need to do is just send back a string of some form. So, we\nlook up the documentation for ",(0,o.jsx)(t.a,{href:"http://ironframework.io/doc/iron/request/struct.Request.html",children:(0,o.jsx)(t.code,{children:"Request"})})," and see a ",(0,o.jsx)(t.code,{children:"url"})," field that will contain\nwhat we want. Let's try the first iteration:"]}),"\n",(0,o.jsx)(t.pre,{children:(0,o.jsx)(t.code,{className:"language-rust",children:'fn hello_world(req: &mut Request) -> IronResult<Response> {\n Ok(Response::with((status::Ok, "You found the URL: " + req.url)))\n}\n'})}),"\n",(0,o.jsx)(t.p,{children:"Which yields the error:"}),"\n",(0,o.jsx)(t.pre,{children:(0,o.jsx)(t.code,{children:" error[E0369]: binary operation `+` cannot be applied to type `&'static str`\n"})}),"\n",(0,o.jsxs)(t.p,{children:["OK, what's going on here? Time to start Googling for ",(0,o.jsx)(t.a,{href:"https://www.google.com/#q=concatenate+strings+in+rust",children:'"concatenate strings in Rust"'}),". That's what we\nwant to do right? Concatenate a static string and the URL."]}),"\n",(0,o.jsxs)(t.p,{children:["After Googling, we come across a helpful ",(0,o.jsx)(t.a,{href:"https://doc.rust-lang.org/std/macro.concat!.html",children:(0,o.jsx)(t.code,{children:"concat!"})})," macro that looks really nice! Let's try that one:"]}),"\n",(0,o.jsx)(t.pre,{children:(0,o.jsx)(t.code,{className:"language-rust",children:'fn hello_world(req: &mut Request) -> IronResult<Response> {\n Ok(Response::with((status::Ok, concat!("You found the URL: ", req.url))))\n}\n'})}),"\n",(0,o.jsx)(t.p,{children:"And the error:"}),"\n",(0,o.jsx)(t.pre,{children:(0,o.jsx)(t.code,{children:" error: expected a literal\n"})}),"\n",(0,o.jsxs)(t.p,{children:["Turns out Rust actually blows up because the ",(0,o.jsx)(t.code,{children:"concat!"})," macro expects us to know\nat compile time what ",(0,o.jsx)(t.code,{children:"req.url"})," is. Which, in my outsider opinion, is a bit\nstrange. ",(0,o.jsx)(t.code,{children:"println!"})," and ",(0,o.jsx)(t.code,{children:"format!"}),", etc., all handle values they don't know at\ncompile time. Why can't ",(0,o.jsx)(t.code,{children:"concat!"}),"? By any means, we need a new plan of attack.\nHow about we try formatting strings?"]}),"\n",(0,o.jsx)(t.pre,{children:(0,o.jsx)(t.code,{className:"language-rust",children:'fn hello_world(req: &mut Request) -> IronResult<Response> {\n Ok(Response::with((status::Ok, format!("You found the URL: {}", req.url))))\n}\n'})}),"\n",(0,o.jsx)(t.p,{children:"And at long last, it works. Onwards!"}),"\n",(0,o.jsx)(t.h2,{id:"issue-2-fighting-with-the-borrow-checker",children:"Issue 2: Fighting with the borrow checker"}),"\n",(0,o.jsxs)(t.p,{children:["Rust's single coolest feature is how the compiler can guarantee safety in your\nprogram. As long as you don't use ",(0,o.jsx)(t.code,{children:"unsafe"})," pointers in Rust, you're guaranteed\nsafety. And not having truly manual memory management is really cool; I'm\ntotally OK with never having to write ",(0,o.jsx)(t.code,{children:"malloc()"})," again."]}),"\n",(0,o.jsxs)(t.p,{children:["That said, even ",(0,o.jsx)(t.a,{href:"https://doc.rust-lang.org/book/ownership.html",children:"the Rust documentation"})," makes a specific note:"]}),"\n",(0,o.jsxs)(t.blockquote,{children:["\n",(0,o.jsx)(t.p,{children:"Many new users to Rust experience something we like to call\n\u2018fighting with the borrow checker\u2019, where the Rust compiler refuses to\ncompile a program that the author thinks is valid."}),"\n"]}),"\n",(0,o.jsx)(t.p,{children:"If you have to put it in the documentation, it's not a helpful note:\nit's hazing."}),"\n",(0,o.jsxs)(t.p,{children:["So now that we have a handler which works with information from the request, we\nwant to start making something that looks like an actual web application.\nThe router provided by ",(0,o.jsx)(t.code,{children:"iron"})," isn't terribly difficult so I won't cover it.\nInstead, the thing that had me stumped for a couple hours was trying to\ndynamically create routes."]}),"\n",(0,o.jsx)(t.p,{children:"The unfortunate thing with Rust (in my limited experience at the moment) is that\nthere is a severe lack of non-trivial examples. Using the router is easy when\nyou want to give an example of a static function. But how do you you start\nworking on things that are a bit more complex?"}),"\n",(0,o.jsxs)(t.p,{children:["We're going to cover that here. Our first try: creating a function which returns\nother functions. This is a principle called ",(0,o.jsx)(t.a,{href:"http://stackoverflow.com/a/36321/1454178",children:"currying"}),". We set up a function that allows us to keep some data in scope\nfor another function to come later."]}),"\n",(0,o.jsx)(t.pre,{children:(0,o.jsx)(t.code,{className:"language-rust",children:"fn build_handler(message: String) -> Fn(&mut Request) -> IronResult<Response> {\n move |_: &mut Request| {\n Ok(Response::with((status::Ok, message)))\n }\n}\n"})}),"\n",(0,o.jsxs)(t.p,{children:["We've simply set up a function that returns another anonymous function with the\n",(0,o.jsx)(t.code,{children:"message"})," parameter scoped in. If you compile this, you get not 1, not 2, but 5\nnew errors. 4 of them are the same though:"]}),"\n",(0,o.jsx)(t.pre,{children:(0,o.jsx)(t.code,{children:" error[E0277]: the trait bound `for<'r, 'r, 'r> std::ops::Fn(&'r mut iron::Request<'r, 'r>) -> std::result::Result<iron::Response, iron::IronError> + 'static: std::marker::Sized` is not satisfied\n"})}),"\n",(0,o.jsx)(t.p,{children:"...oookay. I for one, am not going to spend time trying to figure out what's\ngoing on there."}),"\n",(0,o.jsxs)(t.p,{children:["And it is here that I will save the audience many hours of frustrated effort.\nAt this point, I decided to switch from ",(0,o.jsx)(t.code,{children:"iron"})," to pure ",(0,o.jsx)(t.code,{children:"hyper"})," since using\n",(0,o.jsx)(t.code,{children:"hyper"})," would give me a much simpler API. All I would have to do is build a\nfunction that took two parameters as input, and we're done. That said, it\nultimately posed many more issues because I started getting into a weird fight\nwith the ",(0,o.jsx)(t.code,{children:"'static"})," ",(0,o.jsx)(t.a,{href:"https://doc.rust-lang.org/book/lifetimes.html",children:"lifetime"}),"\nand being a Rust newbie I just gave up on trying to understand it."]}),"\n",(0,o.jsxs)(t.p,{children:["Instead, we will abandon (mostly) the curried function attempt, and instead\ntake advantage of something Rust actually intends us to use: ",(0,o.jsx)(t.code,{children:"struct"})," and\n",(0,o.jsx)(t.code,{children:"trait"}),"."]}),"\n",(0,o.jsxs)(t.p,{children:["Remember when I talked about a lack of non-trivial examples on the Internet?\nThis is what I was talking about. I could only find ",(0,o.jsx)(t.em,{children:"one"})," example of this\navailable online, and it was incredibly complex and contained code we honestly\ndon't need or care about. There was no documentation of how to build routes that\ndidn't use static functions, etc. But, I'm assuming you don't really care about\nmy whining, so let's get to it."]}),"\n",(0,o.jsxs)(t.p,{children:["The ",(0,o.jsx)(t.code,{children:"iron"})," documentation mentions the ",(0,o.jsx)(t.a,{href:"http://ironframework.io/doc/iron/middleware/trait.Handler.html",children:(0,o.jsx)(t.code,{children:"Handler"})})," trait as being something we can implement.\nDoes the function signature for that ",(0,o.jsx)(t.code,{children:"handle()"})," method look familiar? It's what\nwe've been working with so far."]}),"\n",(0,o.jsxs)(t.p,{children:["The principle is that we need to define a new ",(0,o.jsx)(t.code,{children:"struct"})," to hold our data, then\nimplement that ",(0,o.jsx)(t.code,{children:"handle()"})," method to return the result. Something that looks\nlike this might do:"]}),"\n",(0,o.jsx)(t.pre,{children:(0,o.jsx)(t.code,{className:"language-rust",children:'struct EchoHandler {\n message: String\n}\n\nimpl Handler for EchoHandler {\n fn handle(&self, _: &mut Request) -> IronResult<Response> {\n Ok(Response::with((status::Ok, self.message)))\n }\n}\n\n// Later in the code when we set up the router...\nlet echo = EchoHandler {\n message: "Is it working yet?"\n}\nrouter.get("/", echo.handle, "index");\n'})}),"\n",(0,o.jsxs)(t.p,{children:["We attempt to build a struct, and give its ",(0,o.jsx)(t.code,{children:"handle"})," method off to the router\nso the router knows what to do."]}),"\n",(0,o.jsx)(t.p,{children:"You guessed it, more errors:"}),"\n",(0,o.jsx)(t.pre,{children:(0,o.jsx)(t.code,{children:" error: attempted to take value of method `handle` on type `EchoHandler`\n"})}),"\n",(0,o.jsx)(t.p,{children:"Now, the Rust compiler is actually a really nice fellow, and offers us help:"}),"\n",(0,o.jsx)(t.pre,{children:(0,o.jsx)(t.code,{children:" help: maybe a `()` to call it is missing? If not, try an anonymous function\n"})}),"\n",(0,o.jsx)(t.p,{children:"We definitely don't want to call that function, so maybe try an anonymous\nfunction as it recommends?"}),"\n",(0,o.jsx)(t.pre,{children:(0,o.jsx)(t.code,{className:"language-rust",children:'router.get("/", |req: &mut Request| echo.handle(req), "index");\n'})}),"\n",(0,o.jsx)(t.p,{children:"Another error:"}),"\n",(0,o.jsx)(t.pre,{children:(0,o.jsx)(t.code,{children:" error[E0373]: closure may outlive the current function, but it borrows `echo`, which is owned by the current function\n"})}),"\n",(0,o.jsx)(t.p,{children:"Another helpful message:"}),"\n",(0,o.jsx)(t.pre,{children:(0,o.jsx)(t.code,{children:" help: to force the closure to take ownership of `echo` (and any other referenced variables), use the `move` keyword\n"})}),"\n",(0,o.jsx)(t.p,{children:"We're getting closer though! Let's implement this change:"}),"\n",(0,o.jsx)(t.pre,{children:(0,o.jsx)(t.code,{className:"language-rust",children:'router.get("/", move |req: &mut Request| echo.handle(req), "index");\n'})}),"\n",(0,o.jsx)(t.p,{children:"And here's where things get strange:"}),"\n",(0,o.jsx)(t.pre,{children:(0,o.jsx)(t.code,{children:" error[E0507]: cannot move out of borrowed content\n --\x3e src/main.rs:18:40\n |\n 18 | Ok(Response::with((status::Ok, self.message)))\n | ^^^^ cannot move out of borrowed content\n"})}),"\n",(0,o.jsxs)(t.p,{children:["Now, this took me another couple hours to figure out. I'm going to explain it,\nbut ",(0,o.jsx)(t.strong,{children:"keep this in mind: Rust only allows one reference at a time"})," (exceptions\napply of course)."]}),"\n",(0,o.jsxs)(t.p,{children:["When we attempt to use ",(0,o.jsx)(t.code,{children:"self.message"})," as it has been created in the earlier\n",(0,o.jsx)(t.code,{children:"struct"}),", we essentially are trying to give it away to another piece of code.\nRust's semantics then state that ",(0,o.jsx)(t.em,{children:"we may no longer access it"})," unless it is\nreturned to us (which ",(0,o.jsx)(t.code,{children:"iron"}),"'s code does not do). There are two ways to fix\nthis:"]}),"\n",(0,o.jsxs)(t.ol,{children:["\n",(0,o.jsxs)(t.li,{children:["Only give away references (i.e. ",(0,o.jsx)(t.code,{children:"&self.message"})," instead of ",(0,o.jsx)(t.code,{children:"self.message"}),")\ninstead of transferring ownership"]}),"\n",(0,o.jsx)(t.li,{children:"Make a copy of the underlying value which will be safe to give away"}),"\n"]}),"\n",(0,o.jsxs)(t.p,{children:["I didn't know these were the two options originally, so I hope this helps the\naudience out. Because ",(0,o.jsx)(t.code,{children:"iron"})," won't accept a reference, we are forced into the\nsecond option: making a copy. To do so, we just need to change the function\nto look like this:"]}),"\n",(0,o.jsx)(t.pre,{children:(0,o.jsx)(t.code,{className:"language-rust",children:"Ok(Response::with((status::Ok, self.message.clone())))\n"})}),"\n",(0,o.jsx)(t.p,{children:"Not so bad, huh? My only complaint is that it took so long to figure out exactly\nwhat was going on."}),"\n",(0,o.jsx)(t.p,{children:"And now we have a small server that we can configure dynamically. At long last."}),"\n",(0,o.jsxs)(t.blockquote,{children:["\n",(0,o.jsxs)(t.p,{children:["Final sidenote: You can actually do this without anonymous functions. Just\nchange the router line to:\n",(0,o.jsx)(t.code,{children:'router.get("/", echo, "index");'})]}),"\n",(0,o.jsxs)(t.p,{children:["Rust's type system seems to figure out that we want to use the ",(0,o.jsx)(t.code,{children:"handle()"})," method."]}),"\n"]}),"\n",(0,o.jsx)(t.h2,{id:"conclusion",children:"Conclusion"}),"\n",(0,o.jsx)(t.p,{children:"After a good long days' work, we now have the routing functionality set up on\nour application. We should be able to scale this pretty well in the future:\nthe RSS content we need to deliver in the future can be treated as a string, so\nthe building blocks are in place."}),"\n",(0,o.jsx)(t.p,{children:"There are two important things I learned starting with Rust today:"}),"\n",(0,o.jsxs)(t.ol,{children:["\n",(0,o.jsx)(t.li,{children:"Rust is a new language, and while the code is high-quality, the mindshare is coming."}),"\n",(0,o.jsx)(t.li,{children:"I'm a terrible programmer."}),"\n"]}),"\n",(0,o.jsxs)(t.p,{children:["Number 1 is pretty obvious and not surprising to anyone. Number two caught me\noff guard. I've gotten used to having either a garbage collector (Java, Python,\netc.) or playing a little fast and loose with scoping rules (C, C++). You don't\nhave to worry about object lifetime there. With Rust, it's forcing me to fully\nunderstand and use well the memory in my applications. In the final mistake I\nfixed (using ",(0,o.jsx)(t.code,{children:".clone()"}),') I would have been fine in C++ to just give away that\nreference and never use it again. I wouldn\'t have run into a "use-after-free"\nerror, but I would have potentially been leaking memory. Rust forced me to be\nincredibly precise about how I use it.']}),"\n",(0,o.jsx)(t.p,{children:"All said I'm excited for using Rust more. I think it's super cool, it's just\ngoing to take me a lot longer to do this than I originally thought."})]})}function d(e={}){let{wrapper:t}={...(0,r.a)(),...e.components};return t?(0,o.jsx)(t,{...e,children:(0,o.jsx)(c,{...e})}):c(e)}},50065:function(e,t,n){n.d(t,{Z:function(){return a},a:function(){return i}});var s=n(67294);let o={},r=s.createContext(o);function i(e){let t=s.useContext(r);return s.useMemo(function(){return"function"==typeof e?e(t):{...t,...e}},[t,e])}function a(e){let t;return t=e.disableParentContext?"function"==typeof e.components?e.components(o):e.components||o:i(e.components),s.createElement(r.Provider,{value:t},e.children)}},10510:function(e){e.exports=JSON.parse('{"permalink":"/2016/10/rustic-repodcasting","source":"@site/blog/2016-10-22-rustic-repodcasting/index.mdx","title":"A Rustic re-podcasting server","description":"Learning Rust by fire (it sounds better than learning by corrosion)","date":"2016-10-22T12:00:00.000Z","tags":[],"readingTime":10.405,"hasTruncateMarker":true,"authors":[{"name":"Bradlee Speice","socials":{"github":"https://github.com/bspeice"},"key":"bspeice","page":null}],"frontMatter":{"slug":"2016/10/rustic-repodcasting","title":"A Rustic re-podcasting server","date":"2016-10-22T12:00:00.000Z","authors":["bspeice"],"tags":[]},"unlisted":false,"lastUpdatedAt":1730863976000,"prevItem":{"title":"PCA audio compression","permalink":"/2016/11/pca-audio-compression"},"nextItem":{"title":"Event studies and earnings releases","permalink":"/2016/06/event-studies-and-earnings-releases"}}')}}]);