There have been a lot of changes in the low-level Python space these days.
The biggest has to be how many projects have rewritten core performance-sensitive
sections in Rust, or even the wholesale adoption of Rust for newer projects such as uv and Ruff.
On this episode, we dive into the tools and workflow needed to build these portions of
Python apps in Rust with David Seddon and Samuel Colvin. This is Talk Python to Me,
episode 487, recorded November 21st, 2024.
Are you ready for your host?
Here he is.
You're listening to Michael Kennedy on Talk Python to Me.
Live from Portland, Oregon, and this segment was made with Python.
Welcome to Talk Python to Me, a weekly podcast on Python.
This is your host, Michael Kennedy.
Follow me on Mastodon, where I'm @mkennedy, and follow the podcast using @talkpython,
both accounts over at fosstodon.org, and keep up with the show and listen to over nine years of
episodes at talkpython.fm. If you want to be part of our live episodes, you can find the live
streams over on YouTube. Subscribe to our YouTube channel over at talkpython.fm/youtube and get
notified about upcoming shows.
This episode is sponsored by Posit Connect from the makers of Shiny. Publish, share, and deploy all of your data projects that you're creating using Python.
Streamlit, Dash, Shiny, Bokeh, FastAPI, Flask, Quarto, Reports, Dashboards, and APIs.
Posit Connect supports all of them.
Try Posit Connect for free by going to talkpython.fm/Posit.
P-O-S-I-T. And it's brought to you by the Data Citizens Dialogues podcast from Colibra.
If you're ready for a deeper dive into the latest hot topics and data, listen to an episode at talkpython.fm/citizens.
Hey everyone, it's the week of Thanksgiving in the United States. You know what that means.
Black Friday sales galore.
Of course, we're having Black Friday sales at Talk Python as well.
All of our courses are on sale from between 18 to 50% off through our Everything Bundle.
Get huge discounts on the Everything Bundle by visiting talkpython.fm/Black Friday.
But be sure to hurry. This deal ends next week after Monday.
So act now if you've been looking at taking a couple of our courses.
The price is about two courses for the entire library.
So you probably want to jump on that.
And thank you to everyone who's taken some of our courses.
It really helps support the show.
Let's jump into that interview.
David, Samuel, welcome to talkpython.fm.
Thank you very much for having me.
Thank you for having me.
Yeah. Good to have you, David.
And Samuel, it's always good to have you back on the show.
So we're going to talk about Rust.
Some really cool experiences that David had building a linter that works on a ridiculous number of different projects and packages.
And Samuel, Rust is obviously core to Pydantic.
And I want to talk a bit about Pydantic and how you guys have used Rust as well.
Should be a good time.
Yeah. Looking forward to it.
And thanks for having me on.
I've been listening to this podcast for years.
So it's very nice finally to be talking.
It's a little like open source.
You get to help create it, you know?
Exactly.
All right.
Let's do quick introductions.
I know, Samuel, you almost don't need introductions.
Pydantic is so popular.
However, I'm sure there's a couple people out there.
Before I let you introduce yourself and stuff just a tiny, just quickly.
You have the honor, distinguished or maybe otherwise, of actually participating in the loudest Talk Python episode ever.
Do you remember that forklift that was driving around behind us?
It was dystopian, wasn't it?
It was the end of PyCon two years ago.
And we started doing the podcast.
And then they started taking down the PyCon around us.
And there was this forklift.
It felt like slight jeopardy, but it was good.
It certainly was a concentration test to have the forklift driving right behind us, beeping as loud as I could.
It was amazing.
All right.
Tell people about yourself and Pydantic and stuff.
So I started Pydantic back in 2017 as a side project.
And I toodled along.
And then 2021, it somehow, something happened.
And the rate of downloads just started to increase a lot.
Started working on it full time in 2022.
Decided to do this rebuild of the core in Rust.
Because while I was really proud of how much people were using Pydantic, I wasn't particularly proud of its internals.
I had done a bit of Rust, a bit of a couple of other projects that wrapped Rust to produce Python packages, but nothing on the scale of Pydantic.
And then eight months into this three-month project, I was halfway through.
And Sequoia very kindly got in touch and offered to invest.
And so I started a company around Pydantic.
And so, yeah, we released Pydantic V2, the rewrite, middle of last year.
And, yeah, adoption of Pydantic, as I'm pleased to say, continued to grow.
We had, I think, 307 million downloads in October.
And now we're obviously building commercial stuff, Logfire in particular.
But we also do a bit more Rust in Python.
So we have Jitter, which is our very fast Rust-based JSON parser, which is available both.
It's used in Pydantic and is a separate package.
So, yeah, that's a kind of summary of my interaction with Rust and Python over the last kind of five, six years.
Amazing.
Yeah, and it was that move, that pending move or partial move to Rust that actually was the basis of that forklift episode.
David, hello.
Hi.
Yeah, so I am based in London, like Samuel.
I work on a product called Kraken that is not anything to do with cryptocurrency or gin.
I think there's a gin.
Or Git clients.
It's actually, it came out of a company called Octopus Energy.
And it's, Octopus Energy is a renewable energy company.
And Kraken is basically a big Django monolith that we used at Octopus Energy.
And it worked really well.
And Octopus Energy over the last eight years has grown to be the second biggest energy company in the UK.
And so what we've been doing is using Kraken throughout the world in lots of different countries and lots of different energy companies.
I think the interesting thing about it is it's absolutely massive.
It's, I just counted it today, eight and a half million lines of code for one Django packet.
That is nuts.
I was going to have you elaborate because you said I work on a large Django monolith.
Like, it's really large.
That's amazing.
Sometimes I wonder, has there ever been a bigger one?
I don't know.
I should probably ask on Hacker News or something.
Yeah.
On the GitHub Notes.
It's big and it's also, there's quite a lot of people working on it at once.
There's, like I just looked at last week, there were over 400 authors of pull requests in one week, all merging stuff together onto the same Python packet.
And you'd think it wouldn't work.
I would have said it wouldn't work.
It turns out, actually, you can run an energy company on that basis.
You're a huge energy company.
You must have a ton of repositories.
Yeah, we got one.
There are some others, to be honest, but mostly it's one big one.
Can I ask a question?
Is that continuously deployed or how is that managed in terms of deploys?
Actually, each energy company, there are nearly 30 energy companies.
Each energy company gets their own separate installation.
And every time anyone merges, pretty much, we push it out to all of the energy companies.
You know, it's on the basis that really, if we break something, then we can fix it quickly as well.
And also, in the domain of energy, how often do you actually log in to your energy account?
You know, are you going to be on the phone saying, where's my bill?
Do you know what I mean?
We can get away with that kind of thing, which maybe we couldn't do that if it was, I don't know, a payments gateway or something like that.
Or if you built a package that shipped over PyPI to hundreds of millions of people.
Yeah.
We released Pydantic 2.10 yesterday, and we've definitely got quite a few issues back.
So we definitely break stuff too.
Don't, for a second thing, we don't break anything.
We don't mind the things that are unintentionally broken because we can go and fix them.
It's when we change an API.
That's the painful thing for us rather than actually, weirdly, bugs are in some sense, like, less severe than, like, getting the wrong API.
Yeah, that's got to be really tricky to say, you know, we need to add, we need to add a parameter to this function.
Or, you know, the whole one to two switch, you deprecated quite a few methods and stuff as well.
In some sense, deprecated methods and parameters are relatively easy because someone's, you know, it's relatively clear to see what's happened.
It's when it's, like, subtle changes in behavior.
Like, do you apply an, like, integer constraint before or after a function that wraps the integer validation?
Like, things like that, if you decide to change it because you're like, well, this is marginally more correct.
Is it worth making it marginally more correct for a lot of people spending a lot of time?
Very, very confused.
That can definitely generate some open issues on the issue tracker.
Also, the parsing.
I think you all changed the rules about the parsing, right?
One of the pieces of magic of Pydantic is you had a list and you said it was a list of integers, but it happened to be strings that could be converted to integers.
It would just become a list of integers.
But I feel like you changed the strictness or looseness of that behavior at one point, right?
Yeah, we made it a little bit more strict in places.
We said things like you can't coerce an integer to a string because basically the original semantics of Pydantic was like, we'll just call integer on the thing and see if it's an int.
And that mostly worked.
We'll call list on something and see if it's a list.
Problem is with string, you can call string on everything and then you'll get back a string, right?
So it's no longer a valid test of is it a string to call string on it.
What time did this happen?
It happened at angle bracket class datetime dot datetime dot.
And that's another thing because no one writes their unit tests for the weird edge cases of what's valid.
But then if you're a bank and one bank is sending you strings for a number or numbers for a string and you change it, like we change how it works.
That is that's problematic.
But luckily the banks don't pay us anything in sponsorship.
So I don't mind breaking it for banks as a rule.
These are the bootstrapped struggling small businesses.
They're not ready.
These banks to have enough money to, you know, support open source.
We're just trying to persuade them to use LogFire right now.
So I'll stop being rude about banks.
Yes.
No, of course.
It's well, it's a really tricky balance, right?
To get those folks to open their checkbooks and pay for it.
Right. They they don't have charity usually in their bylines or whatever they're supposed to be doing, right?
Like they but with something like LogFire, they can say, well, here's a service that we can use.
And by using it, we might be able to support Pydantic and the other things that are important to us.
So I think that's great.
You know, tell people just real quickly about LogFire out there.
Yeah, absolutely.
So LogFire is an observability platform built, obviously, by the team behind Pydantic.
And if you go to LogFire along the along the top, the I suppose the two things that make it different from some of the stuff that's come before is LogFire is built on open telemetry.
So on open standards that mean that, yeah, the rails of that where the data is being transferred are on an open standard.
And if you decide you didn't want to use the LogFire platform anymore, you can send that data to anything else that supports open telemetry.
But unlike lots of other companies in our space, instead of using open telemetry as an excuse to abandon the SDK space and just say use the horrible open telemetry SDK directly, we have the LogFire package, which tries to make that super nice and easy to use.
And then the other big change we have, I think there'll be an example maybe further on down the page.
Like we maybe maybe there isn't one right there, but we allow you to write SQL directly against your data.
So instead of having to use kind of ClickOps to go around and do surveys.
So if you look here, right, you can go and like write SQL to query your data.
And so there are although we're still early, there are things you can do in LogFire that you can't you cannot and never been able to do in like one of the big incumbents like Datadog because it's just SQL.
And it's obviously much easier to learn for you, much easier for LLMs to write.
So we have the like plausible chance in the future that you could basically chat with LogFire and say, what's wrong with my app?
And the agent can go off and run SQL to investigate things in a way that is much harder if you have your own dialect.
But allowing people to write SQL against data of the scale is a monumentally difficult challenge.
And one of the things we struggle with a lot, but we think it's useful enough for developers that we like put the time in.
So you can write any SQL you want, except for please only do the ones that have indexes.
So our new database is data fusion.
So there are no indexes.
But yeah, if you do, there are queries you can do now that just like eat memory and we have to find ways around them.
And that's actually the hardest bit is like whether intentionally or not, there are people, you know, there are SQL you can write, which is enormously like heavy to go and compute.
And so we have to be able to find ways to like process that without taking down other customers, I suppose, is the point.
You don't want to do a denial of service on yourself.
Right. I mean, the definition of DOS is that like the effort required to DOS is significantly lower than the effort required to process it.
Right. And writing SQL is the ultimate example of that.
Yeah, it absolutely is.
OK, well, that's a really interesting idea to have SQL there rather than like, let's just click around our UI until we get an answer and those sorts of things.
Or you've got some dashboards on that page.
But the idea is like, sure, we'll go and give you a nice dashboard for HTTP traffic and like response codes.
I think it's the next one down.
But if you want to go and edit that, like the point is that you can basically next one after that.
It's the point is you can go and customize that however you like by just editing the SQL if you so wish.
So it's like it's trying to same with alert.
So we have, I guess you would say century style alerting.
But again, that's all configurable because in the end, it's just SQL that you can go and edit.
Congratulations. I know many people were curious about when they heard that, hey, Pydantic starting a company, they have funding.
It's like, what are they going to exactly going to do with that?
And maybe a lot of people were worried just, hey, it now costs money to just use the library.
Right.
I don't think anyone ever thought it would have been a good idea to literally have like Pydantic Pro where you had to have some API key to install Pydantic.
But it could definitely have been what most open source companies do is they have like open source project as a service.
And then they start, if not taking out features, then adding new features to the paid version.
And I was super keen that Pydantic continued to be successful as an open source project.
And so we did the slightly weird thing about building something which is in a different space from what we're known for.
And that definitely has its challenges.
But I think it's overall, I think it's the right decision.
Awesome.
Well, congratulations.
Beautiful looking project service here and so on.
And I think maybe one of the big pieces of news here, and that's not the one, although that is awesome.
That's where we're going.
Is this jitter?
What is this jitter?
David, are you familiar with this jitter?
I've heard of it.
Okay.
Yeah.
Because parsing JSON is super important.
Tell us about this.
So parsing JSON is a big thing.
And when I first built Pydantic version 2 with the Rust core, we're using Serdi, which is the kind of default parsing library in Rust.
The problem is that Serdi wants to parse the entire JSON object before it returns you some representation of that.
And jitter came out of this idea that we could do parsing as we do validation.
So if you have, imagine you have a list of integers, you, instead of what you would traditionally do is you would parse the JSON,
you would allocate some vector of all of the values in that list, each of which would have to be itself a like enum of here are the different types you might get in JSON,
because you don't know what it's going to be up front.
And then once you finish doing that, you can then go and do validation on that.
And jitter, the idea is it's an iterative JSON parser, hence the name.
You can effectively get, if you think of it in Python parlance, as like an iterable that gives you back each individual element of the JSON as you go along.
The truth is right now inside Pydantic core, we're still using this JSON value, which is jitter's variant of doing the whole parsing first.
There are a few optimizations that we get to get away with.
So there are some really neat things in JSON.
Like if your strings, for example, John Doe, you're showing on the screen there, does not include any escape sequences like backslash N or Unicode sequences,
then you can parse a pointer to that range of the underlying JSON object as a string instead of having to allocate that string.
So we do some like clever optimizations like that.
But the plan in a future version of Pydantic, either as a opt-in feature in v2 or as v3, is to be able to do the iterative parsing.
What's crazy about jitter is, well, one, once we started work on this, we've actually got to the point where jitter is full on faster than CERDI in any use case,
even if you're not doing the iterative thing.
But also this iterative JSON parsing thing is exactly what you want when you want to allow people to query JSON in a way like JSONB in Postgres.
And so we went and used jitter to implement JSON querying inside DataFusion when we moved our database to DataFusion.
And it was just like very luckily, yeah, it happens to be exactly the right concept you need for like for querying JSON,
where you want to iterate over looking for the like string foo and then stop as soon as you find it.
So the code samples you got on the repo here, got a lot of semicolons in them.
Is there a, is it interoperable with Python as well?
Is it just a Rust level thing?
If you go up actually, it's a, it's a, this is a monorepo.
So if you go up into source, into crates, which is, so this is, you have a bunch of different crates in here.
But if you look at jitter Python, and I think if you go down, you'll see an example of calling jitter directly from Python.
Awesome.
The reason we released this as a Python package was a large AI company who I don't know if they wanted me to name them,
basically were using Pydantic V1 so heavily, but they needed some of the functionality of jitter.
And so they basically begged us to release this as a separate package so that they could,
they could use jitter themselves before they upgraded to Pydantic V2.
In fact, the OpenAI SDK uses jitter.
So I think that is public information who it might be.
This portion of Talk Python to Me is brought to you by Posit, the makers of Shiny, formerly RStudio, and especially Shiny for Python.
Let me ask you a question.
Are you building awesome things?
Of course you are.
You're a developer or a data scientist.
That's what we do.
And you should check out Posit Connect.
Posit Connect is a way for you to publish, share, and deploy all the data products that you're building using Python.
People ask me the same question all the time.
Michael, I have some cool data science project or notebook that I built.
How do I share it with my users, stakeholders, teammates?
Do I need to learn FastAPI or Flask or maybe Vue or React.js?
Hold on now.
Those are cool technologies, and I'm sure you'd benefit from them, but maybe stay focused on the data project?
Let Posit Connect handle that side of things.
With Posit Connect, you can rapidly and securely deploy the things you build in Python.
Streamlit, Dash, Shiny, Bokeh, FastAPI, Flask, Quarto, ports, dashboards, and APIs.
Posit Connect supports all of them.
And Posit Connect comes with all the bells and whistles to satisfy IT and other enterprise requirements.
Make deployment the easiest step in your workflow with Posit Connect.
For a limited time, you can try Posit Connect for free for three months by going to talkpython.fm/posit.
That's talkpython.fm/P-O-S-I-T.
The link is in your podcast player show notes.
Thank you to the team at Posit for supporting Talk Python.
The other interesting thing that came out, which, again, I'm ashamed to say I had no idea would be useful, this idea of iteratively parsing JSON until you stop effectively turns out to be incredibly helpful in LLMs, where, as you see on screen here, you can basically parse an incomplete JSON string.
And obviously, because LLMs stream you the response, you can use this to effectively do validation as you receive structured responses.
Work on a JSON stream instead of a JSON response.
I've been surprised by how, like, the legs that this has had, which I wasn't expecting when we first started it.
But yeah, the nice bit is it's all the input, the actual JSON parsing is Rust, but then we have the logic to, yeah, basically access that from Python, both in this package and in pydantic-core.
Well, you sent me over to this cargo section, or the crate section, rather.
And looking here, I see the cargo.toml, pyproject.toml, some source.
I think this might be, in the source, we've got some Rust.
I think that this might be, David, a good way to start leading into working with Rust.
And, you know, this is kind of the destination of your whole presentation you gave at PyCon Italy, right?
We sort of alluded to it already.
But Python and Rust can interoperate, which is an amazing fact.
I think it's so easy to think, oh, well, within a particular application, you know, you're stuck with the programming language that you've picked.
But actually, Python was really designed originally, from what I understand, to be a kind of glue language between different C programs and things like that.
So actually, what you can do is you can use that design to use Rust to compile what are called extension modules, which are used in lots of other bits of Python, say, NumPy or something like that.
Or even maybe less obvious ones like SQLAlchemy and other places.
There's optional speedup C extension type things, right?
Yeah, absolutely.
And in the standard library, of course.
But you're not writing C or you're not writing, I think NumPy might even have Fortran.
You're writing Rust.
You're ending up with something that's the same as you might have done with a different lower level language.
And that allows you to use Python for all of the nice stuff like domain modeling and that thing that we're all familiar with.
And the reason we like Python is we can be very productive with it.
But just occasionally, you hit something with Python where it's basically slow.
There's a high level language there and those abstractions are coming at a cost.
You know, we talk about the GIL as well.
You know, the GIL gets in the way of using all of the CPUs on our machine.
But that's not the case for extension modules.
They can leave the GIL behind if you want.
So you can actually, by writing these extension modules, get more out of Python than you might have thought.
And Rust is a pretty new language that's learned a lot of lessons from older languages.
And it's, I think it's really lovely.
You feel like you're in the presence of greatness.
I want to ask both of you this question before we get too far down this path.
Python often is referred to as CPython because the runtime, the core is all built in C, not Python.
Right.
And the original interop story is around C extensions and CFFI and those kinds of things.
So why not just pick C for Pydantic or for your linter, David?
You know, everyone else is doing it.
They had been doing it.
Maybe sometimes it would be the right answer.
But Rust is offering something very different.
It's offering like a really nice developer ecosystem.
All the tooling is brilliant.
And also, I think security is a big thing.
So many security problems come from like mismanagement of pointers.
And Rust is designed so that the compiler won't let you make a certain kind of mistake that happens all over the place in C and C++.
I don't know if you can say more about that.
I'm putting it more simply than that.
I'm not clever enough to write C.
That's a void pointer pointer.
What are we doing here?
Good way of looking at it is that you can have the unsafe mode in Rust where you effectively lose those constraints.
And sometimes there are the occasional place where you need to use it.
When we do that inside Pydantic core, myself or David Hewitt, who's a much better Rust developer than I am, we agonize for genuinely minutes at a time over how exact, whether or not it is really safe to write that one line of code as unsafe.
And whether there was any possible case in which it could lead to problems.
If you write an app in C, every single line is unsafe.
And if you write a...
You're agonizing even over just a string, like a sprint F equivalent.
And it doesn't have to be 8.5 million lines of code for it to be incredibly hard to go through every single line of that.
So I forget there's like 40,000 lines of code in Pydantic core.
It is inconceivable that we could have written that in C and have anywhere near the confidence we have with it written in Rust that it was memory safe.
We have had one memory safety issue reported in Pydantic core.
And it was a side effect of something deep in PyO3, which in turn was a result of like greenlit doing something crazy that technically the guild didn't think you could do.
I mean, that's the level of complexity.
Another example is Jitter, which I showed you.
We released Jitter a year ago now.
It is the most downloaded JSON parser in Python other than the one in the standard library.
No one has ever reported a bug in it in terms of it parsing stuff incorrectly, except for impartial mode, I think.
Now, I said that someone will probably go and find one.
But like it has been...
That just wouldn't happen in Python.
You could not write...
That reliable...
One of the ways it achieves this is by like really stopping you doing all the stuff that you would expect to be able to do.
Whenever I sort of tell someone about Rust, I say, you know, if you have a string in Rust and you assign it to a variable, let's say X, and then you like store that X in Y.
So you do Y equals X.
Then you can't use X anymore because it's gone.
That seems very strange.
You know, and it seems strange maybe as Python developers because we don't actually hardly ever, maybe never need to think about memory safety.
You know, we've got that luxury.
But once you go down lower level, you are starting to.
And what's going on there with the kind of you can't have X and Y both pointing to that string.
I mean, you can if you copy that memory so that there's then separate bits of memory in X and Y.
But it's kind of making you think in detail about where is the memory for this program?
Ownership, right?
The ownership of who owns this.
I can give you another good practical example.
So I was saying earlier in the jitter case, we have this point where some strings, many strings, you can, instead of having to copy the whole of the string out of the chunk of data within the original input of JSON,
you can literally refer to that slice of data as a pointer.
Literally say this is this is the string itself.
You don't have to copy.
But that means the type you get back for when you parse a string in jitter is what is called a cow in Rust.
And it is either an owned string, e.g. when you've copied it, or it's a reference to the underlying chunk of the array.
And so that gets quite difficult when you're actually writing code with that because you can't go and access that string after you've validated, after you've parsed the next token, because in theory that could have now changed.
If you were writing that in C, you'd have to basically manually keep track of am I accessing that cow in the right place?
Whereas in Rust, well, you wouldn't have a cow in C, but you could presumably have some similar construct.
Rust basically takes care of that.
As soon as you go and do another piece of validation, it immediately says, no, no, you can't even access that previous cow because the lifetime of it has now been used when you've called into the parser to get the next token.
And so it's a very neat, it basically stops you from having to think about that.
And that means you can actually do not more unsafe, but more complex things, knowing that the borrow checker is going to save you in the end.
You can try and do something.
And if it doesn't work, the borrow checker will say it's not possible.
And if the borrow checker says it is possible, you're safe to access that reference to a string that you passed like 50 lines of code higher up.
It sounds like a paradigm shift, but also super valuable.
So I know primarily Python people are listening, so this word might not make any sense.
But is this a compile time check or is this a runtime check?
We don't really think about compiling much in Python.
We don't think about it, but you can think of import time when you do different stuff or you think about static typing when you go and run Pyright over your code.
And that is very similar in some ways to compile time.
It's also one of the big disadvantages of Rust.
I mean, people think Rust is faster than Python.
In many cases, if you write your script, it's a lot faster to run it in Python than it is in Rust.
Because it'll take two seconds to run in Rust or in Python or a few hundred milliseconds to run in Python and 10 seconds to compile in Rust.
So it is not universally the case that the development cycle is faster in Rust.
It depends how many times you run the code, right?
If you're going to run it once or twice and you've got to create it to run it, well, you know, maybe it took you 10 minutes versus 20 minutes.
And it speaks to another of the great powers of the Python ecosystem and of PyPI that we, the maintainers of Python packages built in Rust, can take care of compiling that Rust once, distribute all of the binaries.
And then when people go and install it, we don't have to compile it.
If you were using most other, if you were using cargo, you would have to take care.
I mean, putting the controversy over Surdi to one side for a minute, you would have to take care of compiling that code yourself every time you wanted to go and go and use it.
Whereas ActionPyPI does an amazing job of distributing compiled Rust for virtually every ecosystem, every architecture, and it just working when you install it.
Absolutely.
Something I just wanted to say about the memory management, like it is a compile time, but actually it also, you can also use reference counting and things like that.
It's just opt-in.
So there are lots of tools for allowing you to do runtime checking of things.
And sometimes that's what you have to do.
You're like, oh, I can't really do this.
It's just using stuff that's worked out before the program even runs.
I do need to do it at runtime, but it's fine.
I can do that for this particular data type.
And then there's an API to do it and it will come with other trade-offs.
It's giving you that control, but it's giving you control over things which, if you've only ever programmed in Python, which to be honest, pretty much, I mean, I used to do PHP a bit.
My mind is soaked up in Python.
And coming to Rust, it's like, oh, this is a completely different way of thinking.
It's like the red pill in the matrix.
You're like, what is this?
Yeah.
It's nice, though.
It's hard.
I'm not going to lie.
I feel very confused a lot of the time.
I suppose this is a time for the obligatory.
The White House recommends the future software should be written in memory stuff languages such as Rust and Python.
So, right, that's always nice to have around to think about.
Whether or not that's an argument in favor of Rust or not, depending on your take on a particular White House, is another question altogether.
But it's definitely a data point.
I will draw your attention to the date of this.
I'm interested to see what the new leadership recommends next year.
We'll see if they're like, assembly all the way.
We've decided.
No, okay.
Let's carry on with your story, David.
So, you wrote this thing called an import linter, which is kind of unique.
There's lots of linters in Python, right?
There's PyFlake.
There's Ruff.
Another Rust success story, I suppose.
But this one checks more architecturally, right?
And you want to use this on your small little repo that you all are running over there.
Tell us about that story.
I wrote it for Kraken.
But actually, I had the idea for it in a previous company.
You know, it was still a complex monolith, but nowhere near as big.
And to be honest, I find even in quite small projects with just a handful of files, it's
still useful to think about the architecture of those files.
And I think, for me, architecture is ultimately about dependencies.
And something that Python lets you do, which I don't think is very good, is that you can
end up with lots of cross dependencies between different modules and packages within a particular
Python project.
So, for example, you know, I mean, we know that sometimes you get circular import errors,
but this is something a bit wider than that.
Like, possibly, if you've got two sibling sub packages, you might have one module which imports
from something in one, in the other sibling package.
And then there'd be, like, other modules that import the other way.
And it won't stop Python from loading up.
But there's a sort of, there's a circular dependency conceptually between those two packages.
They're tightly coupled.
And, you know, this is just one example of the kind of architectural rule you might want
to impose on a project to say, well, actually, I want there to be a dependency only one way.
I want one of those packages to be kind of lower level and one of them to be higher level.
Or maybe you'd have, like, five packages that are all siblings.
And, you know, you call this layer, really, but where you maybe, you know, say, yeah, this
can import all the ones below it, but nothing can import the ones above it.
And you sort of think of them in a sort of stack.
Here are the details.
Here's the public interface.
And then things use the public interface.
They shouldn't reach down below and vice versa.
Yeah.
That's certainly one thing you can do with it.
It's mostly focused on controlling dependencies between packages as a whole.
You can also do it for individual modules.
But so what it does is it uses imports as the proxy for a dependency.
So it will statically analyze all of the imports in your module.
And then it will build a directed graph.
So you can then think of your module, think of your package as, like, every module is a node and a graph with arrows pointing between all of them.
And the arrows are the imports.
Sometimes you call them edges or whatever.
What import linter allows you to do is to write contracts that are just in YAML or TOML, which just say, all right, these are the rules I want you to check when you run the linter.
And it will then you can put it in CI and it will fail if someone adds an import the wrong way.
And it's fascinating to see, like, if you have an idea for an architecture and then you put an import linter contract on and then discover all the places where you're breaking it, they're almost always quite interesting, interesting things.
And you're like, yeah, actually, this is a bit of a problem.
Like, it needs a bit of thought to kind of untangle it.
It is genuinely, you know, some conceptual tangling that would simplify things if we didn't do it.
Yeah, it's kind of a higher order code smell.
People are familiar with this idea from refactoring a code smell.
Like, it's not wrong in that it won't run, but you kind of turn your nose up at it.
You go, oh, right.
And this is that, but not at a, oh, it takes too many parameters.
But, like, why is it all tied together like this?
Can we think about it better, right?
Yeah, exactly.
This portion of Talk Python to Me is brought to you by the Data Citizens Dialogues podcast.
If you're ready for a deeper dive into the latest hot topics in data, you need to listen to the Data Citizens Dialogues podcast, brought to you by Colibra, the leader in data intelligence.
In every episode of Data Citizens Dialogues, industry leaders unpack data's impact on the world.
From big picture questions like AI governance and data sharing to more nuanced questions like, how do we balance offense and defense in data management?
You'll hear firsthand insights about the data conversations affecting all kinds of industries.
With guests sharing unique stories from some of the world's largest companies, such as Adobe, Fidelity, Deloitte, Hewlett-Packard, McDonald's, and even the United States Coast Guard, you'll get an amazing look inside how these organizations handle their data.
My favorite episode is Solving Data Discovery with a Self-Service Approach.
It's an interesting look inside creating a single source of truth for data inside an online university.
Check them out and try an episode for yourself at talkpython.fm/citizens.
That's talkpython.fm/citizens.
The link is in your podcast player's show notes.
Thank you to Data Citizens Dialogues podcast for supporting the show.
You know, and you can do things like, say, all of these modules are independent, say, so they don't import anything from each other.
I mean, this is like just my whole mentality for architecting Python packages, and I find it works really well.
And I think the fact that we've got these rules in place is probably a big reason why we're still able to have a thousand PRs a week all happening in different time zones without that many faults.
Because we've got, we've architected it.
So say we know that there aren't any imports between, say, two energy companies or between two territories.
You've got to install your competitors version as well to use yours.
It's allowing us to think about things as being more independent.
And so like someone can make a change in, say, Spain, and it's not going to break something in Australia because you're pretty confident because they aren't importing anything from there.
Does that mean, in theory, if you've got this, that you could use this within, for example, CI to only run unit tests, systems that have changed or direct descendants of that package?
Because I presume we do do that because we've got a lot of money on CI.
And so we've had to invest a lot of money to like figure out how do we narrow down the tests.
So, yeah.
Yeah, that's awesome.
That's a great observation.
This is all based on an underlying library called Grimp, which is, I kind of broke the libraries up.
It's a little bit like what you did, Samuel, with Pydantic and Pydantic Core.
So import linter is pure Python, but it has a dependency, which is Grimp, which is, I mean, it's got Python and Rust in at the moment.
But that is just a Python API for building a graph.
And then you can explore the graph.
And you can do things like it's really quite a useful tool if you're just interested to know something about code base.
You know, you can just type in, build me a graph of this package, say, and then you could say, tell me all of the downstream modules of this module.
And then it will explore the whole graph and like give you a set of modules or tell me all the upstream modules of this.
So you can tell what dependencies are or you can like do, you know, what's the shortest path between this module and this module.
Makes a lot of sense.
Yeah. Can you get it to do dependencies, the things you pip install?
Oh, I see what you mean.
Well, you can build the graph with multiple packages.
I don't even know if that's really, is that possible?
Can you have two installable PyPI packages that depend on each other?
I'm more wondering if I can use this to visualize, well, what parts of my app?
So I know that I have this dependency and I have to have it to run.
But what parts of my app are using that library?
So if I wanted to change it or whatever, you know what I mean?
You can build a tree from there's a package for it or uv will give you a tree of dependencies.
But that is based on what packages require what other packages to be installed.
It doesn't actually say what's being imported from where.
So if you have an issue with URL in three, what is the actual like graph of where URL in three is being imported rather than what packages depend on it?
There's a flag when you build the graph, which is include external packages.
And if you pass it, then it will include them.
But it doesn't include them.
It just like includes the root name.
If you wanted the whole thing, then you'd say, build me the graph of, I mean, that would work as well.
But you could say, build me the graph of Django and Kraken, whatever.
Or you could say, build me the graph of Kraken with external packages.
Then you'd just see like Django would just be a node in the graph, but it would just be one node.
Yeah, that'd be great.
Does that make sense?
The story is this was working, but slow.
As an example, one of our contracts, and we had lots of contracts, one contract was taking six minutes to check.
The reason for this is because actually it is quite algorithmically expensive to check whether or not certain rules are being followed.
Because it's not like just doing a kind of search.
It's actually looking for indirect imports.
So it kind of needs to explore the whole graph to see.
It's like a pathfinding algorithm to sort of see, oh, well, do you end up here via this other thing that you didn't ask about?
And yeah, so certain things were taking a really long time.
And that was costing us money because we run this, you know, hundreds, if not thousands of times a day.
And it all adds up.
And it also just slows down, you know, your pull request.
You want to get to green quicker.
So I had heard that Rust was interoperable with Python.
I didn't really know anything about it.
And I found this library called Py03, which is a Rust library.
And it makes it, I'm not going to say easy, but it makes it.
Easier.
It makes it surprisingly easy to write Rust extension modules.
It gives you sort of all the tooling in place.
And I would say, you know, you're writing a lot less code than you would be if you're writing a C extension module.
Because it's sort of a nice, it's a really nice API for creating these interoperable compiled modules.
So like I started by just finding the function that was taking all the time and had millions and millions and millions of calls.
I was like, why don't I just write that in Rust?
And I kind of just sort of almost copied and pasted it over and like cobbled together some pretty rubbish Rust that did basically exactly the same algorithm.
And it was a thousand times slower.
Well done.
There's a good reason for that, which is I just zoomed in too close.
So because that function was called a lot of times, there is actually a cost in crossing the boundary.
So I just had to step one level up and like wrap all of those calls for that function in Rust instead.
So there's only one call from Python to Rust.
And then suddenly, majorly quicker.
I mean, I think that that particular problematic contract went from about six minutes to one minute.
That is, to be honest, and that was brilliant.
But that is not.
That's just scratching the surface of how much quicker it could get.
But still, a six times speed up for relatively little work and basically just writing the same algorithm, but with some curly brackets and a bit of head scratching.
Like I was able to deliver something without having ever done anything in Rust.
And it really made a difference, you know, and saved us lots of money.
Yeah.
So that was a risk.
And now you're a Rust developer as well.
Now I'm a Rust developer and I read through it.
And I'm, you know, I want to move more of Grimp into Rust.
Another example, even less work, was we had a problem with translations.
So there's a kind of standard for doing translations called Fluent.
And some of the listeners may have come across it.
And there are libraries for it in all sorts of different languages, including Python and Rust.
And we realized that it was responsible for almost all of the bootstrap time of our application in production was loading and scanning these translation files.
You know, it was pretty problematic.
It was like really spiky as well.
You could see sometimes it would spike to like nine minutes to turn on the application.
And we knew it was all in this translations thing.
And someone pointed out, look, there's a Rust library for this.
So all we need to do is just link them up.
And so they, it wasn't, it wasn't actually me, but they just wrote this Py03 crate that a crate is like a Rust library.
You know, probably only about 10, 15 lines of code just glued the two things together.
It was really like not very much work.
And compile that, it just completely sorted the problem out.
We went from eight minutes to 30 seconds.
And, you know, of that, like the, the, you know, that 30 seconds is other stuff.
You know, I think, I think it turned it from being something that took several minutes to being virtually instant for really hardly any work.
And I think that that's a really good example of where, as Python developers, we should be aware this is an option.
If something's slow, you might just, I mean, Rust has so many good libraries.
You might just be able to like just glue it up and job done.
One thing I would add, the, the overhead of calling into Py03 has, has dropped a lot.
I don't know how long ago that was you, you were trying, but I think it's, yeah, reduced significantly.
So, I mean, I did this quite a lot of the same work in Pydantic Core to avoid the overhead of calling into and out of it lots of times.
And I think that's less of a thing now than it used to be.
That's good to hear.
One point I want to make is, I think that's really interesting, David, to say that, hey, there's this whole equivalent of PyPI called Cargo, or you can get these libraries just like we can, I mean, sorry, that you install with Cargo, that you can get all these pre-built, pre-tested libraries and maybe just put a wrapper on them and do some amazing stuff.
I just started using this web server at the Python layer called Granian.
And you're like, well, how many people are working on it?
It's got up to 3,000 stars now.
Is that enough to trust?
Yes, but if you really look at it, it's really using just Hyper, right?
And Hyper, Hyper is a library that, excuse me, that has got 14,000 GitHub stars, 400 contributors.
And like, oh, you know, it's kind of this cool application is something of a wrapper around this really popular and well-known thing, right?
I think we'll see more of this kind of stuff.
Giovanni, who worked on this, is also working on, it's R-loop, basically an alternative to UV-loop.
Yes.
I don't know if that's in here as well.
I maintain watch files, which is the file watching library used by uvicorn and some other things.
And that is, again, wrapping a Rust library for getting fast system notifications.
And also R-toml, which is the fastest tomml parser in Python, which is, again, just wrapping the Rust library.
So there are a number of places where you can get enormous performance improvements and indeed like fundamentally more reliable libraries because you're building in Rust.
when you're doing complex, particularly multi-threaded things by relying on Rust.
These libraries seem to be very good quality and well thought through.
That's been my experience.
Somehow it attracts people that are very thorough because you can't really program in Rust unless you're thorough because you can't get an extra pile.
I think it's hard to say Rust does have a bit of a problem with abandoned libraries.
I think that is, I think if we didn't call it out, I know David, you and I were speaking at an event where there was one particular chap who had a bee in his bonnet about that exact issue.
I think he was slightly overblown on it.
But there is definitely an issue with some abandoned libraries in Rust.
But like, I mean, same is true in Python in its own place.
But you're right.
There are also libraries that are abandoned and remaining incredibly good quality.
So Jitter uses a library for parsing floats.
I didn't know this, but the complexity of parsing floats from strings is a entire subject of academic interest.
There are eight different algorithms for doing it with different performance, depending on whether or not it is the structure of the float.
And there was a library that does this very well.
And the library was abandoned, but it worked perfectly.
And eventually the guy replied to my email and went and fixed it.
So, I mean, the quality, yeah, the quality is very high in my experience.
Yeah, I just make the point.
I think, you know, there's over half a million packages in PyPI as well.
And I'm sure there's a non-trivial amount of them that people are no longer maintaining as well.
Sometimes things are done and sometimes they're abandoned.
And it's hard to tell the difference, you know?
Like, no, I haven't updated it in two years because it's done.
But also, also.
It's hard to tell the difference until you're three weeks in and you don't want to back out of using that library and you realize that actually it's...
It's actually not done.
It's not done.
But nobody wants to worry.
All right, David, before we wrap this up, I would really like to have you talk us through sort of the tool chain workflow of...
I've got this idea.
I want to maybe, you know, rewrite it in Rust if you've spoken about it.
What are the building blocks?
What are the moving pieces?
What do people need to know?
Absolutely.
Okay.
So the things you need to know is, first of all, the thing you're going to create is an extension module, which is like a built compiled thing.
And that is going to end up being a Python wheel.
And what you want to do is when you release something on PyPI, you want to have wheels for all the different versions of Python and chip architectures and things.
So that's kind of like some of the complexity.
So that's kind of the end goal.
We want to end up with these built versions of Python that are going to contain some Python code, but also some compiled things for, you know, macOS under Python 312 or whatever like that.
So the way you start this, you need to install Rust on your computer.
And the tool for that is actually called RustUp.
This kind of confused me to start with.
I didn't really understand what RustUp is.
But RustUp is kind of like one level up.
And it's maybe a bit like PyPI if people have used that.
But it sort of does a bit more than that.
Maybe also a bit like uv these days where you can uv install Python based on what you asked for or just create a virtual environment and it'll just grab the tools you need to make that happen.
I'd say that Cargo is closer to uv probably, which is the next thing I'll talk about.
But RustUp is like one step up and it's like it's for managing the versions of Rust that are on your computer and the versions of Cargo that are on your computer, which are Cargo is the package manager for Rust.
What you'll do, your first step is to go to the Rust website and install RustUp.
And then hopefully at the command line, you'll be able to type rustc and then you can compile a Rust file if you want to.
But you don't actually need to do that because that's what you use Cargo for.
So your main kind of thing that you're typing all the time when you're using Rust is Cargo and then some kind of command.
So you've installed RustUp and you've got Cargo.
Then what you'd want to do is create a project and you can create a project using Cargo.
You could do Cargo new and then it'll give you a whole file system structure and you'll be able to run it and run tests and all that.
Very nice.
And that is how you would install the latest version of Py03.
So you would, I probably would just go over onto crates.io, which is the PyPI equivalent,
and look at what the version is of PyPI and then write it in a, I think there is a Cargo command probably for adding it.
But it's also worth saying that Maturin is a big part of the ecosystem.
Jono, David, maybe you could introduce Maturin because I think that's about where that comes in.
Cargo is the thing that Rust people will be using to compile these things.
But you can't just use that on your own because you need the whole pip installing kind of side of things.
So you can use Cargo and just create an extension module, but then you need to like give it the right name and copy it into the right place.
So there's a bit of kind of gluing that needs to happen.
And that is what Maturin is for.
Why do we need two tools?
Well, Cargo is the Rust side of things, but Maturin is actually just a pip installable Python package.
So what you'll do is in your, in the Python project that you want to Rustify, you will do, go into your, activate your virtual environment or whatever, and then do pip install Maturin.
And then you've got this tool for Maturin.
And you could even do Maturin new project or something like that.
I can't remember what the command is.
What I had to do was I had a pre-existing project, so I couldn't just do it like that.
But it might be quite a nice way to learn is to follow the Maturin docs to do a new project.
And what Maturin allows you to do is I think you might edit your Rust code and then you would type Maturin develop.
And then it would compile your Rust code, but it would kind of install it locally so that you can work with it.
And then the final piece of this picture is how that gets onto PyPI.
Maturin also gives you some tools for that.
There's a command called generate CI, I think, which gives you, it just spits out YAML for the, for whatever CI provider you want.
And that's really helpful as well.
I mean, that's a bit about verbally.
I've given a talk of people.
Yeah, well, I think one of the things you both have to deal with here is releasing a package, a wheel for this is not just, well, let's zip up the Python files and put them up there, right?
You're compiling native code and that means you need variations, right?
Yeah, I think Pydantic core releases 60 something different wheels to cover all different possible combinations of Python version and architecture.
I would basically second what David was saying.
Maturin in particular does an amazing job of smoothing out the kind of rough edges between Rust and Python.
Rust has had a good, relatively good story on package management for some time.
Python is just coming, but often when you're trying to do these things, it's the, like, trying to get one to speak to the other and working out what the hell is going wrong and what does that file need to be or that, like, sim link between some DLL and some other place.
Maturin effectively gets rid of that whole challenge for you and just works and lets you work on it like you're, it's as easy as writing Python or Rust interacting with the two.
Yeah, because cargo and Rust C will output Rust conventions, but Python wants C extension conventions, naming like .so versus Dynelib and things like that, right?
And getting the inputs right and yeah.
So what your work, once you've sort of set it all up, your workflow looks like is if you haven't changed any of the Rust, then, you know, maybe like you run tests, you just run pytest or whatever.
That will run tests against the compiled bit, assuming you're writing Python tests against that.
But if you make a change to the Rust, then you do have to have an extra step of building it.
So you type Maturin, develop, and then it would build it.
And then you could run pytest.
Or you might recommend you do, actually, if you're working with Rust and you're not very experienced.
I tend to have a test suite for Python, but also some tests in Rust because you get a much quicker feedback loop because you can type cargo test and that will compile it and run the test really quite quickly.
One other thing I think is worth adding is that, obviously, if you have this binary that contains your Rust code, which is, you know, in the end, it becomes a module that you can import in Python.
But by default, that is obviously opaque to type checkers.
So things like IDEs and static type checking can't look inside there and see what your functions are.
So you want to write a PYI file, which contains your definitions in like stub Python code, effectively.
The problem you then have is you end up with two separate definitions of what your functions have in them.
And so there's a really neat tool, which I didn't know about until quite recently, but we use it in Pydantic Core, which mypy can basically type check that the PYI file stubs match the definitions inside the DLL.
And so you can have like guarantees that where you've written in a PYI file, my function foobar takes A as a string and C as a list of bytes.
That's really cool.
I didn't know about that.
Yeah, that's awesome.
Fixes all together.
Is it a mypy plugin?
If you have a look at Pydantic Core, we run it in CI or in maybe even pre-commit so you can find it there.
I couldn't remember.
I discovered it because like so often with so many of us, I discovered it because it had a bug.
And so it suddenly didn't do quite the right thing for me.
But I mean, in general, it's been perfect until now.
Yeah, it's plumbing until it doesn't work and then it's a flood.
Bug-based discovery.
Definitely a thing we've all practiced.
Bug-driven development.
Yeah, okay.
And if people want to check out these PYI files, there's a project called TypeShed on GitHub that whose job is to basically become these so-called stub files for a ridiculous number of projects that, you know, we're talking about things that are not going to get upgraded.
Maybe they're never going to get typing put on them, but you can go and import one of these or whatever and get.
But also lots of packages now have their own, either they have types in them or they have a PYI file that defines the types either in the Python code or in PYI.
That's really cool.
I didn't realize about the tool there, though, that does the integration.
That's awesome.
I think presumably you can use it to generate your first PYI file.
Then you go in and put your doc strings in, tweak things a bit, and then you run it in test mode.
Oh, nice.
Yeah, because that way use the true version, the Rust version, and just say generate what I need to make this work.
That makes a lot of sense.
Yeah.
Awesome.
All right.
Well, gentlemen, we are just pretty much out of time.
I guess let me ask you one more thing here.
And I don't think I pulled it up.
So there's a new PEP called external wheel hosting.
Have you all heard of this?
I have not seen that particular.
Is that for Wasm?
I know that I've been talking to a fair bit about the challenges of uploading Wasm wheels.
I know it has to do with more than it is five.
Here we go.
Copy link.
It is five, seven, five, nine.
And the idea that if you could, the nomenclature here is ridiculous.
It is in clever.
So the idea is that, you know, each build of Pydantic puts 60 binary artifacts onto PYPI.
And there's limitations on how large your projects can be and how large individual releases can be.
This is especially problematic for machine learning stuff, right?
And so the idea is, can we create a wheel stub?
And what is a wheel stub called?
It's like the wheel without the content.
It's called a rim because wheels go on, you know, and there's all sorts of stuff like that here.
But it contains a hash and then a location where the thing actually lives, right?
So you mentioned OpenAI, for example.
Maybe they have some huge thing they want you to download eventually.
They could host it and you just publish the rim, not the wheel.
Do you know the story of why they're called wheels?
This entertains me a lot.
No, tell me.
So they're called wheels because you have wheels of cheese.
They're called wheels of cheese because the original PYPI was called the cheese shop,
which in turn was called the cheese shop because there is a Monty Python sketch called the cheese shop
where he goes in to buy cheese and none of the cheeses he asked for are available.
And it was called that as a way of taking the piss out of, I forget which other language,
it might've been Ruby or PHP, how their original package registry had no packages in it like the cheese shops get in Monty Python.
And that has now assisted to rims in 2024.
That's incredible.
Yeah.
I did know the cheese shop equivalent there that that was part of it, but I didn't realize that it was the wheels of cheese.
Okay.
Incredible.
There's a lot of Monty Python.
You know, people that you look around everywhere.
The logo is the snake, right?
But the logo, the name is not Python, the snake.
It's Monty Python and that horrible bunny.
If I wrote a book about Python, I'd want to have the knights who say me on the front.
Yes.
I want the bunny, the killer bunny.
It's definitely a certain era of humor.
I don't know.
You know, it definitely dates Python in its way.
And us, perhaps I fear.
Yeah, perhaps, perhaps.
I don't know what this gray hair is about, but we're going to go.
All right, guys, let's close this out.
I'll give each of you a chance to just give us sort of a parting thought on integrating Rust and maybe thoughts and just Rust and Python together.
I'll go, David.
Only really to say I am a better programmer for having learned to write Rust as well as Python.
And I fail to see the value in C anymore for new projects.
I get why I exist for lots of existing ones.
I'm not going to get into the issue of whether stuff should be rewritten in Rust.
But I think if you're starting from scratch and you're trying to write something high performance, the experience of doing it in Rust is completely different.
from trying to do it in C or C++ or C# or any of the other or Fortran or Julia or any of those other languages.
Rust is awesome.
So I'd really encourage people to give it a go.
And when you're giving it a go, you might also have a give Logfire a quick go.
We're releasing our Rust SDK fairly soon.
We have Python already.
So we're matching the like Python Rust TypeScript ecosystem, which I think is the like the stack to build with today.
My parting thought would be like, if you give it a go, don't be surprised if you feel quite inadequate.
It's really quite hard to sort of get your head around what's going on.
And I feel very much still at the beginning of my journey.
But nevertheless, I have actually managed to like deliver some stuff which is valuable, even though, to be honest, I'm not like particularly proud of the code I'm writing.
It's like, you know, I'm a beginner again.
And I just want to say, like, don't let that put you off.
And if you're confused, you know, just keep on going and trying to get your head around it.
It will make you a better programmer.
The book that I found the best for learning Rust is the Rust Programming Language, which is a free book on the Rust website.
Right.
Just click the learn button in the top, right?
Yeah, exactly.
Learn menu item.
Yeah.
Awesome.
Yeah, I'll also just add one quick thought as well to just follow up to what you said, David, is whenever you're switching programming languages and you've been programming for a while, you just, you feel inadequate and you feel like I was so good.
I had it figured out.
I could just sit down and do stuff.
And now even how do I just read a file or just run a program?
I'm lost all over again.
But every time you do that, your prior experience still carries over way more than it initially feels like it does.
And you're not throwing everything away and starting over.
You're learning a new tool chain and then on you go.
Absolutely.
And particularly when it's so easy to call Python from Rust and vice versa.
And so you can build applications that are like hybrid of the two.
Absolutely.
Yeah.
Well, congratulations both on some awesome projects.
And yeah, thanks.
Thanks for being here as well.
Bye, y'all.
Thanks for having us.
Thank you so much.
This has been another episode of Talk Python to Me.
Thank you to our sponsors.
Be sure to check out what they're offering.
It really helps support the show.
This episode is sponsored by Posit Connect from the makers of Shiny.
Publish, share, and deploy all of your data projects that you're creating using Python.
Streamlit, Dash, Shiny, Bokeh, FastAPI, Flask, Quarto, Reports, Dashboards, and APIs.
Posit Connect supports all of them.
Try Posit Connect for free by going to talkpython.fm/posit.
P-O-S-I-T.
This episode is brought to you by the Data Citizens Dialogues podcast from Colibra.
If you're ready for a deeper dive into the latest hot topics and data, listen to an episode at talkpython.fm/citizens.
Want to level up your Python?
We have one of the largest catalogs of Python video courses over at Talk Python.
Our content ranges from true beginners to deeply advanced topics like memory and async.
And best of all, there's not a subscription in sight.
Check it out for yourself at training.talkpython.fm.
Be sure to subscribe to the show.
Open your favorite podcast app and search for Python.
We should be right at the top.
You can also find the iTunes feed at /itunes, the Google Play feed at /play, and the direct RSS feed at /rss on talkpython.fm.
We're live streaming most of our recordings these days.
If you want to be part of the show and have your comments featured on the air, be sure to subscribe to our YouTube channel at talkpython.fm/youtube.
This is your host, Michael Kennedy.
Thanks so much for listening.
I really appreciate it.
Now get out there and write some Python code.
I'll see you next time.