The Rustacean Station Podcast

What's New in Rust 1.46 and 1.47

Episode Page with Show Notes

Jon Gjengset: Hello, fellow humans. We are back yet again. Aren’t we, Ben?

Ben Striegel: Yes. Welcome back to Rustacean Station, for our new tradition of doing a podcast every two Rust releases.

Jon: You know, I like that a lot. I think it gives some nice sense of continuity, when you get to talk about how things have changed over a longer period of time. Builds anticipation.

Ben: It provides some perspective, certainly. We could talk about things that happened in the context of the previous release and the current release, at the same time.

Jon: It’s very exciting. I think it also helps that we’ve been very lucky and we’ve had a couple of guest submissions. You may have heard, if you’ve been listening to this podcast for a while. And keep in mind that Rustacean Station is a community podcast, is really the idea. So if you have some burning desire to make an episode about, really anything Rust-related you can think of, just reach out on the Rustacean Station Discord, and we’ll happily help you get an episode set up. The podcast becomes better when other people contribute, too. Ben and I are dull and boring, and we can’t really think of anything but these versions, you know?

Ben: Well, let’s begin.

Jon: All right. 1.46, Ben. What has changed?

Ben: Let’s look at the first thing here. So, const fn improvements. And so, in our previous episode, I think we talked for a while about how nightly was starting to use const fn, and how this was coming, and so we should still probably give a bit of a recap. Because it’s been a few months, and I’m sure there are some things that are worth going over again. So in this case now, you’ve probably heard of consts in Rust. They let you essentially inline an expression at compile time and guarantee that it kind of gets copy-pasted into any place that you use it at. So const fn is kind of a way of saying, hey, so I’m just having like these, like a raw, numeric literal that gets copy-pasted around, you can actually write an entire function, and the output of that function will get copy-pasted to wherever you use it. And so at the start this was very limited. Kind of like, imagine, you know, a function that just returns an integer literal. So it wasn’t much better than just using a raw integer. In some cases, you could provide some encapsulation to enforce, you know, module boundaries, privacy and whatnot, which was the original emphasis. In this case though, we have now actually— I say we, as in the royal “we”, the Rust developers— I have literally no involvement with this change at all— is to allow you to start doing some actual programming. By actual I mean, like, branches and loops.

Jon: Maybe we should refer to it as them. “They have. They have implemented.”

Ben: They have. We’re all a part of it, right? Everyone in the community is a part of Rust. Some have more involvement with the code than others. But we can all say that we love Rust equally.

Jon: Yeah, that’s true.

Ben: In any case. So they have implemented branching and looping, which are kind of the basic fundamentals of programming, of a Turing complete language. And certainly it’s a bit difficult, although possible to program without these. But now we’ll be seeing in the discussion of the next release some things that this has allowed. But to go over it now, if, if let, match, while, while let, and loop are now all allowed within const fn, so you can start doing some actual cool things in these.

Jon: It’s interesting that for is not supported yet. I’m guessing because it relies on iterators.

Ben: Right. And so for works on any type that implements the IntoIter trait. And I believe that implies some mechanisms that aren’t yet ready for stabilization. Maybe not even implemented, I’m not sure. I’m not sure how far along you know Miri is.

Jon: Yeah, I don’t know. It is pretty cool, though, that now you can do all these additional things at compile time. Like now you can do, once you have conditionals and loops, you can suddenly implement, like, logic in const fns, which I assume, unlocks a lot of cool future things.

Ben: And we should go over again, that const fn is powered by a Rust interpreter, which is part of the compiler. And this interpreter is called Miri, so named because it works on the MIR. The middle intermediate representation, which is kind of a sub-language that Rust compiles down to, before it gets passed on to LLVM, or cranelift, or any other backend you could imagine. And so, this interpreter lives in the compiler, and it does certain things. It’s actually a pretty cool thing. Like, so, Miri is used for conceptually more than just const fn. It’s a full Rust interpreter. And one of the cool things it can do is evaluate your unsafe code for you, and tell you if you have undefined behavior. It’s kind of like Valgrind in that way, or any of the other sanitizers you might have for, like, a C++ code base, where it can kind of create this virtual memory and then detect, hey, like, you touched this thing, and you weren’t allowed to touch that thing, like you would have gotten undefined behavior here.

Jon: Yeah, I really like Miri. It’s such a cool project. And its magic just continues to baffle me. Like, it recently got support for locks, which means that now you can start to actually test threaded programs with Miri, which is going to unlock a lot of cool unsafe testing as well.

Ben: Yeah. Speaking of unsafe testing, though, so I was curious. Kind of like, so, one of the things with— the idea with Miri is that, like, with const fn in Rust is that we want— “we” again; they want to—

Jon: The elders.

Ben: The elders. The council wants to prevent certain things from happening at compile time. And so, for example, a thing that you might— I don’t want to get, like, too into the weeds here. One thing that you can’t currently do, to my knowledge, at compile time, and may never be able to do, possibly, is floating point operations. And the reason for that is that different hardware is allowed to, by the spec, do different things for certain floating point operations.

Jon: Yeah, this is stuff like rounding for example, right?

Ben: Yeah, where, like, so— because Rust doesn’t want to tie itself to, like, any one particular kind of hardware. So the option is either you, like, re-implement all of these things in Rust and say, hey, here is like, the canonical way of doing it. But even if you do that, then you have run the risk of, for example, writing a function that has different output at compile time than at run time. Where if, say, at compile time, Miri tried to evaluate this float, using some math written to be a portable thing. Then at run time, maybe you’re using the hardware interface, which had a different result. You could have these two diverge, and that could lead to very bad things.

Jon: Yeah, interestingly, I had a conversation a while back with— I forget who it was. One of the Miri folks who was talking about how we might even be able to get what are essentially, like, compile time heap allocations where you can, like, build out a tree structure, for example, at compile time. And then the resulting memory and the pointers get, like, burned into the final binary. Which would be really neat. I don’t think this is something you can do yet. I think it was mostly hypothetical.

Ben: Kind of just becomes static memory.

Jon: Yeah, it’s really cool. It’s such a such a neat idea.

Ben: Is there an advantage there, to actually using, like, you know, I guess if you wanted to use a Vec and didn’t want— You could just use static memory in that case. But if you wanted to maybe use a Vec internally or have a structure that just uses a Vec, without having to reinvent all of the collections on your own.

Jon: Well, I’m thinking if you want to do something like implement, like, a B-tree or something, that is built at compile time, I mean, you could do it in just a vector. But realistically, you sort of want separate allocations for nodes and stuff, right? But this— it is true, this is getting very in the weeds. But it’s a— I’m really excited to see where we get to with const over the years.

Ben: One last thing though, is that with regard to things that you can’t do in const fn, is that unsafe is a bit limited. I think intentionally, in terms of not wanting to allow you to do unsafe operations at compile time. Possibly for risk of like, totally defeating type safety, are you aware of any of the actual details there?

Jon: I don’t know exactly what’s going on, but I suspect you’re right that it’s like, you don’t want unsafe code that could undermine the compiler itself. Like unsafe code that runs at compile time can mess with the compiler. I think there’s probably some hope there that Miri can contain the damage. But that’s probably why they’re hesitant, would be my guess.

The next change is one that I’m pretty excited about, and I know it’s something that is going to have some effects beyond just the immediate things it’s used for. So back in March, with Rust 1.42, we got this really neat change where you now get better error messages when you call unwrap and methods like it. Basically so that panics don’t include all this sort of extra output that includes lots of internal Rust standard library calls, and instead gives you just like, the unwrap happened at this line in your code. And that was really neat. And when it landed, I talked a little bit about #[track_caller] and how that was the underlying mechanism that enabled that. And in Rust 1.46 #[track_caller], the annotation, or attribute, has now been stabilized. Now this attribute is really neat— and I highly recommend you read the RFC; it’s pretty readable. But the basic idea with #[track_caller] is that any function that is annotated with the #[track_caller] attribute, any code inside of that function that tries to figure out what the— like, where the code was called from, things like panics that access, sort of, the current location. They all will use the, sort of, file and line number and column number of the caller to that function instead. So if you have, like, the example they give in the release notes is, if you want to define your own unwrap function. Then if you write #[track_caller] and then fn unwrap, then now, if you panic inside of that function, the location that will appear for that panic will not be the unwrap function, like the line inside the unwrap function where the panic is called from, but rather the thing that called that unwrap function. And the underlying mechanism here is pretty neat and actually surprisingly straightforward, and the RFC goes into a lot of detail. Basically, it’s a proc_macro that adds additional arguments to the function, sort of transparently, and then modifies anything that calls that function to add those arguments. And those arguments are basically the macros for the current file, the current line, and the current column. So that way, the caller passes in the location information, which can then be used internally by things like panic. Now what’s really neat about #[track_caller] is that you don’t just have to use it for unwrap. You could use it anywhere where you have a bunch of internal library code and you want to point to an error that happened outside of yourself. Usually, this will mean that you ended up calling panic somewhere. But there are other use cases for this. I know that there’s been a bit of work on using this in error reporting, for example. Because in error reporting, you don’t really want to say that the cause of the error was somewhere deep inside of the error handling library itself. You want to point back to where the library that’s interacting with the error reporting tool or framework actually said, like, an error occurred here. And so #[track_caller] gets handy whenever you want to sort of hide some of the internal complexity in giving messages to the user about the code.

Ben: Those are the two big changes in 1.46. There’s still a few smaller things I think are worth going over. First of all, we talked about how const fn improvements landed, and they didn’t actually result in too many formerly non-const fns becoming const fns in this release. But there was one, which is an important one: std::mem::forget, which lets you intentionally leak memory, is now a const fn. I can’t actually think of a use myself for that, Jon do you have any idea?

Jon: No, I’m not actually sure. I was reading up on the tracking issue for it, and what was written there was, the usage would be in const functions dealing with generics, where you don’t want the generics to be dropped, which I’m not quite sure how— I don’t even know if you can have generics in const code— I guess you can. That makes sense. Yeah, I don’t know. I’m honestly not sure where mem::forget is useful in const code, but I think as with many of the things we’ve talked about before, much of the use here is that if you don’t know where it might be useful, it’s still worthwhile to make it const so that you’re enabling future innovations, that you might just not have thought of yet.

Ben: To be forward thinking in that way.

Jon: Exactly, yeah. I also dug through some of the detailed release notes, as I always do. And there were some smaller ones that I thought were pretty cool. In cargo, for example, cargo will now automatically pick up your README.md or README.txt file without you having to explicitly set the like, readme value in the Cargo.toml. So this might mean that suddenly on crates.io we’ll see a bunch more projects that finally now render a README because the authors didn’t know they had to explicitly list it. Another one that is going to hit very few people, but when they hit people it matters, is that there’s the env! macro, which lets you access an environment variable at compile time. And this macro is really handy for things like, if you want to compile in some constant into your code, and you want to modify it in the build environment. However, in the past, if the value of that environment variable that was compiled into the binary changed, cargo would not rerun the compilation of that binary, so you would still have sort of, the compiled binary contained the old value, and that’s now been fixed. So now, changes to environment variables that are compiled into the binary will now trigger a re-compile. That might fix some subtle bugs that have been painful.

There’s also one small change, that we talked a little bit about before we started recording, Ben, which is this ability to now index into nested tuples.

Ben: Right, so this is— you say the ability, it’s actually just the syntax works now, so you’ve always had the ability to do this. And so let’s just go over real quick. So if you have a tuple, you’re probably used to destructuring it to actually get the things out of the tuple. But tuples are just structs, conceptually. They’re structs, but you never gave them any names for their fields, and so Rust, instead of destructuring, you can just use the normal dot syntax to get the fields out of the struct. In this case, it’s a tuple with, you know, an anonymous struct. And so the names it gives you are numbers starting from zero. And so if you have a tuple foo, you could say foo.0 to get the first field, .1 to get the second field, and so on.

The thing is that before this release, if you had, like, a nested tuple, you have, you know, several tuples within tuples. And you tried to get one of the inner fields, you have, like, foo.0.0, say, you get an error. Because what was happening is that Rust was trying to parse foo.0.0 as a float and saying, hey, this is kind of a weird float you’ve got here. I have no idea what this is. So now that’s just fixed.

Jon: It’s funny when you think about it, right? It’s basically, it’s parsing it as foo. followed by a float, which means that it thinks that you’re accessing, like, a fractional tuple element of foo.

Ben: Yeah, so before you would, just to fix this, you would just use parentheses around it. And so it’s not a new ability, it’s always been there. But now you don’t need this workaround anymore for this kind of obscure feature, which I never ran into. But apparently Jon is happy about this.

Jon: Well, I think it’s because I use newtypes a decent amount internally in the code to, like, try to give additional type safety when— just to disambiguate types that are handled by the compiler. And then these new types, often you just have, like, you just wrap a single type, so you just end up with a .0, and sometimes you want to access what’s inside of it, inside of the newtype. Then this comes up. And so this just saves me from having to name these fields, even though, like the number of structs I have that have a single field called inner is getting a little bit ridiculous. And this means that I no longer need to have all those inners.

Ben: One of the other minor release-note changes that caught my eye. And so we mentioned before how mem::forget was the only newly const made function in this release. But there’s kind of a weird exception, and so mem::transmute, also a very important function, highly unsafe. But now it is usable in both static and const contexts, but not const fns. And so it was not marked as a const fn. But now you can use it in statics and consts. And I’m not sure exactly yet what that restriction is there for. I’m not sure. Jon, you didn’t know either?

Jon: Yeah, no, I’m not sure. I could totally see why you might want it in const, because, like if you have a pointer to something, sometimes you just need to be able to cast it in a way that you can’t do with a primitive cast and you need to use transmute. But I don’t know the ways in which this may go wrong, or why they’re now sure that it won’t. I’m not sure.

Ben: It might just be, like, an abundance of caution, where it’s like we want this to be usable in const context. But we’re not entirely sure. Unlike mem::forget, transmute is an unsafe function, and so they might just be like, hey, let’s just make sure we know what we’re doing, getting into here, before we, like, totally commit to using this in, like, all const contexts.

Jon: Yeah, I think another thing that’s that’s worth mentioning about 1.46, before where we move on to 1.47, is that 1.46 was the first release, as far as I’m aware of, that had a pre-release. So on the Inside Rust Blog, the Rust Team, the Rust Release Team has started posting messages about, like, just in advance of the release of a new stable version, they post this little thing that explains that the 1.46 release is about to release, like, it’s going to be scheduled for this Thursday or something, and then it gives you instructions for how you can try out that release locally, just in order to, like, make sure that everything works ahead of the actual release. and I think they tried this with 1.45.1. That’s the first one I see at least. But I think 1.46 was the first major version that that had this pre-release testing phase. So that might be of interest to someone who’s using Rust in some big production setting, where you really need to be able to test things in advance.

Ben: I did have one more thing too, that I wanted to kind of just go over in a more general sense. The change itself isn’t super exciting. It’s just that now, if you have a tuple of a String and a u16, you can now convert that into a socket address very easily. But I wanted to kind of go over this pattern in the standard library of how, like, for example, so a socket address is going to be a hostname, usually you’d see it hostname:port#. Maybe you’ve seen this in the web browser. Type in a URL to access, like, a special secret port, or a server on a different port than the usual default of 80 or 443. And so in other cases, though, so you say you have, like an IP address, and an IP address gets— so let’s say version 4, it’s just like, it’s four u8s, like four triplets of— actually, octets. That’s it.

Jon: I think, octets, yeah.

Ben: That’s it. It’s just four octets. And so, but, like, before you actually make it into an IP address, so an actual, like, a structure with all the semantics of an IP address, maybe you have it as a String. Maybe you have it as a tuple of these four octets. Maybe you have it as like a bunch of different things. And so Rust actually has a few constructors for this, and it’s using the into pattern, which there definitely great blog posts out there about using into. And the reason I want to go over this right now is because people are coming from C++ or Java, one of the things is, “how do I do function overloading in Rust?” Rust does not have traditional function overloading where you would just, like, define a function and give it like a different input type. And then you can call it with different parameters, and it would dispatch automatically to these different functions. But it does have into.

Jon: Yeah, like, same name and different signature.

Ben: Right. And so it does have this into thing instead. And so the idea is that if you are writing, say, a library, or the standard library, where you want someone to pass in an IP address, because maybe they’re making a network connection of some kind. Instead of forcing them to beforehand, make an IP address using one of, you know, some specific constructor, you can just say, hey, like, we’re in this function. I’m going to as my primer type, I’m going to take an Into<IpAddr>. And then, in that case, a user could then call this function with, say, a String or with their tuple, or with any of the other— there’s a few different ways of making the IP address, and then we don’t need to worry about this. And so effectively from the user side, the API consumer, it looks like function overloading. From the side of the library implementer, basically, what you can do is, you could have a bunch of these things, if you want to like, say, like, dispatch to a single function after you have, like, normalized, you’ve like, called the function. You have, hey, like we now, like, we have this parameter. It’s just you know, so and so implements Into<IpAddr>, we just called .into() on this, and now we have, no matter what it used to be, now we have our IP address. And so it’s a very, like, nice, like a bit more principled, less flexible but more principled way of doing this function overloading pattern from other languages, which new users might want to look into.

Jon: Yeah, and I think the other thing that’s nice, and this particular change exemplifies that, right, is that over time you can keep adding new implementations of into, and all of the functions that rely on into will automatically start being able to do, like, accept arguments of that type is well.

Ben: Right. So that’s all that I had for 1.46. Shall we move on?

Jon: Yeah, Let’s do it. I’m excited. 1.47. Man, time flies, right?

Ben: We’re really going through it.

Jon: We’re at 1.46, now we’re at 1.47.

Ben: Rust moves fast.

Jon: Right. So this too, the first element here, on traits implemented on larger arrays, is also something we’ve talked about before. About how Rust has this feature that’s still baking, called const generics. And so, the idea is that if you have an array, that is of some length N, you might want to have implementations that are generic over that number ,. And in theory, you could have other types that are also generic over numbers, and not just over types. Now, the primary way in which const generics is currently exposed to the world is through implementations on arrays. So the idea is that if you have an array of some length, you might want it to implement, say, Debug, no matter how long the array is. And it used to be that the Rust compiler would pull this trick of, it just copy-pasted the implementation, like, 32 times and just implemented, like impl Debug for [T; 0]. And then it did the same for [T; 1], [T; 2]. And it just repeated this down.

And of course, internally, the compiler is allowed to use unstable features like const generics. And so a little while back, most of the standard library was updated to use const generics internally for this instead. So rather than duplicating all this code, it just had a single const generic implementation for any N. In practice though, they, the elders, did not want to expose const generics to users of Rust, because const generics are not stable yet. And so they put this additional restriction on these generic implementations that said, like, array::LengthAtMost32. So the idea here would be that they wouldn’t expose any implementations of arrays— implementations of traits for arrays longer than 32, because that’s what they had in the past with this manual implementation, and that restriction has now been lifted.

Now things like Debug are implemented for arrays of any length, through the const generics internal to the compiler. And I think the decision for why they chose to remove this sort of arbitrary restriction was that they realized, and you’ll see in the PR that this argument is made as well, that there isn’t really— you’re not really committing to const generics by exposing this feature. All you’re committing to is that going forward, there will be implementations for arrays of arbitrary length. And the mechanism just happens to currently be const generics. But there’s nothing in the, in what has been stabilized that requires that. So it might be that in the future, const generics just, like, imagine const generics does not work out, they could still provide some other built-in mechanism in the compiler that gives implementations for arrays of any length, and that would be sufficient to still fulfill the contract that’s now essentially been stabilized.

Ben: Stabilized in some sense, yeah. So again, actual stabilization of this feature is still forthcoming with no timeline. But there has been movement here in terms of the idea being, sort of trying to stabilize all of const generics was a pretty big thing. Trying to stabilize just a minimally useful subset of const generics, which now has its own feature flag at the compiler, the idea is to get all current uses compiling on this, to kind of like shake out any bugs, and I believe that’s coming along pretty well. I look at the— there’s a tracking issue for the min_const_generics feature flag, where they’re going over, pretty recently, the remaining blocking issues and any bugs that are happening. So, yeah, follow that if you want const generics in your own code.

Jon: Yeah, I mean— what I meant with stabilization here was more of, the stabilization of implementations for any length arrays. Like, that doesn’t bind them to const generics, even though it seems pretty likely that const generics, or at least some part of it, is going to come hopefully, maybe soon.

Ben: Yeah. Hopefully this year at least.

Jon: One interesting point that we don’t need to spend too much time on is the fact that most traits are now implemented for arrays of any length. But there’s an exception, which is the Default trait. So they now have these generic implementations for any N for, like, Debug and Clone and Copy and whatnot. But Default is not among them. And the reason for this is that in the standard library today, like prior to 1.47, there’s an— the implementation of Default for arrays of length zero do not require that the type of the array implements Default. So even if you have a type that doesn’t implement Default, you can still get a zero-length array of that type, by using the Default trait.

Ben: Which is totally reasonable, in a vacuum.

Jon: Yeah, because you don’t have any Ts, so it doesn’t matter if T implements Default. Unfortunately, there isn’t, at least far as I’m aware, a way to express this in today’s const generics, right? If you imagine that you write like impl<T, const N> Default for [T; N]. You don’t have a way to say, like, require Default for T if N is greater than one, or greater than or equal to one, but don’t require it if N is 0. You can’t currently write that. And you can’t write it as two different impls, because they would be overlapping. Now maybe specialization can combine with const generics in some interesting way to deal with this in the future, but I think for the time being they decided to keep the manual implementations for Default this time around. And then it’s something that might in the future be stabilized to also work for any length array.

Ben: It’s one of those things where whenever you’re doing like, you know, an initial release of Rust 1.0, kind of, try to be forward thinking and think, hey, let’s not, like, you know, pin ourselves into a corner. But you can’t ever catch anything, so sometimes these things come back to bite you. Unfortunately, it’s not a super big deal in this case.

Jon: Yeah, but as you said too, I think the standard library, as it was, makes a lot of sense, like you probably do want zero length arrays to implement Default. And this might even be a thing that, like, macro authors could take advantage of, I’m not sure. I’m sure there are various crates that take advantage of zero-length arrays in some way. And so I think the standard library originally did the right thing of saying, we’re going to implement Default for any T if the length is zero. And now we just need to figure out how to actually make that work with const generics.

Ben: The worst case scenario, I imagine you could do some kind of weird compiler hack. Obviously, it’s not the ideal solution, and you’d probably prefer not to do that if you can avoid it. But it’s not totally intractable, I think.

Jon: Yeah, I mean, I think the idea is to use specialization on constant generics, right? So that you say you have a generic implementation that’s generic over both T and N, and then you have an implementation that’s generic over T but not N, it gives a concrete value of 0 for N, that does not require Default, and specialization in theory could could detect this thing that’s specialized to 0 is more specific than the other one, and realize that it needs to use that one.

Ben: Even then, though, I’m not sure because it seems like the— so the second, this second specialized implementation where it’s like we want to implement Default for this. It’s simultaneously both more and less specific than the other one.

Jon: Yeah, it’s weird.

Ben: And so it’s like, could that even be done? And specialization is so up in the air right now, Who knows?

Jon: Yeah, I don’t know. But I guess this is why the elders have decided that it’s not time yet.

Ben: And we have fallen into the weeds once again, so— in our digressions.

Jon: Alright, let’s let’s backtrack back up, to shorter backtraces.

Ben: You’re talking about the #[track_caller]

Jon: I’m sorry, Ben.

Ben: —in the previous release, and I get the impression that what’s happening here is actually related to #[track_caller], where there was a regression a while back where the backtraces got a lot bigger. Where previously Rust was trying to kind of compress backtraces to only be useful and then #[track_caller], some changes there made them much longer, and now we’re kind of getting back to the the good and proper, only having useful info in backtraces. Do you have more information about this?

Jon: Yeah, I think what actually happened was just the— like, in certain cases, the mechanism that Rust uses to shorten the backtraces didn’t work correctly. In fact, looking at the PRs, I think what happened was that the sort of marker thing that that Rust uses, which is this __rust_begin_short_backtrace stack frame was being optimized away by the compiler, using, like, tail call optimization. And so now, when walking the stack, it couldn’t find the frame that told it to stop walking. Because it had been optimized out. And so now, that frame has— there’s been some modifications to ensure that frame does not get optimized out. Which means that now the unwinding stops at the right place. And now you get the short backtraces again.

The other change that landed in 1.47, that I don’t really know what to say about, is that LLVM, which is sort of the backing compiler, or— is compiler the right word?

Ben: Code generator.

Jon: Code generator, yeah, that’s right. Was upgraded from the previous version to LLVM 11. Do you know more about what this means?

Ben: Not really. I mean that— the idea obviously is that LLVM is not, like, internally doesn’t have, like, a stable interface. And so you kind of have to keep, like, upgrading. Because it’s kind of like, you know, they provide an interface. But if you want all the new and shiny stuff in terms of all the improvements, you have to keep on upgrading. And so Rust is just on the normal update treadmill. And in this case, I think one of the— Rust doesn’t, you know, need to update as soon as LLVM has a release. And in this case, I think actually, it might even be on a pre-release of LLVM 11. But I think that a few releases back, there was some concern about some performance regressions in LLVM. And I think that from what we’ve seen, that that has kind of been clawed back in this release. And so obviously compiler performance is a big deal. And so I think people were just eager to upgrade.

Jon: Yeah, I managed to dig up the performance— like, there’s perf.rust- lang.org, which gives you, sort of, performance information about every release, including nightlies, of Rust. And that particular change that that integrated LLVM 11 seems to have just given mostly small but not insignificant compile improvements, for basically every benchmark that they have there. The fastest one is, like, sped up by almost 30% which is, it’s obviously pretty significant. But most of them hover around like 3%, 2% or so, which is still pretty good.

Ben: There are some new— actually, before we do that: Control Flow Guard on Windows. What is this?

Jon: Yeah, so this is— I mean, I don’t really use Windows much myself. But when I saw this in the release notes, I had to go digging a little to figure out what it was. And on the, like, Windows documentation page for Control Flow Guard, there’s a little diagram, and basically what it does is, it injects a little bit of code into your binary that does some runtime checks to make sure that if someone, like, attacks your program and manages to overwrite a buffer or something, they can’t easily take over control of the control flow of your program. So this would be something like, imagine that you take a function pointer F, and you call F through that function pointer. And then imagine that some attacker manages to overwrite F with an address of, like, some evil code they want to run instead. What CFG does, the Control Flow Guard, is basically inject this little check, just before the call to F, to check that F is still a valid target, that it doesn’t point into some, like, bad memory or something. And if it does, then it just terminates the process. There’s a little bit of sort of coordination that’s required here between the compiler and the Windows kernel at runtime that keeps track of, like, what are valid jump targets, like what are valid functions to call, and such. So it basically prevents some attacks that are bad when they happen. I don’t know how effective it is at preventing real attacks, like in the wild, but it certainly seems like something that that could be a pretty good idea, to just—

Ben: I’m sure it’s based on things that have been observed in the wild. I know that, like, it might not be as useful for Rust in particular, in terms of, like, if you’re writing unsafe code in Rust, I’m sure it is, but it’s not actually turned on by default in rustc, because I think the idea is that almost all Rust code actually doesn’t need this. Obviously, there are plenty of things in Rust, that Rust does turn on by default. And so you have, like, typical hardening things, like position independent code and all the other— we have stack guards and stack canaries and things that kind of like, even though normally you wouldn’t encounter these. Sometimes it’s just good to have for additional security. But I think in this case, it was just decided that the performance tradeoff to actual risk was not good enough to turn it on. But obviously, you can turn it on if you so elect to, with this new flag to rustc.

Jon: Yeah, that sounds about right.

There were a bunch of smaller library changes in 1.47 as well. And I think before we go to some of the specifics, do you want to talk a little bit about the const changes here?

Ben: Yeah. So as we mentioned before in the previous release, const now has the capability of doing a bunch of ifs and loops. And so there are plenty of newly-const methods. In this case, although the scope is pretty narrow, the biggest group are probably: for all integers, there are various operations. There’s checked_add, checked_sub-traction, checked_mul-tiplication, and by “checked” we mean that in this case, if it would overflow, it will provide an error. Also saturating_add, saturating_sub, etc. In this case, if it would overflow, it will— or under flow as well, I’m pretty sure, I guess in subtraction. I guess you can also add a negative in the signed case.

Jon: Yeah, it’s—

Ben: But yeah, in either case, instead of overflowing, they will just kind of clamp themselves to the highest value. And so, if you try to add two MAX ints, you just get MAX int back. Or try to subtract zero or, you know, like a MAX int from zero, you just get zero back. And so different people want different behavior from integers. If they overflow, again, there are also, in the standard library, overflowing versions of these operators, and then the default, obviously, if you just use normal operators, is to panic in debug mode, overflow in release mode, although that is implementation-defined and that can change theoretically on, say, hardware that did this efficiently, they could just say, hey, overflow or check.

Jon: Yeah, I think the standard library calls overflowing just wrapping, I think, because that’s usually what you observe in practice, right?

Ben: Yeah, So all of these are now const fns, and so you can get this nice overflow-free or overflow-checked behavior in your const math. Also a bunch of operations on ASCII, and so you can check a string and say, hey, is this like an ASCII string? Are all these upper case, or are all these, like, digits? There’s a bunch of these. We’ll link to these in the release notes, but a lot of ASCII- specific string functions are now implemented as const. So you can do this at compile time.

Jon: Yeah, and I assume that many of these were really just waiting for branches—

Ben: Well, also loops, because you have to go over every single one.

Jon: Right. Yeah, that makes sense.

Ben: So yeah, it’s just a natural extension of, hey, we can do this now. Let’s do it.

Jon: Yeah, that makes sense. There are some other smaller changes that are cool. I really like that we now have Vec::leak. So Vec::leak is basically the same as Box::leak. So Box::leak was already in the standard library; it lets you take a heap allocation, like a Box<T>, and then give you a static mutable reference to T. So the idea here is that if you allocate something on the heap, and then you forget the Box, then because the Box will never be dropped, the memory is never freed. Therefore, that pointer lives forever. And therefore the reference is valid for the static lifetime. And now we have the same thing for Vec where you can leak a Vec, so you leave the heap allocation running forever, it never gets freed. And that gives you back a mutable slice reference that can live for however long you want it to, up to static because it will never be dropped or invalidated.

Ben: leak is a much more direct name than forget, which is how you would normally do this.

Jon: Yeah, because remember that in Rust, leaking memory is still safe, right? There’s no memory safety violation. It just causes you to run out of memory.

Ben: Well, so is mem::forget.

Jon: Yeah, exactly. I also thought that a pointer::offset_from is kind of cool. So this is something that comes up a lot if you end up doing some kind of pointer arithmetic, especially if you deal with parsing or interacting with certain low level C libraries. pointer::offset_from is a pretty handy function to have available to you.

Ben: I was curious to see some additions where, so now there are some new constants. Numeric constants, not just, you know, const in the Rust sense, but like, actual numbers defined in the float modules, f32, f64, for a constant called Tau. Are you aware of Tau, Jon?

Jon: Yeah, Tau is 2π, right?

Ben: It’s 2π. And so it’s kind of like saying, hey, like, why not, just, we already have— pi is already in there. It’s always been in there, and the idea being, like, why should we have tau in here? And there’s a whole— it’s kind of— I don’t know. It’s kind of a funny thing to me. I’m not a mathematician. I think some people feel very strongly about tau. I’ve read, there was a document called The Tau Manifesto, which argues about how pi is the wrong constant to use for circles. We should be using tau instead. And broadly, tau is defined as 2π. And because pi is defined as the ratio of the circumference of a circle to its diameter, tau is just circumference to radius. And so if you’re using— if you’re in application domains where radius makes more sense, I think a lot of trig functions, especially, care about this. And if you’re working in radians, tau is more natural than pi. Then you probably want to be using tau. Other than that, you could just be using 2π, honestly.

Jon: I mean, I think the summary of The Tau Manifesto is that we should be saying that pi is half tau.

Ben: Right.

Jon: That’s really what we should be saying, Rather than saying that tau is 2π.

Ben: We’ll link The Tau Manifesto in the release notes. It’s worth a read. It’s good. It’s just good fun math knowledge to have, in terms of like, illuminating why certain things are the way they are. It kind of just shows, too, like there’s path dependence in a lot of fields. Or maybe if people had been using tau for the past 2000 years, maybe we’d be like, we should add pi, now. Pi actually is the right constant to use, and so—

Jon: We would have The Pi Manifesto.

Ben: All fields have path dependence, and you know, change resistance, and Rust is not immune to these things. Rust has plenty of inheritance from C-style languages and other things before it, and things after it will have to decide, you know, languages that follow Rust, take up the mantle, decide what to take from Rust in the future.

Jon: It’s true. It’s true. In 1.47 as well, there were, like, some smaller detailed changes that I’ll go over quickly. One of them is that build dependencies, so these are often things like proc_macros, or bindgen, or other things that have to, like, parse code and spit out something else, like things that depend on syn very often. Those things used to be compiled in the same mode that you’re compiling the main crate. The idea here was that, why wouldn’t you? And the answer turns out that for these kind of build dependencies, often they’re not doing that much processing. And so it doesn’t really make sense to spend a lot of time in the compilation process to optimize them, when they’re only going to run for a very short amount of time. And so, with 1.47 the default is now to not optimize build dependencies. So that way you can compile them quickly, and then if they run and only have to take a little bit of input, you probably won’t even notice. So in theory, this should make compilation of certain crates a decent amount faster. Of course, as the pull request that made this change also argues, there are cases where this is not true. Like if you have a bindgen or something that has to process, like megabytes of code, or gigabytes of code. Then at that point, you might actually want your build script and bindgen to be built in release mode. And there’s an override you can use to do that.

There was also a really cool change to some of the Cargo documentation, that I was very happy to see, which is basically a SemVer compatibility guide. So this is a document that essentially goes through what are all of the things that constitute breaking changes in Rust, and what things do not constitute breaking changes. And also gives some examples of which are which, and why these matter. So I highly recommend you give this a read. We’ll link that in the show notes as well. And also, and this is very minor, but it’s going to matter on, like, you’re going to notice this on the usability level, which is that cargo help— so if you type cargo help, followed by some command on the command line, it used to just give you this, like, short command line, brief help text, basically what’s generated by clap. And now it will actually display the man page. It will give you a lot more detail about how each cargo subcommand works, which in theory should be a lot more helpful to people. So now, when you write, like, cargo help publish or something, you’ll get more expansive text that might include things like examples, rather than just a very terse explanation of exactly which flags it accepts.

I notice one more thing, actually, that I’d like to talk about for 1.47. And this is something that is not— you’re not really going to notice if you are using Rust, as much as you’ll notice it if you’re helping contribute to Rust, which is that x.py, the tool that’s used to bootstrap the Rust compiler when you build it from source, used to have all these weird settings, that every guide on the compiler suggested you change the the settings for, or run it with particular flags so that it wouldn’t take as long. And now all of its defaults have been updated, so that normally you can just run x.py build, or x.py test, and it will just run the suggested things rather than build lots of things you don’t need. This is a very minor change, but it’s a big quality of life improvement, I think, especially for new contributors to Rust itself.

Ben: I think that’s all that I had for 1.47.

Jon: I think that’s all I had too. There’s a pretty exciting change that’s coming down the line. There’s a bit of a teaser, probably 1.48.

Ben: Jumping the gun?

Jon: Yeah. So this is— I’m only going to mention them briefly, because I’m guessing that we’re going to talk about them a decent amount when it comes out. But these are inter-doc links. The idea here is that if you have in your doc comments in your code, let’s say that you have a library that has two methods. It has two functions, foo and bar. And in the documentation for foo, you want to link to bar, and vice versa. Well, previously you had to write like [`bar`](...), and then find, like, the filename that cargo doc would generate for the documentation for bar and stick that in there. Basically, you would have to write the URL, or at least the the relative URL directly into the documentation.

And now, with inter-doc links, you don’t need to do that anymore. The documentation compiler basically is able to resolve symbols for you and then automatically link them. So you would just write [`bar`], and that’s all you had to write, and the compiler would realize that you meant to link to bar in the same crate. Because you didn’t give a particular URL. And this works for all sorts of things, like modules, or things in other crates. So you can do, like, crate::some_type. It works for methods and types and consts, like whatever you can think of, anything that can be— that you can, like, use in that file in the Rust code, you can also just name directly in the documentation with the— surrounded by ticks and square brackets, and the links will be generated automatically. So this is just a really nice quality of life improvement for, for writing good documentation.

Ben: Well, I think that’s it.

Jon: Yeah, I think that’s it. I think for people who are excited about, like, as we talked about, we the Rust community, right? There are two working groups that have recently been announced, or project groups, I guess they’re called, where if you’re interested in helping out the Rust community, I recommend you go give those a look. One is the error handling project group, which is going to look at how to improve the error handling story in Rust, both about libraries, but also how the language can help improve it. And the other is the Portable SIMD Project Group, which is working on how to get SIMD, the like, data parallel CPU operations, to work better in a portable way in Rust. And I think both of these groups are happy to get input and advice into their discussions. I think all their discussions are public. If you want to get involved and care about either of these use cases, then I highly recommend you do so. And we’ll link to those in the show notes as well.

Ben: Speaking of community, I know that— I think we mentioned last time, but also it’s worth reiterating, is that Jon has been expanding the role of our Rustacean Station Discord into a more broad direction beyond just podcasts. And now there are plenty of Rust streamers making their home there as well.

Jon: Yeah, that’s right. This came up because I noticed that, especially in the last maybe six months, maybe given the current world situation, it’s not that strange. A decent number of people have started to stream sort of technical content, whether it’s programming or just talking about Rust. And I figured that this is another great way to learn Rust beyond just, sort of, the written materials. And I figured that given that Rustacean Station is all about community content, that sort of teaching, learning content, exploring Rust, why limit ourselves only to the audible spectrum? Why not include the visual spectrum as well? And so now on the Rustacean Station Discord, you can find all these Rust streamers, you can chat to them. You can suggest ideas for streams you would like to see. And of course, you can also jump on there to just chat to Ben and I, and some of our great editors and other contributors.

Ben: If you wanted to help out, say—

Jon: For example…

Ben: If you wanted to edit this episode, which I mean, I guess it would be hard for you to edit it from hearing this specific request for help, considering that we’re currently making it and not—

Jon: A time traveler.

Ben: Who knows? Weirder things have happened in fiction.

Jon: That’s true. You are totally right.

I think that’s everything. I think we ran through 1.46 and 1.47. Good job, us!

Ben: We will see you again in 12 weeks. Three months.

Jon: Very, very exciting. And by then, hopefully in theory, I will have graduated, Ben.

Ben: Excellent. So you can spend full time on Rust at that point.

Jon: Yeah, very exciting.

Ben: No more of this silly waste of time on academics and personal achievement.

Jon: Yeah. I know, right?

Ben: Do you want to, even real quick— let’s say for you, let’s plug the thing you’ve been working on for your PhD, or whatever it is. It’s a project written in Rust.

Jon: It is. So over the past five years or so, I’ve been working on Noria, which is a database engine that’s written from scratch entirely in Rust. It’s like— I think it’s about 80,000 lines of Rust code, and the idea is basically that it builds a cache into the database. So rather than have to, like, write your application so that it talks to like MySQL, and then it also talks to Redis or Memcached or something. The database just does that for you, and makes all your queries fast, basically always. It is a research prototype, but it’s a really cool project. I’m very happy with it. I’m doing my defense in, like, a bit under two weeks. And I’m hoping to put the presentation of that work online. So maybe even by the time you listen to this, that video is online, you can see a bunch more about it there. But it’s a cool project. I encourage you to give it a look.

Ben: All right, so let’s say our farewells.

Jon: So long, Ben. See you in 12 weeks.

Ben: So long, farewell, auf wiedersehen goodbye—

Jon: Ah, beautiful. Bye, everyone.