 Thank you. Thank you. Hello everyone. So well Martin already sold me I guess. So bonjour. My name is Alexi. You can guess from my accent that I'm French. I'm sitting at number Lee. I've been around open source for quite a while now. I'm a maintainer of MK docs in case some of you know about it and I know I've heard that Tom Christie is a participant of Europe Python. And this is yes my 10th Europe Python participation. So I'm also a tech speaker and writer. You can find everything on my website and I go around the internet as a ultra bug. And today I'm the guy standing between you and food and I'm here to talk about the language that this conference is not about. So I guess most of you are wondering what they are doing here. So I'm gonna try to make that clear right now. People taking interest in Rust today usually already know one programming language. Thank you Captain Obvious. We all come with our programming experience and bias. I have for myself not a student computer science. I'm a self-taught programmer and a mediocre one. I don't have a deep understanding of the lower representation of the types I work with every day using Python in my code. And I even hated programming before I discovered Python 20 years ago. Makes me old. So this talk is not a Rust versus Python talk. If you are in for the fight, sorry. This is not a comparison as well between Python and Rust. I won't go into lower details between the two. I'm not fit for it. And I'm not trying to convince anyone to use Rust instead of Python. So if you want to find a good excuse to switch and don't do Python anymore, you're not at the wrong place either. Instead, I want to share my experience and rational in adopting Rust as another tool and other programming language that I can use. I want to share my experience in getting Rust in production at Numberly. And I want to maybe emphasis on some thoughts and perspectives that I got along the way. Now, I need to explain some business context and how Python is used at Numberly and how I got to Rust in the first place. So here we go. At Numberly, we process a lot of data. We are a data marketing company helping clients connect with their own customers using all digital channels available. And for this, we need to crunch a lot of data. A typical pipeline looks like this. We have three, let's say, Python applications that we will call Python data processors that will get some data out, some Kafka topics. So some raw data that is coming is read by three different Python applications that will then have to enrich this data, meaning that I'm taking some raw data. I'm going to issue some query to a remote database, no SQL one that is CLA DB in our case. And then I get some new data in. I add it to the raw data and I put the result back into a dedicated topic in Kafka. Those dedicated topics will be in turn consumed by downstream applications. So in this case, you can see that I have three front end Python applications doing this enrichment. And then the enriched data will be fed to partners, business logic, and even clients. Those downstream applications can be written in Python, Go, Java, whatever. It doesn't matter really. And all of this is scheduled and deployed using Kubernetes at Numberly. Now, this pipeline reliability is really latency sensitive and we need strong resilience because if one of those three main processor Python application starts throttling or slow or fails, our business and partners are at risk. In the worst case scenario, we lose money or our partners do. And we get angry clients and nobody wants angry clients, right? So we need to trust those Python applications with our lives. At some point, we had and we were faced with the challenge to actually rewrite the business logic around those three Python applications so that we could make them into one. So this redesign thinking had to happen and it got me thinking quite hard, maybe a little too bold, because I wanted to approach this challenge differently. The go-to decision would just have to merge, have been to merge those three Python applications into one bigger Python application, right? But I was following Rust and Rust maturation for a while and at that time, it started to, I started to have the feeling that the language as well as its ecosystem were mature enough for a spin. Plus, their marketing motto speaks to the marketer inside of me and pouring everyone to build reliable and efficient software. Everyone, well, I felt included so maybe it could be me. And then, as you heard before, I needed those reliable and efficient software patterns for my use case. So after a not-so-long thinking, I went to my colleagues I was working on with on those three applications and Python application and said something like, hey, why not rewrite those three Python processor apps into one Rust application? They looked at me like this. I don't blame them even if for a while I almost lost my CTO batch because of this. But I don't blame them because those three Python applications, they were up and running for almost five years or something. They were battle-trusted. We actually did trust them with our own lives. So we knew them by heart. They were very, very, very good. So instead of having a blunt sentence like this, I figured out that I needed to explain my motivation and the rational that got me there and got me to engage with Rust instead of Python for this use case. So Python is usually the first language you learn even at school today. And this is amazing to me at least. And it's probably due to its popularity and accessibility and wide range of use cases. So you can use Python for almost everything. And I always have been amazed by these batteries included, philosophy, and that you feel very, very fast when you start using Python. And it advertises itself as a general-purpose programming language. Python does fit indeed a lot of use cases at Numberly in wide range of use cases. It did fit my processor pipeline use case very well and for a long time. And coped with this growth without a glitch. Over the five years it almost tripled in scale without a glitch. So I want to emphasize something very important right now. Python was fast enough to handle those pipelines at scale. That means that I did not choose Rust to be faster. And I agree with Brett Cannon. I don't know if you know this blog post of his. And a lot of us are talking about him today. I see. And that's great. And I agree with him when he says that selecting a programming language can be a form of premature optimization. And so I want to add this. I did not go for Rust to be faster. And I know a lot of people take interest in Rust because it's supposed to be omigot fast out of the box. But coming from Python, I think it could be a mistake. And at least I don't find it to be the right angle to approach Rust. Because Rust advertises itself as being efficient, not fast, which is not the same to me. Because the fast meanings vary depending on your objective, context and experience. Let's look at it. Is it fast to develop in Rust? No way it can be faster to develop in Rust than in Python. I've been doing Python for 15 years. So I want to jump into the Rust ship to develop faster. Is it fast to prototype using Python? Of course not. The code must be complete to run and correct as well to run. So you just can't Python it or just run a simple command and see what happens. That does not exist. Is it fast to process data? Well, Karim just before me said that yes. So of course, maybe prove it. That's a fair assumption. But at least I know that Python was fast enough in my case. Is it fast to cover all failure cases? Yeah. This is built in the language syntax. That's a really interesting feature. Is it fast to maintain? Well, in our case, nobody else at Numberly were doing Rust anyway. So I would be the only one maintaining this. And I'm not that fast. So I'm not sure. Type checking helps and typing helps. But in our case, no. So in summary, going for Rust will mean that I will be slower. So why would I want to lose time in one word? Innovation. Because innovation cannot exist if you don't accept to lose time. The question is to know when and on what project. Rust makes promises and advertises something else than efficiency, reliability. So I thought that what makes me slow can make me stronger. Why would it make me stronger? Because Rust has some low level paradigms, such as authorship, borrowing, and lifetimes. At least if it compiles, it's supposed to be safe. It has strong type safety. It makes the code predictable, maybe more readable in some cases, and maybe more maintainable in the long run. The compiler and the Rust compiler is an actual friend of yours when interacting with Rust and trying to figure out what you are doing wrong. It's still better than random Python exceptions. The dependency management in the cargo terminal is just a joy. The first time I encountered it, I couldn't believe my eyes versus the requirements in English. I know the PyProject terminal initiative goes the right way and I'm happy about it for Python. It has some exhaustive pattern matching. This one is a lifesaver because it brings you confidence that you will not be forgetting a special case. And it has some error management primitives with the resultinums, which allows you writing the syntax of your code to properly handle failure. And that's pretty interesting as well. At least that's some nice promises that you can try to take for a spin. So I chose Rust because it provided me with the paradigms and the abstraction level that I needed to understand and better explain the reliability and performance of my application. In other words, I felt like this project was the right opportunity for me to innovate on the reliability and efficiency marks. Now, I expected Rust to be difficult and it really was, but not for the reasons that I anticipated. Let's look at how I learned Rust the hard way. I told you production is not a hello world. I am aiming for a real world application and to replace some real world and business-critical Python applications. So adopting a new language straight for production is quite hard. Even more with the high stakes that we are talking about, which you have also to monitor and prove with observability because you can't just take Rust promises for granted. You have to measure them and prove them that they are right. So here is the stack that my new Rust program had to interact with. Obviously, I needed to be able to connect to Confluen Kafka, get some data out, deserialize it somehow. Then I will need to be able to issue queries to remote no SQL database to get new data out of the raw data that I got to get enriched data. When I got this enriched data, I need to put it back in Kafka. I need to be able to monitor for some failures in my code and report them. So for this, we use century at numberly. And then I need to prove everything that I have been saying so far with proper numbers and graphs. So I need to have Prometheus and then Grafana plug to it. And all of this runs on Kubernetes. So let's start with the first world that I hit in my Rust journey. And it started pretty fast because it was due to reading data from Kafka. At numberly, we use Confluen Kafka with a schema registry feature so that we can enforce strong schemas on the data that we put on the Kafka topics that we rely on. And the Confluen Kafka schema registry had some magic bite at the start of the payload when you put it in Kafka. And this breaks the vanilla apache avro deserialization. So I had to issue a manual approach. I'm showing the code here. I'm not expecting you to read the entirety of it. But this is how it's done for later reference if you want. And that's the most performance way. Then I found out that Yerar Klisch had a crate, which is the Rust word for library that is called schema registry converter. We worked together on making sure that using his crate didn't hit performance on reading and the Kafka payload. So now it's available. And if you ever have to do it, use his crate. It's great. So thank you, Yerar. So I was able to read data from Kafka. Now I need to deserialize it using avro. And then again, it was broken. I couldn't deserialize the data that I was now able to read. And at this time, there was no point in maintaining in the apache avro organization, at least for Rust. You are new in this programming language, right? So I blame myself for days before I even thought about opening the apache avro Rust source code and have a look at it. But I finally did. And to my own surprise, it was actually broken. So for our complex schemas, the official apache avro Rust library was not working and it was broken. I opened it some Jira for this. And I actually contributed the fixes to the library, which got merged a few months later by the newly appointed maintainer, Martin. So thank you, Martin. I must admit that I felt like being the only person on earth using avro Rust in production, which was a weird feeling in that path. But once it's working, though, it's working amazing. And the first gratification I got from it is to actually see the effects of the Rust compiler optimization. So when you just wrangle with the code, you run in debug mode so that it's faster to build and to iterate. And then when you start getting serious, you just build your code using the release mode. There should have called it serious mode. And then it had some nice optimization. And this kind of optimization got my P50 latency down from something like 600 microseconds to deserialize one message down to almost 70 milliseconds. Sorry, microseconds. So it's pretty interesting. It allowed me as well to prove that avro deserialization is actually faster than deserializing JSON, at least in our complex schema use cases, which was something that was expected by a colleague of mine and that I could finally prove here. Now, experimenting requires tangible evidence of the impacts of what you're doing. And that's why I always, always, always set up and use Prometheus to measure everything right from the start of my iterating on an application. And a great learning that I wanted to share here is to fine tune your histograms, buckets, because the default ones are usually not what you are wanting. So you have to know what you want to measure and to have an expectation on the latency range of what you are measuring. In my case, I expect a CLA-DB insert, so in a remote database. I expect this latency to vary from five microseconds to up to 15 seconds, which is the maximal time outset on the server. This is how it's done in Rust as well. Once again, it's here for reference, but you basically start a timer, issue your query in the case that this query went okay. You can observe the duration, meaning you can store the result and the latency for Prometheus to scrap later. If it did not work, I don't want to pollute my matrix, so I just drop the timer and don't register it. So this was great. Now, once you have the matrix, you need to observe your code with Grafana. That's what we use at Numberly. And this is true as well for Python. It's not particularly related to Rust in this case. Graph all the things, really, but do it right. Make sure you graph query, throughput, rates, the occurrences of some things and know the difference between an occurrence and graphing an occurrence and a rate. Make sure that you also have strong details about your errors, Kubernetes, what not. One thing interesting is that the Grafana folks wrote a blog about graphing properly histograms, which is not as straightforward as it looks. So I've put a link to this because this is something that can have good impacts on the data and insights that you get from what you are graphing. So this is how it's done. Now, another interesting thing that I had the chance to work on is, well, since I had to issue queries to a remote database for every message I got from Kafka, I needed to find a way to absorb tail latencies that come from the database lookups so that it does not ruin my overall performance of the pipeline. Because sometimes you just have a query that can take, you don't know why, more time than usual. And you don't want your reading from this raw data stream of Kafka impacted too much by this no SQL query. That's what we call tail latencies because they happen to stack up a bit. And I had great fun discovering and playing with Tokyo RS, which is the leading asynchronous runtime in Rust. You can compare it to Async.io, but this one you have to install it. So it's not built-in, it's not batteries included. And for this, I tried to play and I played with using and controlling my green thread parallelism. I was able to cope with those latency spikes properly. So the main learning is keep your CPU bound operations in the main loop. In my case, I get on the main loop, I get the message from Kafka. I could decide to either, sorry, deserialize it right here, right now in the main loop or, that was my first approach, defer this deserialization and then the no SQL lookup database lookup into a green thread. But actually, deferring a CPU bound operation to a green thread has pretty bad performance implications. So use green threads when I.O. is required and wait as much as you can to defer this I.O. bound operations to those green threads and then allow some parallelism and control it within your code. A good demonstration with graphs, since I have numbers. Here you can see that at some point in time, the CLA-DB servers had some latency spikes, the select and insert statements, latency went up by a factor of 16, which is not something that you want. This is the graph of my green thread parallelism, which I also graph as you understand. So what it means is that since my queries were slower to the remote database, I was starting to have more green threads stacking up and waiting at the same time. Some of them were still very, very fast, but some of them, because of tail latency, were slowing down. By using this green thread parallelism, I was able to hit only my processing capacity, so the throughput of the whole reading of Kafka and outputting to Kafka by a factor of two. So instead of having my application impacted by a factor of 16, using this technique allowed me to only be hit by a factor of two, which is pretty great. Rust made this exploratory work very efficient and actually pleasant, even if I still was learning the language, and this is the kind of understanding that was harder for me to pinpoint using Python before. For the number of hungry of you, these are some key figures. So what's the max throughput of this application in our production pipeline? It's around 200k messages per second on 20 partitions, which if I relate to other numbers, let's say that you have 80 million messages waiting for you to be processed, you are able to process them in less than seven minutes. It's pretty cool. What's the late P50 latency of the avro deserialization? We already saw around 100 microseconds or less. Inserting data or getting data out, the NoSQL CLADB database is also less than a millisecond on P50. So that's pretty good as well, because they are really big tables, like more than two billion rows in the tables, and you're just looking for one of them. So it's pretty strong. Now, did I feel like losing time using Rust with all these experiments? Hell no. This whole experience actually changed me more than I anticipated, and the production results were rewarding as well. Now, allow me to share some thoughts and perspective about this journey. So while Rust is usually not an intranse programming language, I feel like, and maybe you feel too, that it's less intimidating that CRC++. I wouldn't have taken the chance on CRC++ myself. And that takes twin syntax, which is quite easy actually to understand coming from Python, because of this great level of abstraction. It makes you feel like a programming language that you could adopt, at least. Python plays very well with Rust, and Arthur showed us earlier this morning how. So thank you, Arthur, for this. And that way you can benefit from both worlds, as advertised by Brett Cannon and Karim before me. Beware, though, Rust being appealing to Pythonistas does not mean that it is an accessible language. Easy to adopt does not mean accessible. You should expect your Rust journey to be difficult. As a general proposed programming language, Python was thought to be accessible and easy to use. And I guess, and I hope that you all agree that it is, and losing all those batteries included is really, really hard at first. You feel like struggling for even basic things. The best example that comes to my mind is base 64, right? You need to base 64 some string, and you just have to install a separate lib in Rust, whereas you didn't think about it in Python, right? Now, Rust reputation of a systems programming language is not so wrong when it comes to understanding the internals of your computer. In that perspective, Rust sets higher expectation on you as a developer, and on your design decisions, I have experimented and showed you before. But that means that you also get the chance to understand the benefits and the drawbacks of certain implementations. And I learned a lot doing that, and I'm grateful that I could. Now, the Rust bureaucracy forces you to learn and care about those lower details. It can be devastating. I had some bad hours. Feeling bad about myself, yeah. But, yeah, you learn a lot and benefit from the optimizations that comes with it afterwards. It's a long-term investment. If you're in for the short term, maybe baby.org, you're just better than me, which is a high probability as well. The best example that I have in this is handling integers, right? In Python, you just set a variable, you set it to an integer like 2, and then later in your code, these two can transform into 115 billions. And you don't have to, and you don't care, right? This won't happen in Rust. You have to know in the first place that this integer can grow so big. If you don't, bad things will happen. In the best case scenario, the compiler will just tell you. So you have to account for this. The Python interpreter promoting this integer for you does not exist. You will inherently then be slower coding Python than Rust. Slower plus harder means that there's a bargain in using Rust coming from Python, I think. And this bargain can actually be beneficial if you have a clear picture of what you're looking for. Paradigms such as ownership, borrowing, lifetimes, and the result option syntax, they are mind blowing. Once you start taking interest in them and start understanding with them after struggling with them, you start getting it and just try to picture it. I mean, being able to write in the syntax to express whether something went okay or failed, or if that variable is either something or nothing, right from the syntax, no try except anymore, right? It's really changed my perception of the code that I write. So Rust is strict so that you can go to sleep without the fear that some random thing that you failed to cover or just forgot about will break loose in the middle of the night and that your own cold guy will just phone you at 3am in the morning. That's better than those random Python exceptions and I hope you didn't live through. I had this strange feeling as I went by writing and struggling in writing this application because even if it was harder for me than coding Python and I was really feeling uncertain about myself, I was actually feeling very confident in the software that I was writing. And that's a subtle difference. That's a really strange feeling. So this safe development mindset and experience brings confidence. Now, I would like to, I won't stress enough to reflect on the fast meanings. Reflect on those old meanings about the word fast. It's not a silver bullet. Listen to Brett Cannon. Python will remain my default go-to language to materialize my ideas and iterate on them. Another example of the rust bureaucracy that will get in your way, static types. Python type checking does not convey the confidence that rust type checking does. That's actually why it's called hinting, I guess. Good naming. Thank you. And Python is actually strict about types. It's just not strict at ensuring that a variable does not change type along the way in your code. That's not the same. And I never, I have to confess, sorry, today, that I never got used to type hinting in Python. And doing rust comforted me into not adopting it, actually. And not investing much time into it. Because I understood that that's because it was not what I was looking for when I was using Python. So why bother? So I'm not saying it's useless. I'm just saying that's not what I'm here in the first place. So I feel better with myself with it. And rust is super strict about it. And to my surprise, I find it reassuring when I need it for certain applications. So if I feel that my project requires those strict paradigms and bureaucracy and will benefit from it in the long run, then I can consider using rust. And then when I can consider it, I apply the rust bargain that I explained to you with my colleagues to decide. So remember, efficient does not mean fast or does not convey all the meanings of the word fast. Now, the Python community is a living joy and a great example of diversity, humility and inclusion. I'm proud that a general purpose language such as Python turned out to create such an amazing inclusive community. That's what Europe Python is actually to me. And that's why Martin said that I've been around for quite a while. And that's why I'm happy and proud to be part of it. I don't know the rust community as well as I do Python. But from my experience, I found it less you less used to bring in new people without overwhelming them with low level details that they don't need at the time of their own journey. So I wish the rust community takes inspiration from the Python's one and follow its path. Now, with not some random thoughts, rust changed me more than I anticipated. Using rust in production forced me to develop new reflexes that I applied to my daily Python. I still don't use type in team that much. But I care more about my variable types, lifetime and scope than before. One thing did not change. I'm still a mediocre programmer in Python and now in rust. And I'm still fine with it. I don't care if my rust is not as idiomatic as a real Russian, because I have the confidence that at least it's safe. I don't care that I don't master every feature of the language I use. I don't feel like I know and master every feature in Python already anyway. Are you using yourself all the Python features? Well, guess what? You don't have to use every rust feature either. Rust is not the silver bullet. Python is more, in my opinion, and at least it's intended to. And now that I understand for myself where I can benefit from the rust bureaucracy, I consider using it else Python is just great. Now, every upstart language takes inspiration from the existing ones. That was true for Python when it was an upstart language, which is not anymore, and it's also true for rust. But the other way around is possible too, I hope. In that regard, I wish Python tooling experience were better and took inspiration from rust. Karim told us before about cargo and I wished some cargo-like tool existed for Python. We could do some. I imagine it would be named PyKG something, so you want to create a new project and have a predetermined scaffolding and working example of a Python, how it's supposed to be with your requirements, TXT, or PyProject. Sorry, today. Already set up for you. PyKG new project name, thank you, just like in cargo. You want to have a new library installed on your project. In Rust, you do cargo add the create name and I wish we could do the same, using the same tool, actually, instead of having to switch to PyP. I wish there was a clear and embedded no-brainer reformatting instead of having to install black, so just straight from the language tooling. And I wish the equivalent of cargo clippy, which it's an amazing tool, it just analyzes your code and tells you what you could do better, detect anti-patterns and just propose you to fix it for you or explain to you why doing this could be made better by doing this another way. It's pretty amazing and I wish something similar when we were embedded in the default Python tooling. Once again, recent work on PyProject.toml and the Python interpreter hinting are going the right way and I'm happy about it. So it felt like taking the good in my Rust journey and bringing it back to my daily Python made me a better Pythonista. I hope this talk was informative and gave you some interesting perspectives. Let's keep in touch. I'll be happy to answer your questions and see you around during the conference. Thank you very much for being here. Thank you very much for sharing all your experience with Rust and Python together. We have time for maybe one or two questions, but not too many because dinner is short. So if you have one question, could you please go to the microphone and ask the question? Yeah, thanks for your talk. You had the opportunity to kind of burn your old projects and start a new Rust. I'm wondering if it's possible to like start something in Rust and still use your old Python code here and there. You could, technically you can. Karim and even better, Archer this morning showed how you can embed Rust code into your Python already preexisting programs for some specifics maybe that you could have. So yes, it's doable and quite easy to do, actually. So yeah, there is the most efficient one I think is PyO3, which I think is pronounced PyOxide for the O3. That's my old chemistry background maybe. But yes. Okay, thanks. Thanks for the question. The next question, please. Thank you for your presentation. My name is Viktor. The question is what can you suggest like, for example, migration architecture patterns or anti-patterns for the code migration recreation from Python to Rust? Keeping in mind, for example, that Rust doesn't support Object 3 in programming like classes and stuff. Also, Tokyo is not I think IO at all, so it's really different things. What can you suggest to people whose brain is damaged by Python or other languages? And in addition, I can you suggest to use PUN's build? It's a tool for doing what you mentioned. You also you can format your code and build and install dependencies, it will track dependencies and stuff. Okay. So when your brain is damaged, you that's a that's a that's a hard one. I think I think the best thing is to have actually the realization and have what is damaged in your brain in the first place. Okay. What is hurting you every day and try to figure out by looking at the perspectives and the Rust promises, if one of those promises could heal you there. So as I said, at least you know what you are entering and what you are looking for so that you can get a clear point of view and a clear focus, because that will allow you to not get overwhelmed with all the rest of the language, right? And it will also have a second good effect that you will get your rewarding experience at the end, because you you just know what you were looking for when when starting this journey. But apart from that, I mean, I don't feel like Python damaged my brain. I think that I hated programming because I was people try to to teach me Java, which I actually felt survival reflex to push back. And I think I did well. I didn't I did have I didn't have it using Python. So I think Python healed me more than it damaged me. But but but I think the paradigms of Rust, trying at least to understand them, even if you don't plan to use Rust every day, is really interesting, because that that's some people that that that that will help you, I think, see your code and approach code differently. And maybe you can find the right use case for you later. Thank you very much for that question. We have time for one more, please. Hi, thanks. Yeah, so I've been programming Rust for about a week. One of the things that I find most difficult to get my head around is lifetimes. And just at the end of the talk, you said that thinking about lifetimes has changed the way you think about lifetimes in Python. Just wondering, could you say a bit more about that? Yeah. Lifetimes is I still struggle with it. And even more with the actual lifetime syntax in Rust, which is not mind blowing, but brain damaging actually, Victor. That being said, I care about it in the sense that Python is very relaxed in how you can pass or not a reference to a variable in your code. So sometimes you you just ask yourself, hey, in my Python function signature, should I just pass this variable along all the way, right, or not? Or should I make it mutable and mutable somehow? So maybe I will consider using some another data model or internal data model. Rust doesn't have all this kind of mess, right, because of the borrowing logic. So you can go through your whole experience that's at least my experience of your Rust experience, just being fine and understanding the borrowing in Rust. And that's enough so that you don't have to care that much about lifetimes. At least it saves you for to keep this topic for way later. Okay. And this understanding made me look at how I create my functions and I pass variables and data around my program differently in Python, just like the signature stuff or using proper data structures in Python. Okay. Thanks for all the questions. We should now go towards the lunch break and we'll continue with the program from two. Let's have another round of applause for Lexus. Thank you.