The Relicans

loading...
Cover image for From FastAPI to Rust with Dylan Anthony

From FastAPI to Rust with Dylan Anthony

Mandy Moore
Single Mom 👩‍👧 🐶😺😺😺😺 Owner/producer: Greater Than Code 💕 #DevRel 🥑 WiT/D&I 👩🏻‍💻 Podcast Production 🎙 #BlackLivesMatter #python 🐍 she/her
・35 min read

Relicans host, Aaron Bassett, talks to Senior Fullstack Software Engineer at Rigetti, Dylan Anthony, about how quality is core to everything they do. Their goal is to make all the software in the world better, faster, and less frustrating to users which is why they talk about switching from FastAPI to Rust!

Should you find a burning need to share your thoughts or rants about the show, please spray them at devrel@newrelic.com. While you're going to all the trouble of shipping us some bytes, please consider taking a moment to let us know what you'd like to hear on the show in the future. Despite the all-caps flaming you will receive in response, please know that we are sincerely interested in your feedback; we aim to appease. Follow us on the Twitters: @PolyglotShow.

play pause Polyglot

Jonan Scheffler: Hello and welcome to Polyglot, proudly brought to you by New Relic's developer relations team, The Relicans. Polyglot is about software design. It's about looking beyond languages to the patterns and methods that we as developers use to do our best work. You can join us every week to hear from developers who have stories to share about what has worked for them and may have some opinions about how best to write quality software. We may not always agree, but we are certainly going to have fun, and we will always do our best to level up together. You can find the show notes for this episode and all of The Relicans podcasts on developer.newrelic.com/podcasts. Thank you so much for joining us. Enjoy the show.

Aaron Bassett: Hello, everyone. So today on the show I am joined by Dylan Anthony. Dylan is a prolific blogger, and they have recently been documenting their transition from Python and FastAPI to Rust on dylananthony.com. Hello, and welcome to the podcast, Dylan.

Dylan Anthony: Hey, it's great to be here.

Aaron: Hi, so happy to have you on. Okay, so I really want to get into this whole FastAPI, Rust transition because FastAPI is, I have to admit, one of my favorite frameworks. [chuckles] And so I'd be really interested to know why you're making that transition across to Rust. But before I do, there are a couple of quotes on your GitHub profile that I loved. When I was doing my research into this podcast and yourself, I checked out your blog, and I checked out your GitHub, and there's a couple of things you have in your profile. And the first one is this quote here about "Quality is core to everything I do. I want little more than for all the software in the world to be better, faster, and less frustrating to users." That's great. As somebody in Developer Relations, that really resonated with me. Where did you come up with this, and how does it impact how you develop?

Dylan: So, yeah, it's like my core personal mission that I carry through every project that I do, whether it's a full-time professional job or in my open-source work. It really is a core focus of mine. As far as where it comes from, I think it's probably due to the fact that I have an IT background. So before I got into software engineering, I spent a lot of time doing help desk work and some enterprise IT stuff. And just seeing all of the frustrating idiosyncrasies of lots of software, especially in some really crucial fields like healthcare and stuff like that where the software has to stick around for a long time, just seeing how bad even some little bugs can be over time, I think it's probably where that drive for wanting better software quality really comes from. [laughs]

Aaron: Yeah, working on any kind of support desk will really help you build your empathy for other people.

Dylan: Yeah, absolutely.

Aaron: So did this strive for quality software influence your decision to move to Rust? Is that too much of a leap? [laughs]

Dylan: No, there's a direct line. You can definitely connect one point to the other. Rust as a language just provides more tools than any other language I've ever worked with to enhance your software quality, and so that really is the primary motivator for me in particular in trying to move some Python work that I've been doing towards Rust, specifically FastAPI stuff towards some sort of a replacement in the Rust ecosystem.

Aaron: So you talked a little bit there about Rust tooling. Can you go into a little bit more detail about that? What does it offer that really attracted you?

Dylan: Okay. So I love exploring new languages, and I do that all the time. And so, at one point a few years ago, Rust just happened to come up in the list of languages that all right, well, it's time to try something new and let me play with this one. And it's like a systems-level language, at least how it was built at the time, and it is now too. But they've sort of changed their mission a little bit. But I was like, well, I'm not really into C or C++, so this probably isn't for me, but I got to give it a go. I like trying new languages. And I think the thing that really captured my attention pretty early was the fact that they didn't have null as a concept. And that is something that has come up just so many times throughout different projects I've worked on as just a horrible, sneaky bug that gets in.

In most languages, their type system, however it's defined, will always have a special exception for null where everything is whatever type it is but also may be null. And a lot of languages are becoming better about that over time, static checkers, that sort of thing. But the fact that there just wasn't a null, you literally couldn't break the type system that way in Rust. You have to use...there are fancy words for it, but it's an enum. So you can have sort of multiple types, and options is what they use instead. So either this thing isn't something, or it is something. And you can express the same ideas as you would with null, but it's not an exception to this type system. It's just a normal part of it, just like every other type. And that change, that difference was so wild to me coming from a background of mostly Python and Java that I just immediately was intrigued, and I had to dig deeper, and the deeper I got, the more I fell in love with Rust. [chuckles]

Aaron: Yeah, because that's a weird kind of concept. When you first said it didn't have a null, I was like, okay, that's strange. But as you explained it, something is either something, or it's something else. That does kind of make sense, really, when you think about it.

Dylan: Right. Exactly. And the other one that comes...first of all, it's the same system, and generally, they're called algebraic data types, this concept of it's a type that's more than one type, and you have to actually check to see what it is before you could do anything with it. So it's like a union of types but more strict than that. So the other thing that comes out of that is the way that Rust does error handling, which is not unique to Rust but was unique to Rust in the languages that I use. So instead of exceptions, runtime exceptions, like most languages, I was using use, so Python, Java, JavaScript are all exception-based errors. Rust, again, it's just a type. You get a result type, and it's either an error of some type or okay of some type. And so you have to check for the errors. You can't forget to check for an error. And your program's not going to mysteriously blow up because you added a string to an integer, and you didn't mean to.

Aaron: So it kind of just wraps everything in a try then. [laughs]

Dylan: Well, so in some Python applications that I've worked on, ones that were particularly important to never fail, we would have four or five layers of try excepts at different stages just to make sure if somewhere, somehow we forgot to check for an error it won't crash the whole thing. It'll stop here. And then you just get nested, nested, nested blocks of try and excepts, which are just a whole bunch of sort of catch-alls for, I don't know, maybe there might be an error, and I'm not sure what to do about it versus something like Rust where you know exactly every single possible error condition that could happen and how to handle it.

Aaron: Yeah. Because it's so easy in Python sometimes to just make error exceptions, and I think that's a little bit too broad. You end up starting to catch exceptions that you really shouldn't be; that should be something…that you're aware that you have some kind of flaw in your logic. But it turns debugging and error tracking into a whole mess.

Dylan: Yeah. And in Python, linters will often tell you, "Hey, you shouldn't have such a broad exception clause." Like, "Hey, don't catch every exception. That's an anti-pattern." But if you really don't want your application to crash, you can't deal with it just rolling a stack trace. Then you have to have a broad exception at some level to catch everything because, especially in a web application, you don't want to send back your stack trace to the user somehow by accident.

Aaron: Definitely not. [laughs] It's a little bit too much information sometimes given away in those to have that public, really.

Dylan: A little bit. [chuckles]

Aaron: Just slightly. So it kind of sounds like the Rust...you're intriguing me here because I'm a Python developer. I've been a Python developer for a very long time, and that's really been kind of my stack since I moved away from PHP so long ago that I can't remember when. [laughs] But this idea of having a language that it sounds like is trying to stop me from shooting myself in the foot, something I'm very good at, that does sound kind of intriguing. [chuckles]

Dylan: Right, yeah. And so I think that the greatest strength for Rust for me personally is what a lot of people also cite as its greatest weakness which is it can be very verbose because it makes you check everything. It makes you be sure and be explicit about everything you're doing. And there are ways around that. There are some escape hatches to say, "I really don't want to deal with this right now and just let it crash, and it's fine. This doesn't need to be reliable." But the default is to make sure that you don't shoot yourself in the foot. And that can lead to a pretty steep learning curve initially for Rust just because there's a lot of new concepts, a lot of new syntax, especially if you're coming from a higher level scripting type language.

So the appeal for Rust, I think, for a lot of people is the speed of it. So you might think that coming from a language like Python, which is traditionally one of the slowest mainstream programming languages just execution speed-wise, that maybe the reason that I'm trying to switch to Rust is just performance, but that's just nice to have for me. It's convenient that it also runs blazingly fast, but that's not why I'm here. I'm here for the safety and the security of knowing that the language is going to stop unknown stuff from happening most of the time anyway.

Aaron: It's funny that you mention Python being known as quite a slow language. And there's a phrase that I've heard brought up that’s like Python is optimized for developer cycles, not processor cycles. Python may be slow to run, but it's optimized to be easy to write. Do you think with the verboseness of Rust, then is there still a place for having languages like Python where it's really easy to put together a proof of concept, or would you still, for everything, just turn to Rust now?

Dylan: So obviously, every language is created for a reason, and the communities keep using them for a reason. And Python has a lot going for it. It's the language I had the most experience with. So I sort of both have a deep fondness for it, but also I see its flaws unlike I could see any other languages’ flaws just from having been so deep in the nitty-gritty. The few complaints that are really popular amongst people talking about Rust are, one, the learning curve is kind of steep, and there's a lot of verbosity. And the other thing is compile times, which isn't something you really think about until you have quite a large Rust application, and it's taking you several minutes waiting to compile. It really is significant when you're trying to do rapid iterations on things with something like Python.

And so you do have to think of it as a sliding scale definitely between the amount of time you're spending writing versus the amount of time it's going to spend running. And I would say the further your application falls on the scale of it's going to run a long time and not need a ton of modifications, the more likely you are to pick something like Rust. And on the other side of things, you have things like Python, which are very quick to write, things like Go are very quick to write and sort of fall in between Python and Rust on that scale. But yeah, if you need to make that rapid iteration and compile times are important to you and just prototyping time is important to you, that's where something other than Rust would probably fall in place.

Something I have definitely experienced is trying to whip together a quick script, which does some stuff which doesn't need to be infallible. I just really want to try to parse this data that I got from this source and turn it into a log file or something. Or I'm trying to whip up a quick prototype, and it doesn't need to be production-grade, but I just need something that runs in the next hour. Rust is going to frustrate you if you're trying to rush with it basically because it's going to force you to do everything right. You can't just say, "I'll handle that later." Sometimes you can, but for the most part, you can't just say, "Oh, I'll handle that later. I just want to get the happy path done right now." So yeah, for sure, if I was reaching to do a project that writing quickly was the most important thing, I would still reach for Python.

Aaron: But then I guess you were saying that it's forcing you to do things correctly the first time around. There's probably still Python code of mine at previous jobs I wrote from a decade ago that has comments going quick hack --

Dylan: [laughs] Right. That is definitely I think something that we're all guilty of as software engineers is for one reason or another, we write what we consider to be a proof of concept, or it's just for the demo or whatever it is, and then that makes it into production.

Aaron: Yeah. [laughs]

Dylan: And so you definitely need to be careful about that. If you're writing something that could...or that's intended to eventually get to production, in my opinion, [chuckles] you should try to do it as right as possible the first time with something like Rust helping you along the way. Whereas if it's really a script that you're running on your local machine to like, you know...so if you're doing a lot of data science, I think Python still makes a ton of sense because I feel like, at least in my experience, a lot of data science is prototyping and experimentation and rapidly changing things. And you might never run the same piece of code twice. You're switching between things all the time. And then maybe once you move that into a long-term ETL pipeline or something where you're like, this is definitely how we're going to do it, then maybe you reach for a different tool other than Python. But yeah, the data science tooling in Python is pretty incredible. So I think it's always going to have a home there.

Aaron: Yeah, it's so interesting as well if you do look at different applications for languages. So I'm a web application developer. That's what I did for many years before getting into developer relations. But now, with being in developer relations for so long and seeing other parts of the Python community, just seeing the approach of different parts of the community to how they develop is so interesting. The data science, as you were talking about there, it's really just about okay, I have some data, and I need to run some processing on it, and I want an output. How do I get from input to output? Don't really care, and I'm probably never going to go back and modify it. So whenever you see how they're setting up their projects, and you have this incredibly detailed README which essentially boils down to works on my machine, [laughter] it’s a completely different world. And it just shows, even within the same language, how people can approach things differently.

So we've heard a lot about things that Rust does better. And you also mentioned that you worked with a lot of different languages and that you love learning new languages. And you have this blog about cross-pollination, which is really interesting as well, like, what have you learned from one language and applied it to another? So with you still doing a lot of work in Python, what have you taken from Rust that you now bring into your Python code?

Dylan: Several things. So something that fairly recently, I maintain an open-source project called openapi-python-client, which takes an open API spec and generates the Python code. And that involves a lot of parsing and sanitation and transforming strings from one format to another. And you're trying to take some arbitrary string that someone gives you and turn it into a valid Python identifier or a valid Python class name or whatever it is. And so, something that I applied fairly recently...I sort of got the concept from the way Rust does things is instead of just using a string for that, I created a new type.

So I needed to generate the name of a class, and I call that type class. And I can create the name of a class from a string and be confident that that's a valid class name and that that's what that string represents, and now it's a class object. So then I just use that type instead of string. So let's say the code takes in the specification of the class and then generates the Python code related to that. It won't take in a string for class name. It'll take in a class. Therefore, you can enforce that you've already done the validation, the transformation and that this is the right type of string, not just a string. That's the sort of thing that Rust type system really encourages you is to encode your business logic in the types themselves so that you can be much more confident that you're doing the right thing.

And actually, the reason I applied that particular solution there was because we encountered a bug in that project where we were using the wrong string in the wrong place. So in OpenAPI, there's a concept of a reference, which is how you refer to some other piece of the document, some other pieces of the spec. And that reference is what we were generating the class names from. But if we use the wrong one in the wrong place, we've either broken the reference because we're trying to use a class name to refer to something and that doesn't exist, or we've generated an invalid class name because of the reference. And they were getting a little mixed up because one comes from the other one. So creating a separate reference type and a separate class type just to enforce. So I use a static type checker, and I think the most popular one, the one I use anyways, is Mypy in Python. So if you put these types in place, then Mypy will check that hey, you're not passing in the wrong thing to the wrong place. It'll be able to enforce for you that you've done what you intended to do.

So it goes hand in hand with really good testing. Obviously, you want to write really, really good and thorough tests in order to enforce that all your business logic is working the way that you intended to, but this is just sort of another layer on top of that. First, you can see when you're reading the code you can see clearly what is this? It's not just a string. What contextually is the string? What is the significance of it? And it provides that extra barrier with Mypy to enforce that you're not putting the wrong thing in the wrong place. So that's I think the biggest one that I took from Rust is using types to enforce logic.

Aaron: I can't believe you would have any kind of issue with strings in Python. [laughter] That is the first time that's ever happened.

Dylan: Another one actually related is, and I'll make this one shorter is units. [chuckles] So when you're dealing with numbers in Python, they're often either all floats or all ints. And it's really hard to tell what that represents. So if you're dealing with temperatures, is this in Fahrenheit? Is it in Celsius? Is it in Kelvin? If you've got multiple different sources and equations that need different units, it's really handy to just create a temperature class, and then you have to explicitly tell it what units you're putting in or taking out. That way, you never get it wrong because you can't get it wrong.

Aaron: I think I actually talked about this in a previous episode of the podcast with handling numbers in any program, especially Python, is so difficult sometimes, especially whenever you're talking about numbers that do have a unit, whether it be currencies or temperatures or anything like that. We had a project that went through a partial rewrite at a company that will remain nameless. And as part of the rewrite, they changed how they were doing rounding and in one particular section of the application. And it meant that the financial records that were coming out of it were slightly different than before the rewrite, which when we discovered it some of our customers at the time had been using those reports to do their taxes. That was difficult to explain. It's like, yeah, you’ve either been filing the wrong taxes before, or you're going to be filing the wrong taxes now, and we can't tell you what it is. [laughs]

Dylan: I think Python, going from 2 to 3, I think, they changed the rounding behavior. Integer division used to be truncated, and then it started rounding. And now you can get a different operator if you still want to truncate it. But a codebase I was working on was all Python 2. We converted it all to Python 3, and a few months later, we realized that there were some rounding problems deep down in the guts of the program. Everything else worked fine. We'd converted everything properly, but that operator changed, and we missed it.

Aaron: Yeah, it's something I think happened to a lot of people. It's also something that was very difficult to explain to newcomers coming into Python as well that you divide two numbers and you don't get a remainder. If you divide two ints, you're going to get an int. Or if you divide an int and a float, well, you're going to get a float. And it was one of those ones where you don't know if it actually did make sense or if you're just being conditioned to think it made sense.

Dylan: Yeah. There are a lot of conveniences that have been added to languages like that where it makes sense to the people who write the language, but it can definitely be difficult to people who aren't as familiar with it. I still have to check sometimes, depending on what language I'm using. I'm dividing these ints; what’s the behavior going to be? I think Rust mostly has functions that you can call, which have an explicit how should I round this behavior? But it's just so convenient to be able to use mathematical symbols to represent your equations. And then you got to double-check, double-check everything. That's a good place for tests, I guess. [chuckles]

Aaron: Yup. Very much so. The whole cross-pollination, the fact you have learned a lot of different languages then, do you find it difficult sometimes in remembering these small convention changes between languages, or does it help you to have this broader view of the way different languages handle certain things?

Dylan: Yeah, I think both are definitely true. So context switching can take a little bit of time. Like for me, to get into TypeScript mode or get into Java mode after writing a bunch of Python takes a bit of time. Rust and Python I have no problem with just because I use them so much. So it's sort of just an automatic switch for me at this point. Yeah, it was a little hard in the beginning. But the languages I use less often definitely take a little extra work to get my mind in the right place and stop using the wrong syntax. But on the other hand, yeah, I think the perspectives that the different languages and the different ecosystems really provide is huge because it goes beyond just like, oh, well, Rust has this concept of algebraic types, and so I should try to do the same thing in Python.

There are idioms in different languages, which the community has decided that that's the best way you should do this thing. That really leads you to change the way you write code in general. And even the differences in the way package management works and the way that the API, the language itself evolves over time can really influence how you think about things in a way that I think a singular language if that was your entire stack, wouldn't open your mind to that concept. I mean, diversity is sort of, in general, a great way to expand your way of thinking and lead you to solutions that you wouldn't have otherwise gotten to without that perspective.

Aaron: Yeah, I think that that's so true across so many aspects of our personal and professional lives as well. Having diversity really does help with our thinking and ideas that we've brought to the table. One thing you had mentioned there that I just want to ask you about is package management. So package management it's a difficult subject. And is it fair for me to say it's a difficult subject in Python?

Dylan: Yeah. Yeah. There are a lot of ways to do it, and none of them feel particularly great. [laughs]

Aaron: And I do think so as well. I think for a very long time, PyPI, the Python Package Index, was a solo kind of developer managing the whole thing. So I in no way want to throw any shade in what they've done or the work that was put into it. To have such a fundamental part of I think the whole internet really be down to a singular person or maybe two people is almost unbelievable. In no way do I want to disparage what they've done. But yeah, it is an area where people seem to find a lot of confusion in Python is; how do you do packaging? From all the languages that you've used, which is the one that you think has got it closest to, correct?

Dylan: It'll probably come as no shock when I say Rust.

Aaron: [laughs]

Dylan: The languages I've done a significant amount of package management in are Python, JavaScript, NPM, and Rust's Cargo. And of those, if I could pick one, I would definitely pick Cargo. It's not perfect, and I don't think any package management system is ever going to be perfect because it's a very hard problem to solve. And there are trade-offs you have to make. You have to decide, for example, between network performance of an individual install versus the upfront cost of caching an index. So Cargo will download the entire index whenever you try to install something. And you just have a local copy of that on your machine with a few gigabytes of disc space being taken up, but that means that subsequent calls to install things and to resolve packages are very quick locally.

Python is sort of at least the way that most package managers…so thinking like Poetry is the one I use the most. They tend to resolve things one at a time, and they have to download the metadata for a package, then check all the requirements of the package, and then go and get the next package and so on, which can take a significant amount of time just to do the resolution step.

So I think the main thing that Cargo solves is the idea of having multiple versions of a single package. So that's the thing that's a big no-no in Python. You can only have one version of a package installed in a particular environment. And typically, you'll use virtual environments for each project in order to keep your dependencies from conflicting with other projects. I guess one example is if you're using something like a Flask framework, a lot of Flask is built with extensions to Flask. And so you can't update any of your extensions unless all of them have been updated for whatever version of Flask you're trying to set it to because they all have to share the same version. And that's the thing that has caused a little grief. It becomes even more difficult with something that's lower level, like if you're using gevent or eventlet or something that lots and lots of different stuff might depend on. Then it can be a little frustrating to try and upgrade that package because you might have to wait for a bunch of other things.

The way that Rust does, it is, or I guess Cargo does it, which is Rust's dependency management tool among other things is, for the most part, links dependencies based on their name and their semantic version and semantic versioning is extremely...it's not enforced, but it's highly encouraged in the Rust ecosystem. It's very rare that you'll find a package in the Rust ecosystem that isn't semantically versioned. And if you're not familiar with semantic versioning, it's basically the version string, which is usually like 1.2.3 or something like that. Each part of that represents something. It's not just, oh, it's version 2 because it's newer. It's version 2 because there was a breaking change to the API. And so that lets you keep track of things like what features from a particular package does my library depend on, and what compatible versions can you actually use with it?

So with Cargo, you could have three or four different major versions of a single package. They'll get linked with the name of the package and also the version of the package, which means that if you depend on Serde 1.2 and your dependency depends on Serde 0. something, you can have both, and everything will just work fine. The only exception there is if you need to pass a data type from a dependency because once everything's compiled if your function needs to...let's say you've got your package, and you've got something you depend on. And then something that you depend on that your dependency depends on. So it's a little complicated, but it's basically you want version one of Serde, we'll just use that as an example, and something else that you're using wants version zero of Serde.

Okay. So if you construct something with Serde and try to pass it to that other package, that's not going to work because that thing is expecting a version 0. whatever of that class. So there are some instances where you still can't do that because it can't just magically make different versions compatible with each other, but it will allow you to install different versions of dependencies which just simplifies things a lot. You're not having to...I cannot count the amount of times that I've had to dig into the dependency graph of a Python project I was working on to figure out where is version XYZ coming from of this thing because I need a newer version. And then either see is there an update to dependency, or do I need to go make a pull request on GitHub in order to try and get them to update it because I need something slightly different? Or do I do some nonsense with namespacing where I try to make them different? It's much more difficult.

Aaron: Yeah. And it can be difficult to explore that dependency graph as well. You need to have...it's not something that comes built-in right to Python. And nor can you just go look in your site-packages folder because everything's just sitting at the same level. So being able to figure out where this dependency comes from is an interesting task as well. It also means uninstalling packages is just as difficult too because you can't uninstall a package's dependencies because you don't know if those dependencies are also being used in your project or being used by something else within your project or another package.

Dylan: Yeah. That's why I use Poetry as the only package manager I use in Python. I think that GitHub handles [inaudible 29:04]. I'm not sure the name of the person who writes it, but it's a fantastic tool and certainly the best thing I've found. It's slow. So when you go to add a package, it's going to resolve your entire dependency tree to make sure there are no conflicts and then pick the best version based on all the packages you have. So it's doing a whole bunch of work to figure out that everything you have is compatible with everything else you have. It also gives you a lock file, which shows you the dependencies of all your dependencies. So you can trace things like that, which is the way I normally track those down. And if you remove something, it'll figure out what it can prune and get rid of everything that you don't need. Yeah, but it's certainly not included in Python by default. And it's not something that a beginner to Python will find easily. In fact, no one will find it unless you really go searching for it unless you get irritated enough with pip or maybe Pipenv that you're like there has to be a better way, and you go searching for it.

Aaron: And it's something that almost every Python developer you speak to at a certain point will have their own like, yes, this is a tool that I use. So like yourself, you use Poetry. I use pip-tools. You mentioned Pipenv as well is another one folks use. There's such fragmentation in the community around it, too, that it is difficult for newcomers coming in to understand what is the tool that I should be using? What is the accepted way because there is no accepted way? [laughs]

Dylan: Right. Yeah, absolutely. It really bleeds into the way that packages are used in the ecosystem as a whole and the way that libraries are written and maintained. And you get a really different perspective on when and how to use third-party packages, depending on what ecosystem you're in. So I think JavaScript has a very sort of just go for it vibe where they just install all the things that install all the things. And we'll have some tree shaking to get rid of the stuff that you don't need. With Python, I feel like packages tend to be more monolithic. They tend to be quite a bit larger. And I think it's because you want to include as many behaviors as you can internally to not rely on other dependencies because then you'll end up with a bunch of conflicts. And with Rust, dependencies tend to be very small because you're expected to end up with multiple copies of them at some point just because of the way the resolution works. And so, you get much more modular chunks of libraries. That's just what I've noticed. That's not a hard and fast rule, but I do find it interesting that the tooling seems to dictate how people actually end up using other code.

Aaron: Yeah. I think that's like really good examples there with Node versus Python. Some people may remember the left-pad, 11 lines of code that broke pretty much all of the internet because it was dependency by everything, just to add the ability to left-pad a string. Whereas you look at like...I still think that Django doesn't have any external dependencies. I could be wrong, but I'm pretty sure it is for that exact reason.

Dylan: Yeah. I'm not sure I know that a lot of fairly large libraries... so FastAPI, which we've mentioned earlier, is based on a combination of Starlette and Pydantic, and then tying that together with a bunch of nice interfaces. And I think that Pydantic doesn't have any dependencies. So I think it's actually implemented in C, but it does just a ton of stuff. It does so much stuff related to validation and creating JSON schemas and just being able to operate on nice classes. But I would assume that a lot of that functionality could be broken up into smaller pieces but is unlikely to be done in the Python ecosystem just because of the way package management works.

There's always this build versus buy mentality you have to have. You have to always sort of weigh the pros and cons of do I just write this thing myself or do I pick up a package? And I think that your ecosystem will definitely influence the way that that sort of algebra plays out in your head as far as is it worth it to pick up a third-party library, or is it worth it for me to invest the time to write something myself? Even openapi-python-client has its own complete implementation of OpenAPI schema parsing. There are other libraries that do it, but it was so hard to get them to work just the way I wanted them to, or I would need some specific behavior that they couldn't have, or they would make a change that would suddenly be incompatible with my project. And so, yeah, I forked and vendorized someone else's, and of course, credit still goes to them, and they're in the license and everything. But yeah, I think packages just tend to be quite a bit larger.

Aaron: Yeah, you have to be very thoughtful with whenever you're recruiting an open-source Python project because of the fact that anything that you bring in as a dependency could stump on some other packages dependency and cause that kind of incompatibility. So it's like, do I really need to bring in this package, or could I just implement the bits that I need myself with my own package? Yeah, there's a big trade-off there. But the last question I want to ask about packaging in Rust and with Cargo, so we've talked a lot about installing packages and package management. What about creating packages? Is that easier than it would be in Python? Is it something that is part of it or…?

Dylan: Yeah, again, I think Cargo is definitely the easiest experience I've ever had. It's similar to how Poetry works. So again, that's what I use in Python because it’s what I had the most experience with in that all your metadata is sort of in one file, and you type it out, and it's fine. The sort of vanilla way of doing things in Python involves a lot more work. You need a setup .py, which may or may not include some sort of requirements .text or maybe the other way around. And then sometimes you have a separate file for some additional metadata to pull in. So, yeah, with Cargo, the defaults are very sensible, and I think it just comes from it being a much newer toolchain. It was all really designed with other package managers in mind. Pip and Setuptools and all of that ecosystem didn't really have anything else to lean on when it came out. I don't remember exactly when it was, but it was, I think, around 10, maybe more than 10 years after Python had already been around. So they were sort of playing catch up trying to add package management to Python. And there was nothing else really to base it on because I don't think that NPM even existed yet.

Aaron: No, it definitely did not.

Dylan: Which is sort of one of the first things people think of, maybe Gradle and Maven in the Java ecosystem maybe, but that’s I don't think necessarily a good system to base package management on. Because I don't know if you've had an experience, but that can be quite frustrating. With Cargo, yeah, you just put some information in a single file, and it works sort of more similar to the way that NPM will set you up with your package .JSON. You'll have a Cargo .toml. You put some metadata. You can publish it right from there.

The one thing that I will say is harder in Rust is if you want to have private dependencies. So a lot of times in companies, you want to have your own private registry running somewhere that's not just on the public internet. So you can have your own dependencies and manage them the same way you'd manage any other dependency. It's usually a very nice pattern, and I've done that with Python fairly easily. There are a lot of services that support that. NPM; there are a million and a half different private repositories for that. I know RubyGems there's a bunch, and Java there's a bunch. With Cargo, there's, I think, one hosted service that allows you to do private dependency management. And I've only seen one open-source project that lets you run it yourself, and they're doing the best they can. Shout out to Cloudsmith, which is a great package management service and the only one that supports Cargo. So I'm very grateful. They also have a very friendly social media team that I've interacted with once or twice. [chuckles]

Aaron: That's always helpful.

Dylan: But yeah, and it's just down to the way that Cargo was implemented. I mean, it was designed to be part of crates.io, which is this whole open-source ecosystem for publishing Crates. And I think there's a lot of work to be done still to get it so that it's really comfortable to use as a private dependency. But on the other hand, Git dependencies are much easier to use in Cargo than they are in pip. So trade-offs.

Aaron: Yeah, I still remember the days when sharing code or wherever publicly or within your company was literally okay, I'm going to zip up or tar up these files, and I'll email them across to you. And then, at some point in the future, if I find a bug, I'll try to remember to send you the updated version as well.

Dylan: Right. In my past, we've tried Git submodules as a way to have shared dependencies, but those get out of date very quickly, and you don't have good versioning, you know, private dependencies. You can also try monorepos, but that comes with its whole other set of trade-offs. And yeah, I think the packaging ecosystem for software engineering, in general, is still really trying to find its footing. So, Deno, I think that's how it's pronounced; the Node replacement and Go both have a much different system where it's like you just point at URLs and download things, and it works. Whereas Go originally...and I've recently been getting back into Go. But like I said, I try languages all the time. So I tried Go a few years ago, and I was quite frustrated by the fact that it seemed to be designed only with monorepos in mind, and managing third-party dependencies was very difficult. You had to have one copy on your entire computer of a single dependency and everything sort of linked to it. So that's gotten a little bit better, and they sort of followed in the footsteps of what Deno is doing as far as...or maybe it was the other way around, I don't know, but fetching things directly from URLs.

So I think there's still a lot of exploration to be done there, decentralized versus centralized package managers. And how do you version things, and where do you keep caches for indices? But I'm excited to see where it goes, and I'm excited to see newer languages like Rust really thinking about this early and creating good developer tooling early because it's important. And it takes up a lot of developer time to be thinking about details that probably aren't relevant to the problem they're actually trying to solve.

Aaron: It goes right back to that quote we talked about at the start; it's all about making things less frustrating to users. And if you're going to spend a lot of time figuring out how you can run somebody else's code within your language, that doesn't sound like a less frustrating experience to me.

Dylan: Yeah, absolutely. And that's part of what I'm trying to do with my blog posts exploring web frameworks for Rust, FastAPI is, I think, just the best way to create Web APIs today. There is nothing even close in any language that I've tried. So whether that's in Node, which has a ton of Web API packages and frameworks available, or Go or anything, FastAPI just takes the cake. It's amazing. What Sebastián Ramírez has done with that project is just simply incredible. And I cannot say enough good things about it.

That being said, Python leaves a lot to be desired as far as correctness of using strong type systems and concurrent code. The async story in Python is still not great. It's fairly young. And as a result, the ecosystem is very split between async things and not async things. The most popular ORM, SQLAlchemy, now has an async mode in their latest release. But I think it's still not using asyncio underneath the hood. I think it's using green threads somehow and a little bit of Magic, which is cool. But it's chaotic trying to figure out how to effectively do concurrent code. And that usually leads to people doing things sequentially, which leads to slow performance, and I think that's part of why Python gets the reputation it does for being so slow. FastAPI is fast. It's not just in the name.

Aaron: [laughs]

Dylan: It is very fast for most common use cases for Web APIs.

Aaron: So it's not false advertising is what you're getting across then. [laughs]

Dylan: No, it's not false advertising. Oh my gosh. So there's actually a thread on GitHub where someone posted in GitHub, and they were like, "Hey, you're false advertising. Basically, FastAPI is not as fast as X, Y, or Z. And you claim that it's on par." And that thread has gotten a little heated. I've since muted it because I didn't want to keep participating in the negativity. But yeah, it depends on what you're using it for, and you should know going into it what sort of workloads you want to put on it and what your metric for performance really is, is it latency, or is it a throughput? But it is fast. It is fast enough for the vast majority of workloads. And I think it's the best way to create Web APIs today.

But that being said, I think that Rust as a language just has the potential to unlock even more as far as making sure your validation is really good, making sure you don't forget to authenticate endpoints, something that I've seen too many times doing some security research where authentication or authorization was just forgotten for a particular endpoint or set of endpoints. These are the sorts of things that you can use a type system like Rust type system to really enforce. And I think that we'll get there in a few years because it's going to take a while to catch up to the ergonomics of FastAPI. But it goes back to that barrier to entry thing we were talking about where FastAPI is just so easy to just start writing. And then you have something running, and it works, and it does all the things. And the tutorials, the documentation are absolutely fantastic. And right now, the barrier to entry for writing APIs in Rust is much higher. And so I'm trying to work on bringing that down a little bit, making it a little easier, a little more accessible.

Aaron: Well, I very much look forward to you launching a faster API in Rust.

Dylan: [laughs]

Aaron: I will definitely give it a try at that point. But we are out of time, unfortunately. I've been really enjoying this conversation, so we've actually run over. So thank you to all of our listeners that have hung around with us [laughs] for this little bit of extra time today. Before we go, though, I just want to give you...we've talked a little bit about your personal blog, which I think it's on dylananthony.com. Is there anything else you want to give a shout to, other projects, or where people can find you online?

Dylan: Yeah. If you want to follow me on Twitter, it's @TBDylan. That's where I sort of keep people apprised of everything that's going on, whether it's new blog posts or a new open-source project. So that's probably the best way to keep up with me.

Aaron: Fantastic. I just wanted to make sure I was following you; I already am. So I want to keep up to date with your new launches. This has been such a fun conversation, and you really have sparked an interest in me with Rust. I've seen so many Python people go to try Go. And I thought that would be my next thing to have a look at, but you may have changed my mind. I may have to check out Rust instead. So thank you very much for that. I've learned so much on this chat today.

Dylan: It was a lot of fun, and it was great talking to you. I've had a great time.

Aaron: Okay. Well, I will let everybody go then. And thanks very much for listening, and we'll see you next week.

Jonan: Thank you so much for joining us. We really appreciate it. You can find the show notes for this episode along with all of the rest of The Relicans podcasts on therelicans.com. In fact, most anything The Relicans get up to online will be on that site. We'll see you next week. Take care.

Discussion (0)