EPISODE 1677 [INTRO] [0:00:00] ANNOUNCER: Linting is the process of checking source code for programmatic as well as stylistic errors. Ruff is a highly popular Python linter written in Rust. It was developed by Charlie Marsh who also founded Astral, which is focused on next-generation Python tooling. Charlie joins the podcast to talk about open-source development, Ruff, the UV package installer and much more. This episode is hosted by Josh Goldberg, an independent full-time open-source developer. Josh works on projects in the TypeScript ecosystem, most notably TypeScript ESLint, the tooling that enables ESLint and Prettier to run on TypeScript code. Josh is also the author of the O'Reilly Learning TypeScript book, a Microsoft MVP for developer technologies, and a live code streamer on Twitch. Find Josh on Bluesky, Mastodon, Twitter, Twitch, YouTube and dot com as Joshua K. Goldberg. [EPISODE] [0:01:09] JG: Charlie, welcome to SED. How are you? [0:01:12] CM: Thank you so much for having me. I'm doing great. Really excited to be here. [0:01:15] JG: Well, that's great. We're happy to have you. You've been working in programming and open source for a little while now, across multiple languages like Python and Rust. Could you tell us how you got your start in programming? [0:01:24] CM: Yes, sure. Let's see. I taught myself programming Java the summer before I went to college. I had some time to kill and people always told me that I would really like it. So, I spent some time learning how to program in Java before I went to school. Then, by the end of that summer, I was pretty convinced that I wanted to do computer science as a major. So, I spent four years at school doing computer science. In that time, I started to have some interactions with open source. I guess, my first real brush with open source was a friend and I worked on this project. We created this thing called Jasper, which was kind of like an open-source Alexa, before Alexa existed. So, it was ahead of its time. It was also not very good. The speech-to-text was not good, but it was scriptable, and people could kind of write their own modules. That was open source, and it kind of like, blew up a little bit, like it was on the front page of Hacker News, and there was a Wired article about it, and a Forbes article about it. So, it was kind of a cool thing where we built this thing for ourselves, because we thought it was cool, and then just saw it kind of explode and saw this ecosystem grow up around it of contributors, and lots of people using it and all that kind of stuff. That was my first brush with open source. Then, since I got started in industry, after graduating, I would say I've really been more of a consumer of open source than a maintainer. My experience, like over the past year and a half since we launched Ruff and then UV, the two tools that we've been working on, in this sort of Rust-Python ecosystem, I've kind of had a crash course in what it's like to be a maintainer. Because I came into that experience, again, mostly as a consumer of open source, right? I've worked in the web ecosystem, and made some contributions there. But I've never been a maintainer of anything big. So, that's been a very interesting evolution for me over the course of the past year, which was you go through this kind of phase of like you start the project, and like, no one really cares about what you're doing, but you have like a few enthusiastic people, and you're kind of doing whatever you can to make them happy and make sure that you're building for them. Then over a period of time, we evolved to now we have like, a lot of users and everything we ship gets feedback and gets tested within five minutes. It's been kind of an interesting evolution for me as a programmer and going from someone who mostly consumed open source to now someone who spends most of their time maintaining open source over the past year, year and a half. [0:03:54] JG: Do you prefer the earlier stage versus a later stage as a person who has to work on the project? [0:04:02] CM: Yes. I think it is almost strictly more fun. I have this - I mean, it's different, right? The position that we're in right now, like with Ruff, for example. We have well over like 10 million downloads a month, tons of the biggest projects in Python are using Ruff. We have like, I don't know, our VS Code extension has like half a million downloads or something. So, we have like a lot of users. With that comes, it's like, with great power comes great responsibility. It's like we have - there's a ton of impact to everything we do, which is awesome. But we also have to be like way more careful about changes that we make. So, when I started working on Ruff, it was really like the Wild West. I was shipping releases sometimes more than once a day. I wouldn't say I was like intentionally breaking anything but like things were breaking, right? I was changing the interface a lot and I was just kind of doing whatever because I was moving really quickly and there weren't that many users. Over a period of time that became, even if we weren't shipping breaking changes, like shipping twice a week became way too much, and people were like, "This is changing too often. My CI is updating every day." So, over time, we kind of pared that back. Everything we do now, it's like when we ship something, we know we're shipping it to a really large group of users, and that it has - it's kind of a cool feeling to be like, "Okay, if we add a new rule with an auto fix, suddenly, that's going to show up as an editor integration for this huge group of users. Everything we ship, it's a really good platform for impact." But I do have this weird feeling that, of course, I believe this because it's the way that I feel that this project. The hardest part, I think, of building software really is like compatibility. I think if you never had to think about users, and never had to think about like maintaining compatibility or anything like that. Building software would be so much easier. It's the exact reasons why hacking on prototypes and stuff is like way easier than working in an established project. Because now with Ruff, every time we want to make a change, we just have to be really thoughtful about how it gets communicated, how it gets rolled out. We have to maintain the existing version and the new version for some period of time. I find it a lot more fun to work in the early stages of the project. But the later stages are very rewarding. And getting constant feedback from users is something you often don't have in the early stages of a project and it's part of what makes open source, I think, really fun. [0:06:30] JG: Absolutely. [0:06:30] CM: You have this direct relationship with users that just doesn't really exist in the same way in a lot of other categories of software. [0:06:36] JG: Yes. Before we dive into kind of what is Ruff, why would you write a Python linter in Rust. I want to explain agreement with that point that there's always this push and pull of the more users you have, the more restricted you are in dealing with them. For example, you mentioned breaking changes. No one likes breaking changes twice a week. But if you don't have users, it's hard to get ideation for your project. Get feedback from people. So, it would be lovely if we could find an area of the industry that had kind of the ideal setup where we have a lot of users, but we're still able to do things quickly. I think building for developers is as close as I've ever seen to that, where developers are the most comfortable understanding, the concept of say, breaking change. Have you found that to be the case as someone who also works in the linting space? [0:07:17] CM: Yes. I think that's - well, first of all, I love building for developers. Because I mean, you can like cheat a lot, because you are kind of the ideal user. You're not ideal, but you are an example user. So, you get to rely on your intuition a lot and you're working really closely with people who have very similar problems and experiences to you, which I think can be really energizing. I mean, the main thing that I've found with breaking changes, and the like, is the most important thing for us has been to have clear expectations around them. We didn't have a versioning policy for a long time. So, people were like, "We don't know what to expect when we upgrade Ruff." As long as we know what to expect, we can like act accordingly. But if arbitrary patch releases can contain arbitrary changes, like it's just very hard for us to know, should we upgrade Ruff or should we not? One of the best things we did was we added a very formal versioning policy where we wrote out all the rules for it's - not exactly SemVer. It's like close to SemVer. I don't know. Anyway, we have a versioning policy. It's all a complicated, because it's adding a lint rule, a breaking change, like we had to kind of grapple with some of those questions. But we have a versioning policy around when we bump patch releases, when we bump minor releases, and we have a whole like preview mode where you can kind of opt into changes that aren't yet stabilized and all that kind of stuff. So, we've had to put a lot of effort actually into making that work. I think it works really effectively. And now that we have clear communications around it and expectations around it, people have been a lot more understanding. [0:08:46] JG: That's great. Let's talk about that. Understanding concepts. Can you just from the very start, tell us why is it that you would write a linter for one language Python, in a completely different language, Rust? [0:08:58] CM: Yes, totally. So, a lot of what I started doing with Ruff, and now with UV, some of the inspiration for that came from things I saw in the web ecosystem, where in web in particular, there were all these CPU-intensive problems that web tooling was naturally buying into or accepting. The fact that you had to bundle your code and minify your code. Those are concepts that don't really exist in Python, for example, but they do exist in JavaScript, right? So, ever needed to bundle their code and bundling became slow, and that, at some point led to the birth of esbuild. It's not important, whether it's like Rust or Go or whatever else. But the idea was we can empower people who are building in JavaScript by writing like the foundational tooling in other languages that might enable us to write more performant foundational tooling, and it should be done in a way that is completely unintrusive to the user. To use esbuild, you don't need to know anything about Go. Or you don't need to be able to write Go. To contribute to esbuild, you do. But to use it as a user, it should be kind of just transparently useful to you. Ruff is based on very similar ideas, which are we think that Python tooling could be really different, and I would hope in a way that is better. By leveraging, in this case, Rust, for example, to help us write tooling that scales to much larger and more complex projects. A lot of that's based on my own experiences, whereby I was maintaining a pretty large Python codebase. Well, I mean, large by my standards, not by like Google standards, or anything. But we were a team of 10 engineers and we had a lot of tooling. I was just sort of sitting around thinking, like, it feels unusual to me that my linter takes like one to two minutes to analyze our codebase, because we're a team of less than 10 people. We have like a moderately sized project. And this isn't a feeling that I really experienced in some other ecosystems. Is there a fundamental reason why my linting has to take that long? For example, was kind of the motivating question. The thing that I was seeing was like in web, there was more tooling being written in non-JavaScript. And then in Python, there are really, really good tie-ins, actually, between Python and Rust. There's a very good ecosystem of tooling that enables Rust-Python bridging in different ways. So, Rust was kind of a natural fit for that, and the real motivation there to start was like, let's try to build something that's much, much faster with few or no caveats or like hindrances for the user. So, it should just be a tool that feels the same in terms of - let's try to make sense, it doesn't have any tradeoffs. I guess, is the way we put it. But it's much more performant. That was kind of like the motivating thinking behind Ruff. It was like, I think there could be much faster Python tooling. Let's see if Rust can enable that and then let's see what kind of tradeoffs we would have to make in order to make that possible. [0:11:52] JG: That's fascinating that you're actually able to optimize for no tradeoffs. That's not something you normally hear in software engineering and design. [0:11:59] CM: There definitely aren't no tradeoffs. But I think the tradeoffs are good. The tradeoffs are or the negative tradeoffs, which are worth talking about of like building Python tooling in Rust, for example. Some of them are, one, we're shipping like big binaries. Our tools or like big compiled binaries. That's really different than shipping a bunch of Python files. So, there's like a binary size element to it. Our development process is more complex, because we are writing Rust, and then we have to build native artifacts and publish those. Our development and release process is more complex. The third is we don't really have access to the Python standard library or anything like that. So, often, in Python linters that are written in Python, they might need something from the standard library like a parser. Hey, parses source code, right? Or, hey, given a string, is this like a valid variable name, right? That's like a reasonable thing to ask. There are things in the standard library that enable all that. The standard library has a parser and it has things like that, that you can leverage. We don't have access to any of that. So, if we want a parser, we have to write our own parser and maintain our own parser. We do. As new versions of the language come out, like we have to keep up with that and implement all of that. So, there's a big maintenance cost to that, which is we have to keep up with Python itself, because we can't leverage the standard library or anything that's built into it. Then the biggest - the limitation, or the drawback that I hear about most often, which I think, actually hasn't been true for us, but I think can be true in general, is this idea that if you build Python tooling in Rust, for example, or JavaScript tooling in Rust, or JavaScript tooling in Go, or whatever, you will not be able to create like a community of contributors, because there will be a bar to contributing and getting involved. Because most of your users write Python, for the most part, probably, and they may not know Rust. So, that statement is probably true. But for us, at least, we haven't really had problems at all with creating, like bringing contributors and creating a strong and sustainable contributor base. You've actually had a lot of contributors who it's their first-time writing Rust, and they see Ruff as an interesting entry point for them. They want to learn Rust. They know Python. They're looking for someplace to contribute and they see Ruff actually as like a place for them to go and contribute and kind of learn Rust. So, I don't know how it would work if every tool in Python was written in Rust. Would there be enough people right to contribute and maintain those tools? That's kind of a different question. But for us, at least, we have like, I should have looked before coming on the show. I don't know. We have like a couple 100 contributors. We have like a lot of contributors and we get tons of issues and PRs and a lot of people who want to come in and learn Rust and see this as a way to do so. So, I think it can vary a lot. It's definitely a higher bar to contributing than a tool that's written in the language that it's meant to serve 100%. That's just indisputably true. But I don't think it's necessarily true that it's impossible for people to contribute or that you won't be able to find contributors. [0:15:13] JG: Your contributors' graph on GitHub has exactly 100 contributors from August 7, 2020 to March 14, 2024, and you have more commits Ruffly than everyone else combined. Nicely done there. [0:15:26] CM: I thought, we have 396 on the front-page contributors. I don't know what the difference is. [0:15:34] JG: Tooling is hard. But it's like a chicken and an egg problem, that if you have an ecosystem largely driven by contributors in one language, it's hard for them to move to a different language, which means you have fewer people in that other language, which then the cycle continues. So, if anything, having good, important, new experimental to stable tools like Ruff exist is a good forcing function, to make it so that more people jump on to this new type of tool, which can then make more of this type of tool happen. [0:16:01] CM: Yes, and I think, again, I'm not trying to necessarily say that there is no cost to writing the tool in Rust. There are definitely a lot of people who would like to fix an issue, but they don't know Rust, and they don't have time to learn it, or they don't have motivation to learn it or something very reasonable. That definitely does happen. I think for us, the best thing we can do is like, try to make it inviting project for people who do want to learn Rust, and do want to use this as either an excuse to learn some Rust, or like an opportunity, or an entry point, or something like that. So, trying to have good like contributor documentation, being willing and able to mentor people who come in and write Rust, for the first time, and giving them feedback, and kind of coaching them through it. Those are all things that we try to do. You're right, I think it can compound over time. We've had contributors and Ruff which we released August 23. I think, August 22, and I'm getting confused, like a year and a half ago, we've had contributors there who then contributed to UV, which we released less than a month ago. And those people, I don't know how much Rust they wrote before they contributed to Ruff. It totally depends person to person. But there's definitely people who like, we see them again on our different projects. So, it's cool to see that growing a little bit. It's still very nascent in Python, I think, but it is becoming more popular. [0:17:27] JG: I want to talk a little bit about the difference between what a linter means in say python versus Rust, versus other languages. Every language has a different concept of what is a formatter versus a linter, versus a type checker. Python in particular, I think, has a really interesting, what is a type checker model, given that it's a very dynamic, intentionally funded loose language that has had types added to it over time. So, from your perspective, or the Ruff perspective, what is the role of a linter in a language like Python? [0:17:56] CM: Yes. So, one thing that's happened with Ruff, prior to Ruff, I would say the Python ecosystem, the static analysis tooling was very - this isn't even - this word sounds pejorative. It's not necessarily meant to be, but it was very fragmented, right? There were a lot of different tools that were intended to be used together. For example, like import sorting, and making sure that your imports are sorted and organized, that was a separate tool called [inaudible 0:18:27] that was viewed as distinct from a linter. There were lots of different linters that covered more narrow categories, and some of them had plugins. But there were also standalone tools, like Pi Upgrade, for example, is a very common tool. It's a linter that includes code mods. But it effectively exists to try and upgrade you to newer syntax, or newer standard lib - things in the standard library. So, if you're going from Python 3.8 to 3.10, there's a bunch of stuff that you can fix, or change, or upgrade, and it exists to serve that role. One thing that happened in Ruff sort of by accident, is we ended up consolidating a lot of tooling into one. I sort of say, performance for Ruff was like the flagship feature. But I think one of the most impactful things for users has actually been that like Ruff now bundles a lot of different stuff together. So, when people migrate to Ruff, they're often replacing, like 20 or 30 tools with one. In Ruff, import sorting is a lint rule. It's just another rule amongst many others. I would say, and I guess the other thing that's different about Ruff is like we do a lot of code modification. So, we do a lot of like, I don't know about most, not quantitatively, but a lot of our rules ship with auto-fixes. So, we'll do code transformations to try and fix them automatically. For Ruff, I would say the linter is really about like trying to identify potential problems in your code that are not type errors. We view that as separate. So, it's not about type checking, but trying to identify potential problems in the way that you express an idea, and trying to hint people towards better ways to do it, or oversights, or other kinds of mistakes. But it's very distinct from a type checker. We don't do any type checking in Ruff. We sometimes like leverage type inference to be like, "Oh, this is probably an integer." So, it's unsafe to call this method with an integer. We might do things like that. But it's a little bit different. That's actually a good example. Imagine it's unsafe to call some method with like an argument of a certain value. Maybe you're doing some like cryptography thing and the argument it takes is like an integer that indicates which hash algorithm to use. A type checker will be fine with like any integer, probably. But a linter could actually say, "Yes. This value is considered like unsafe or unsound. Or this isn't that cryptographically secure." So, linter, it's not necessarily about validating that the types of things are correct. It's more about understanding the behaviors, and a lot of the library-specific and domain-specific concepts, and the ways they might be misused. [0:21:16] JG: Do you have the ability to have library or specific lint rules loaded in by users for their specific library or framework? [0:21:23] CM: No. We don't support any user-provided or user-defined rules or plugins right now. That's actually part of why we ended up bundling so much stuff, is in the early days of Ruff, I couldn't really see, and I have more ideas around this. But there wasn't like a clear and obvious path to user plugins, and user-provided rules, because like in Python, and in JavaScript, that's much more straightforward, because the languages are so dynamic. So, people can just kind of like hook into what you're doing, and you just like run some JavaScript or run some Python to run their rules. In Rust, that's a lot more complicated, because everything is compiled in ahead of time. So, the idea of like a user providing a rule, and being able to run that, like an arbitrary rule at runtime, is just there are interesting ways to enable that. But there aren't like clear and obvious and established ones. So, at the time, it was like, okay, people need this set of lint rules in order to adopt Ruff. Okay, I'm actually just going to build those into Ruff. That is what became like the current system, it was like, okay, people need these, like, docstring-related rules. All right, let's add support for linting docstrings and it kind of like spiraled out from there. [0:22:38] JG: If anything, that might be a strong value prop for you, and ESLint, the JavaScript linter, we have all sorts of user pains from these multi 100-line es lint configs. There's a new, what's called flat config system, that's being migrated to as we speak. It's a lot of user pain. There is value in these user plugins. But the fact that you've built so many things such as docstring validation, internally to Ruff, I imagine must mean a very nice, smooth user experience for trying to adopt Ruff from a new project. [0:23:05] CM: Yes. I think it ended up being a strength. So, first of all, I think it's a better experience for users. I think, second right now, and I think second of all, it's been really helpful for us as maintainers to effectively not have a public API, which I know is like a little bit of a callous thing to say. We do have a public API. But the public API is our command line. So, we don't have users who are like using Ruff as a library, and that just gives us a ton of flexibility. Because we can effectively change like anything we want to write internally and do a lot of refactoring. The tool is just very much in flux, as we're just developing it pretty aggressively. So, it's been actually really helpful to have those boundaries. I think there's a little bit of a difference. People still want custom rules and I think it's still interesting to think about how to do that. I'm quite interested in supporting it. I do feel like there's a little bit of a difference between like plugins and custom rules, which maybe sounds strange. But the thing that I see that people want the most is like rules that are specific to their application. A plug-in would be like a way to lint docstrings. But a custom rule could be like when you're in this class, you can't import things from this other module. A custom rule, in my mind, the things that I see that people still want, even though we bundle a lot of stuff is like ways to enforce things that are specific to their application. Or in some ways, proprietary, or like domain-specific. That's kind of what I'm more interested in enabling, because that is something that every company has. [0:24:33] JG: Sure. Let's talk about companies a little bit. A lot of companies, a lot of dev teams, web platform, dev platform teams, use linters, and other nice static analysis tools to help make sure that the developers are writing safe, consistent code, whether it's logically consistent, stylistically, and so on. If you were to try to sell Ruff, or to advocate for Ruff to a dev enablement team, what are the points you would make around features that we haven't yet talked about or pull it all together? [0:24:57] CM: Yes. I think, like I said, the flagship feature is like performance. The second is simplicity. But there are like a few others that have been really important, I think, to Ruff's success. One is like, we just had a really strong focus on making it easy to adopt. So, from the start, we basically mapped our rules back to existing other lenders so that when people migrate, they should see effectively the same sets of violations, and that's been like a really helpful thing, because people moving from project to project, they already recognize the rules. They're already familiar with them. Their projects are probably already in compliance with them. So, lowering the activation energy required for people to actually use the thing, I think is really important. Over time, we are going to ask more of users. We're going to try and change user behavior in meaningful ways. By that I mean, like, it's not necessarily clear that that's the best possible API for Ruff. It's just the most convenient one for people migrating over. Eventually, we might be able to come up with better APIs, and maybe better tools to help people get started, and all that kind of stuff. But having a really quick on-ramp for people I think is like, it's underratedly important, for adoption and for getting people to buy it on your thing. If you have something that's actually dropping compatible, and it has X, Y, and Z benefits, and it's like 10 or 100 times faster, suddenly people have to come up with reasons like not to use your thing. They have to be like, "This is why I'm not using it." You kind of invert the calculus around decision-making. From a developer marketing perspective, that's sometimes how I think about what we build. It's like, how do we make it? We should make it as easy as possible to adopt with clear and obvious and like indisputable benefits. Then, we should look at all the reasons that people can't adopt it, and we should just like somewhat ruthlessly go inspect those and understand, can we fit that use case or can we not? [0:26:51] JG: It's a funny word to use there, ruthless. Because in a sense, you're right. Of course, you're looking at these problems and aggressively - no, it's a good word to use. Because it's a negative connotation of ruthlessness. But it's a positive application that your early stages of a tool, you're really building it out. That's what you're doing right now. You're figuring out what are the problems in the ecosystem and trying to solve them in a way that users can stomach. [0:27:13] CM: Yes. That was sort of an attitude that I developed when working on Ruff early on, because there were - it'd be a project that's like, I really want to use this, but it needs to implement like import sorting. And by ruthless, I mean, then I would just be in my head. I'd be like, "All right, we're going to add in for sorting." Then, it would be hard, right? But I would sit down for a few days and just figure it out and just focusing, I think, just people, you just have to really, honestly ask yourself, what stopping project X, Y or Z from using my thing? If you want your thing to be used, which is a totally separate question. A lot of people build things and this is not their goal and that's totally fine. But if you are trying to build something and gain some traction and momentum, I think you just need to really look honestly at other projects, and be like, what's stopping them from using my thing? What are the things that are getting in the way? It doesn't mean that you have to do everything the same way as an existing tool. That's not necessarily the lesson. But you do have to think about what tradeoffs are you making? What are you asking of the user and what is it buying? What is it buying you to do that or not? [0:28:16] JG: It's a much smaller activation energy to go from not using your tool, to using your tool with the same settings. It's basically just the same thing and faster than alternatives. Then, it's also a much smaller activation energy to go from that state to using your tool with more powerful, better APIs as opposed to it all. [0:28:33] CM: Yes. That's kind of the - that's like part of the transformation that we're thinking about now is like, if we started from scratch and said, "What do we want Ruff's API and interface to look like?" And we didn't have influence from existing tools, what would we do? Is there a way for us to make it so users can who are already using Ruff to migrate to this new system, in a seamless way? So, it's a lot of the same principles, which is, like think about how you make things as easy as possible for users. A linter, especially, the linter is not going to be the top priority thing for - well, for most teams, it's not going to be the most important thing, right? They probably have other things to prioritize and it could be a huge quality of life improvement for them. But even still, it might be hard for them to prioritize. So, putting yourself in a position where they actually can prioritize it, is one other way to think about it. [0:29:21] JG: Absolutely. [0:29:21] CM: It's the most important thing in my life, working on the linter. But it might not be the most important thing in their lives. [0:29:27] JG: Absolutely. Let's talk a little bit about the long-term then. We've talked about onboarding to linters to Ruff and so on. The industry is going through a little bit of a slow upheaval with AI in general dev tooling enhancements, and it looks like linters and linting and static analysis are also going through a nice set of revitalizations. What do you see as the next 5 to 10 years of linting in Python, Rust, and similar? [0:29:51] CM: So, I think, especially the intersections with AI are pretty interesting. I've seen - well for one, so I think that it's interesting to think about the role and importance of static analysis tools as we get towards a world where more and more code is generated non-deterministically. So, I think about this sometimes. If people are generating more code, I think the value of tools that are consistent, and explainable, and reliable, actually kind of potentially goes up. If you have a bunch of generated code, the idea of having a linter to enforce consistency and style and correctness, I think is actually more important, potentially than it is today. The other thing to think about with AI is like, there have been some interesting explorations around LLM-powered linters. So, linters, where maybe you like describe a rule in plain text, or you give it a few examples, and then it figures out how to detect it on your code base. I think that's pretty interesting, actually, built a little bit of a prototype around this, before I released Ruff. It was called the auto bot and it was kind of meant to be like Copilot for your codebase. So, you gave it an example. You gave it a before and after. You give it a diff. Then, it would go and try and apply that diff to usages across your codebase. I think that's quite interesting. The idea that you could take things like custom, or domain-specific logic, things that you could describe in plain text and figure out how to map those onto lint rules. I think the thing that doesn't really capture is that most people in my experience actually don't want to be writing their own lint rules. That's actually a lot of the value of a linter, I think, is you're putting together a curated and opinionated set of rules. Most people don't want to sit down and like develop their own taxonomy of rules, even if it was made very easy to do so. So, I think the value of this stuff still remains very high. I think it totally depends on like, it's just very hard to predict what people's relationship to code will be in 5 or 10 years. That, for me, is the part that's very hard to predict. [0:32:00] JG: Absolutely. [0:32:01] CM: I'd be curious to hear your opinion on it, actually. [0:32:04] JG: Oh, absolutely. I'd be happy to. I think that one of the many interesting things about the AI revolution that's happening is that for the first time, in a long time, we are seeing the average quality of written code go down. AIs write really, really bad code. It's extremely difficult to understate how bad most code written by AIs is. So, the value, as you said, of tools such as linters, of type checkers, static analysis in general that can look at code and tell you exactly what's wrong with it and how to fix that, especially, as Ruff does support fixing, as you noted. That goes up. That's really good. And of course, APs are going to get better over time. We're just seeing the very, very early onset of them. The comparison I like to make is that saying that AIs are going to replace developers is like saying that a Roomba is going to replace a Janitor. It's a very small subset at the current time. So personally, my answer is I'm just excited to see linters get more powerful so that we can, from the bottom up, so to speak, really improve the code that's written whether it's by humans or AIs. [0:33:04] CM: Yep. Yes, I agree. One of the directions that we're trying to take Ruff in is like, we want Ruff to become just a lot more like sophisticated and powerful over time, which are very, like vague terms. But like in more specifics, what that would mean is we want Ruff to be able to do type inference and analysis and inference across files, which is just those are like really big upgrades compared to what we do today. So, it's really nice. In Rust, Rust linter, Clippy, that has access to a lot of type information, and so it can implement a lot of really powerful rules and transforms that like in Python, we can't really do, because we just don't really know what any of the types of anything, of any of the variables are. We have rules that are supposed to only operate on dictionaries, for example, and we kind of have to guess in various places if something's a dictionary, and like, we can do like a little bit of local type inference within a function, but we don't trace across boundaries or anything like that. So, that's the direction that we're trying to - we want to move in with Ruff over the course of the next year, which is just making it the highest confidence tool in Python, I think, is one way that we've thrown it around internally. So, just trying to increase the confidence of the diagnostics that we give out that, we produce, and make it so users can really rely on it when it tells you things. [0:34:21] JG: How does type checking across files work with your extremely performance friendly architecture now? Do you foresee any problems with trying to cache information across files that then gets invalidated by fixing? [0:34:34] CM: Yes, definitely. Well, I mean, we have a really simple architecture right now, which is we don't do any analysis across files, which is actually pretty amazing that we're able to get away with that. But we are and it works. We analyze every file independently and we can cache on a per-file basis. That's like all going to need to change though once we start thinking about, yes, multifile analysis, and it's pretty critical. I mean, it matters a lot too, for like language servers, and language servers introduced their whole, their own set of challenges. For example, in like a language server, this is like an editor or an editor integration. The user could have like two files open and they could be like editing them, and maybe the changes haven't even saved to disk, but you might want to reflect to the changes in the user's editor. So, there's a mismatch between like the stuff that's on disk and the stuff that's in memory, and like the user's representation of the code. These are all things that we need to be thinking, that we're going to be thinking about over the course of the next year, basically, is like growing Ruff into something that can deal with analysis across multiple files, that can do really smart type inference in Python, that can support really good in-editor experiences. These are the kinds of directions that I want to be taking the tool in, or that the team wants to be taking the tool in, and I'm also excited about. [0:35:52] JG: Sure. Are there any other directions that you feel compelled to note, as coming up sooner or later for Ruff? [0:35:58] CM: I think for Ruff, this is related to the editor integration thing, or to language servers, sort of. But it's a very interesting technical problem. We want to make Ruff a lot more like error-resilient. So, often, if you're in an editor, and you're typing, most of the time, you have a syntax error. Most of the time, you're typing something that's not a valid syntax, because you type function, and you haven't put the parentheses in yet or something like that. So, we don't really support that at all right now. Our parser, when it looks at invalid source code, it has to just kind of give up. If you're like typing, and you have like a syntax error, all the diagnostics will like disappear until you like get back to not having a syntax error. So, we want to make our parser and the linter-like error resilient, which means that when they hit a syntax error, they can actually still like try to parse the rest of the code. They can kind of guess at what's missing and keep going. It sounds sort of crazy, but it actually does work, and like other tools, do it, which is you try and build error recovery into the parser and say, "Okay, this looks like a function, but the parentheses are missing." So, let's just keep going as if this is a function, and parse the rest of the file. Then, you get a much smoother experience when people are an in-editor, because while their files are in this broken state, you can still surface the same information in the same diagnostics. So, again, it's really to me to like, we want to have a really, really good in-editor experience and I think that's a big part of it, but it's not something we do today. [0:37:25] JG: I actually have one area I'd like to get your take on you haven't brought up yet. I noticed on the website, you've got a lot of preview rules with names like missing whitespace around modulo operator, missing whitespace, multiple spaces and so on. Are you building formatting concerns into your linter? And do you believe that formatter in linter are not intentionally the same tool in your face? [0:37:45] CM: I think they're definitely different. So, we do have a formatter, which is separate from the linter. So, if you run Ruff Check, that's our linter. If you're on Ruff Format, that's our formatter. And the formatter, it's totally separate from the linter. It's in Python, it's most similar to this tool called Black. In the web ecosystem, it's pretty similar to like Prettier. One of the problems we run into is like the formatter came after the linter. So, we did have some stylistic rules in the linter that cause problems with the formatter and we've had to document that. We don't really want to remove those rules, because some people use them, and sometimes they're valid maybe, if you're not using a formatter. But we have had to spend a lot of time like documenting, basically, the linter format or compatibility and achieving that has actually been quite challenging. A lot of it comes from the fact that we did the linter first and then the formatter, and there are things in the linter that we built that weren't compatible with the formatter. The other interesting thing that's Python specific is, and Python has this thing called PEP 8. A PEP is a Python Enhancement Proposal. This is basically the process that they go through for language changes and other kinds of standards. Very early on, there was a proposal called PEP 8, which is it's a style guide. I think there's some debate around whether it's meant to be like how rigorously enforced it's meant to be as a recommendation or requirement for Python code. There are parts of it that are ambiguous and maybe a little under-specified. But there's definitely a community people who their main goal with formatting is to adhere to PEP 8, which is less rigorous than writing a formatter on your code. It is less well-defined than writing a formatter on your code. But there's a bunch of lint rules that we have that are focused just on PEP 8, in part because there are a lot of people who don't use formatters, but want their code to do in PEP 8. These are the kinds of dynamics that we have to be able to balance in our tools, which is we have just a really large diverse community of users, and we want to build to support, like the group of people who don't want to use a formatter but one, use PEP 8. We've decided that's something that we're going to support. We could decide something we don't want to support. But we've decided we want to support and that creates some of these challenges. But for me, if I had my way, and I guess I do, I could have my way. But I was going to just say whatever we're going to do, whenever we want, I probably take all the format-related roles out of the linter, and I would say, you should just use the formatter and that would be that. But again, these are the kinds of things that - they're the kind of judgment calls and balances that we need to make. For now, at least, we've continued to support that. But I suspect you hear that. [0:40:20] JG: That's very, very mature of you, to have an opinion and not enforce it. I'm of the similar opinion. In the JavaScript space, we've shuffled all of our formatting rules into a separate plugin from core. But you're right, a lot of users wants them and do not want or even cannot use a formatter. [0:40:37] CM: Yes. I think some of it, I guess one of the ways I develop empathy for that, is like to try and understand why people would want that. Why would they want just PEP 8, but not a formatter? In some cases, the feedback that we received is like, these are people who are teaching Python, and it's a lot to introduce a formatter, but it's easier for them to introduce like, "Hey, here's PEP 8. Here's a document that talks about how you strike code and it's less more limited, and this is what we enforce." So, I don't know that I like completely agree with that. But I do definitely understand it, that like if you were someone who is teaching the language for the first time, or someone you were teaching it to people who are learning program for the first time, having something like a document that spells out, these sort of like looser rules for general coding practice makes sense. The other thing, I will say, actually, this is like so specific. But since we got on this topic, there is one cool difference about cool - I'm using the word cool very loosely here. There's one cool difference between like linter and the formatter, which is a linter, will tell you what's wrong and why. Whereas a formatter will generally just change your code, right? So, there's no formatter that I know of on Earth, that will tell you why certain parts of the file were changed, and like what they were violating. It just tends not to be in the design. When prettier reformats your code, it doesn't tell you like this function was under-indented. So, it had to be indented more. That doesn't really happen. But a linter does. A linter actually gives you feedback on like, you've had three blank lines here, but you're only supposed to have two. So, I have more empathy for that as like a teaching tool, for helping you understand why your code is "wrong". But again, I think it would make everyone's lives a lot easier if we just use the formatter. It certainly makes our lives a lot easier. But yes, anyway. It is an interesting topic. [0:42:24] JG: Yes. That's a good segue, speaking of teaching. I want to end the interview on a few personal history questions for you. You haven't always been in your current role at Astral. You, I believe, we're at Khan Academy. Is that right? [0:42:38] CM: Yes. I started my career at Khan Academy. I was an intern there the summer after my junior year of college. Then, I went and joined full-time, and worked on a lot of different stuff. I mean, I started my career in web front-end. We did a lot of - Khan Academy was kind of like, I don't know if it's actually true. But it was said that it was like the first company to run React in production outside of Facebook. The number one committer to React outside of Facebook was Sophie Alpert, who worked at Khan Academy at the time, then join Meta. We just had like a lot of React. We were just really big on React. So, I wrote a lot of React in my career. Then, I also worked on like our early, our first Android and iOS applications. Then, our web at the time, our web app was entirely written in Python. They since rewrote it in Go, which was like a year's long migration. Very interesting in its own. But everywhere I've worked, actually, we've just had like a ton of Python, just like sort of by coincidence. [0:43:42] JG: That's how you managed to escape the JavaScript ecosystem. [0:43:45] CM: A little bit. [0:43:46] JG: A little bit. [0:43:47] CM: I like the JavaScript ecosystem. I miss it. [0:43:50] JG: It's a great place to be. So, for a lot of people, for context, folks, like you and I, who have worked at companies like Khan Academy or Code Academy, there are full-on development teams. Not everyone there is teaching. But you are on the content tools team at Khan Academy, which helps make tools that help other people teach code. Of course, you are likely mentoring, teaching other folks internally. Are there any particular lessons or ideologies that you still keep with you from Khan Academy, the good pedagogy there that you've taken to using linting today? [0:44:20] CM: I mean, actually, mostly, it's like, I think it had a massive influence on me, but mostly from the perspective of like engineering and building engineering teams, and like shipping code and technical best practices and all that. The value of code review, I think was like, really, I keep using words that sound pejorative. It was really hammered into me. I just learned so much from really good code reviews at Khan Academy and I've tried to carry that with me for the rest of my career. I kind of view code review as like, maybe the most important avenue for learning and teaching other developers. Maybe not everyone would agree with that. But I don't know. At least in my career, I got so much out of that. I think having clear values on a team is another thing that I got out. We had very clear engineering team principles. That's something I've kind of tried to carry with me to through other engineering teams in my career. So, I think, in my time at Khan Academy is like, where I learned how to be a developer, basically, and hopefully, how to be like a good developer. I don't know. I just learned so much. It was like such a good experience for me. [0:45:29] JG: My last content question for you is, you do trivia in your spare time. What is and what are the benefits of LearnedLeague or Learned at League? [0:45:40] CM: LearnedLeague? So, I'm in a daily online trivia league called LearnedLeague, and I love it. I'm not that good. So, if you're in LearnedLeague, don't look me up, because it's like mildly, mildly embarrassing. But I do love it. The core concept behind it is like, you're in a division with like, there's like 20 of you. Every day, you're playing against someone, and everyone in the league has the same six questions that they need to answer and you have all day to answer them and it's purely honor code that you don't look them up or you don't cheat in any way. So, you have all day to answer these six questions and you're both trying to answer the questions and you're matched up against an opponent, like, it's Charlie versus Josh. You also have to rank the questions in terms of how difficult you think it will be for your opponent to get them. So, there's an offensive component, which is like answering the questions, and there's a defensive component, which is, I think, there's no way that Josh knows this one, but he definitely knows this one. Then, at the end of the day, there's a scoring system based on how many you got right, and how your opponent ranked them for you. You either win, lose, or draw, and the season just proceeds like that. I don't know. I've always been really into trivia, despite being not that great at it. I watch a lot of quiz shows in my spare time. I spent less time now on LearnedLeague. I used to spend a lot of time on LearnedLeague. You have all day to answer all six questions. So, you'd be surprised, like, you might think you see a question you don't know the answer. You'd be surprised how much time you can spend thinking about a question that you might know the answer to. So, that's one thing I like to do. I like to do in the spare time that I do have is like ruminate on those six questions all day. [0:47:20] JG: That's fantastic. Do you feel like it helps you with programming to try to get your mind in the mindset of trying to think on a rattle like that? [0:47:28] CM: I think it can. Yes. I think it's helped with my memory, maybe. It has actually made me like more educated on various topics. I did a deep dive for a while where I was trying to get a lot better at art history. I know this is so nerdy, but I was like, I was trying to get better art history, so I started doing spaced repetition, where I was looking at paintings and trying to - I would have to name like the painting and the artist. I just did this like for 10 minutes every day for like six months or something. It actually like very materially changed like how much I know about artists and art history. Some of it is superficial. Being able to name who painted a painting is not actually like important, right? But it then pushes you and understandings like, "Oh, what is like fauvism? What is abstract expressionism?" You kind of learn, it's a vehicle for learning more about topics that you didn't already know about. So, I don't know. It's something I like to do and it's something I share with. There's a group of my friends who are also in the league, so we always like debrief on the questions afterward and talk about what we knew and what we didn't, and lament the things that we almost got right, and all that kind of stuff. It's a good like form of social connection too. [0:48:35] JG: Socialization and connections and friends are great, but also specifically art history actually, has a lot of really interesting applications in modern development. I started fantastic talk in last year at Wey Wey Web. Isabella De Cuppis. Pardon with the pronunciation. Talking about how form follows a motion, not just function, where a lot of modern designs like say the iPad, the iPhone, were reactions not just to the needs of the consumer, but how people had previously been trained in design, in art, to work with things like skeuomorphic design and the iOS interfaces. It's a common example. That's relevant and useful, not just fun. [0:49:10] CM: Yes, exactly. [0:49:12] JG: Great. Well, Charlie, this was absolutely phenomenal. Thank you so much for coming on. I love talking to people who are working at the latest, greatest, cutting-edge open-source stuff that pushes the industry forward. So, really excited that we got a chance to talk. Is there any way that you would prefer people find out more about you or reach you on the Internet? [0:49:28] CM: The best thing is to follow me on X. I post a lot of stuff there. Then obviously, my GitHub too, if you want to be at that level of detail, there's a lot of stuff coming through my GitHub as well. But X is the best way to keep on top of things that are happening with for me personally, and for Astral, and for Ruff and UV. [0:49:46] JG: Great. And both of those are @charliermarsh. [0:49:48] CM: Yes. [0:49:50] JG: Super. Well, for Software Engineering Daily, this has been Josh Goldberg with Charlie Marsh and Astral and Ruff. Cheers. [END]