LongCut logo

Crust of Rust: Functions, Closures, and Their Traits

By Jon Gjengset

Summary

## Key takeaways - **Function Items Are Zero-Sized**: A function item like `let x = bar;` is a zero-sized value carried only at compile time that uniquely references a specific function instance. It has zero bytes at runtime as shown by `std::mem::size_of_val(x)` outputting 0. [04:02], [06:12] - **Function Items Coerce to Pointers**: Function items coerce to function pointers, which are 8-byte values pointing to function code, enabling generic calls over matching signatures. This forces code generation even if not directly called. [06:27], [07:50] - **Fn Traits Form Ownership Hierarchy**: Fn takes &self (callable multiple times concurrently), FnMut takes &mut self (call once at a time), FnOnce takes self (call once). Function pointers implement all three since they have no state. [12:54], [18:07] - **Closures Infer Capture by Need**: Closures capture minimally: shared ref for reads (implements Fn), mutable ref for mutation (FnMut), ownership for moves (FnOnce). Capturing prevents coercion to function pointers. [23:29], [27:42] - **Move Forces Ownership Capture**: The `move` keyword forces moving variables into closures, making them FnOnce even if only borrowing was needed, useful for static lifetimes but moves everything captured. [30:47], [35:23] - **Dyn Fn Requires Matching Access**: Box<dyn Fn> works with shared refs, but dyn FnMut needs mutable access like &mut or Box; Arc<dyn Fn> only implements Fn due to shared-only access. [39:27], [46:28]

Topics Covered

  • Function Items Aren't Pointers
  • Fn Traits Form Ownership Hierarchy
  • Closures Capture Determines Fn Type
  • Move Keyword Forces Ownership Capture
  • Dyn Fn Requires Sized Locals

Full Transcript

Hello everyone and welcome back yet again to another Crust of Rust.

This time I wanted to tackle functions and closures and the function traits and function pointers because sort of like the situation is with strings in Rust, there are a lot of different function things or function-like things and the distinction is not always clear and I feel like...

it would be useful to just sort of get a survey of the scene.

What we're going to talk about today is like, not necessarily...

not necessarily...

a super large area.

We might be able to cover it in as little as an hour.

I don't know.

But I do think it's useful to have some information about the distinction between these different types and traits and how they interact, just because you're going to come across them a lot.

Like, especially things like being generic over functions is, I think, something you run into a lot in the Rust world where you want to sort of have these nice...

like callback functions, or you want to be able to write things in a slightly functional style, and then function pointers just come up everywhere.

And so I wanted to make sure that everyone sort of understands that fairly versatile primitive in Rust.

Before I start, I do want to say that I wrote a book.

It is called Rust for Rustations.

It is the It's intended to be basically a follow-up to the original Rust book, the Rust programming language by Stephen Carroll.

So this book sort of picks up where that book left off and tries to give an introduction to all of the slightly more intermediate, slightly more niche, slightly more...

slightly more...

I don't quite want to say advanced, although maybe that is an appropriate word too.

It tries to cover more of what you run into after you know Rust the language and you want to know how to use it better for particular use cases.

And it focuses on things like idiomatic usage, but also like how some of the more complex building blocks of the language work and how you can use them effectively.

The ebook version is now up on Nostarch Press.

And the print book, I think...

I think...

is like with the printers.

And I think the goal is like early November for it to actually get printed and shipped from those starch.

It will be get like distributed by, I think Penguin Random House.

And because it's like global shipping problems, it might take a little bit longer to get the book if you buy it through other retailers, but it will be available on like Amazon and stuff already as well.

Um, and yeah, it's a lot of what I talk about here is covered in that book too.

Uh, please get the book.

It's a good way to support me as well.

If, um, if that's your, your cup of tea.

Um, all right, now let's get into functions and.

closures and traits.

So when we talk about functions in Rust, generally what people think about and in fact, let's let's start a new lib over here or something.

Let's start a bin.

And we're going to call it We're going to call it closure.

See, it doesn't work.

I was thinking of closure in the sense of feeling like you get closure in something, but it has the same as the type, so it doesn't really work.

Let's go with call me.

And what we're going to do in this is Well, here we have a perfect example, right?

So fnMain is a function, perhaps unsurprisingly.

And fnMain specifically has a type.

Like if I in, let's say I go bar over here.

So if I in main set let x equals bar.

Okay, x now has a type.

What is that type?

Some of you might say, well, it's a function pointer.

It is in fact not in Rust.

In fact, if we try to look at the type of this, it sort of looks like it's a function pointer, but it's not actually.

What this is is a function item, which is subtly different.

Notice here, I'm not calling bar, I'm just taking the sort of identifier of bar.

And the type of x here is a function item, which is a zero-sized value that is only carried around at compile time that references the unique function bar.

And in fact, if bar was generic, then I wouldn't be allowed to do this because this does not uniquely identify a function.

I would have to do something like...

I 32, for example.

So this now specifically names bar instantiated with the type I 32 as T.

Um, but if I now said, uh, X equals bar U 32.

then you'll notice here I get a mismatch type error.

And that is because this is not a function pointer, right?

The signature of this function, right, is that it takes no arguments and return nothing.

So bar i32 and bar u32 are really the same in terms of if you thought of them as a function pointer, but that's not what x is here.

x here is a function item that's uniquely identifies the function bar that takes i32 as a t.

If you try to assign that bar that takes u32 as a t, they're not the same type.

And if I skip this and I try to do something like println mem sizeof x.

Sizeof...

I forget what it's called.

Is it sizeof val?

Sizeof val, yeah.

and CD call me and cargo run, you'll see that the size of this is zero, zero bytes.

So it doesn't hold a pointer at all, because this is just an identifier that the compiler uses to identify this unique instance of this function.

Now, function items have a coercion defined for them into function pointers.

So I can define a function baz.

that takes a function pointer, which is written like this.

Let's make this a little, just give it a better signature and then change bar to match.

So Baz here takes a function pointer.

That's what this signifies.

So here I'm saying I take a function pointer.

So an actual like a pointer sized value to any function that has the signature, one U32 in and one U32 out.

And I can call Baz with bar U32.

I can also call it with bar I 32.

In both cases, what happens here is that the compiler coerces the function item type into a function pointer type so that this function can be called.

So inside of here, if I now print size of val of F and run this, What?

Oh, zero.

That's fine.

You'll see that here, the size of the value is eight because it's an actual function pointer.

And that's necessary because these two functions are distinct from one another, right?

There are two instantiations of bar for different types, which means they have different code, right?

They've been optimized separately.

They're just, they are different chunks of code that happen to share sort of a generic name.

Um, but...

but...

we need to pass the pointer to the first instruction of one instance of that function when we call Baz the first time and a different function pointer the second time around.

But this does allow us to be generic, if you will, over which exact function is passed in as long as it has the same signature.

Right?

And...

And of course, if you passed in something that was the wrong signature, like if I did this, right, then now it wouldn't compile because it'll tell me it expected a function pointer to a function that had the signature u32 in, u32 out.

What it found was a function item that has a different signature, that is, it's a void function.

It takes no arguments and returns no value.

And crucially here, you see it doesn't coerce here because it doesn't match the required type.

But what we learned from this is that function items and function pointers are different from one another, but function items are coercible into a function pointer.

And part of the reason we need this coercion is because if I do this, if I didn't ever call this, right?

then now, even though I've named the function bar with T instantiated to I32, the compiler doesn't actually need to generate the code for that function.

It's under no obligation to because it's never called.

However, here, I'm calling Baz, right?

And so, and I'm passing in this course to pointer is that it needs to be able to produce a value that's a pointer to that function's body.

And therefore it has to generate the function so that it can generate that function pointer in the first place.

Of course, if the whole thing gets that code eliminated, then that won't happen.

But in general, this forces the compiler to actually have to generate this function body.

In general, of course, you'll do the same here.

Like, you're unlikely to just assign it and then leave it.

But regardless.

All right.

Video choppy this time.

What is going on?

Oh.

It's fine now.

Okay, it's fine now?

I'll keep going if it's fine now.

If it's not fine now, I'll stop.

What do you think?

Fine, not fine.

It's really weird that this is happening.

I did upgrade OBS.

So maybe it's OBS.

Um, that's very, very strange.

Fine on Twitch.

Okay.

Um, so the, the takeaway from this, right, is that a, uh, function item uniquely identifies a particular instance of a function.

Um, whereas a function pointer is a pointer to a function with a given signature and you can turn one into the other, but not go the other way around.

Um, And this, of course, would then work again.

All right, so that's function items and function pointers.

And then we have this thing called closures.

So you might have seen, isn't quacks is like the next meta-generic type, I think?

So this takes an f, where f, and let's start out with is just fn.

Okay, so quarks with bar U32.

What does this do?

So this also works.

And what we're saying here is that quacks is a function that is generic over some f, where f implements the fn trait.

And notice that fn with capital F is different from the lowercase fn that we use for a function pointer.

Although the construction is the same.

So if I wanted to say it takes a u32 and returns a u32, I do it in the same way.

but one is a trait bound and the other is a function pointer type.

So this is not a type, this is a trait.

And here there are three different traits.

So if we go back and look at the list of operations, there's Fn, FnMute and FnOnes.

And the distinction between these is the definition of the trait.

Specifically, Fn takes a shared reference to self.

fnMute, as its name implies, takes an exclusive reference to self.

And fnOnce takes an owned reference to self.

And the implication here, as the name implies for fnOnce, you can only call an fnOnce a single time.

And at that point, you've moved the value of the function that you wanted to call, and you can no longer call it again.

Um, FN mute, you ha you can only call once at a time.

Um, so you need a mutable reference to it in the first place.

And if you have one, you can call it multiple times, but you can only call it once at a time.

Um, and this is important.

For example, if you stick an FN mute in an RC, you wouldn't be able to call it.

Um, similarly, if you've given a shared reference to something that's FN mute, you also cannot call it.

This is also why in general, an FN mute, if you pass it to multiple threads, like through something like rayon, then you couldn't call an FN mute for multiple threads at the same time because it wouldn't be exclusive.

FN, on the other hand, is sort of the equivalent of having a shared reference in that you can call it multiple times and you can call it multiple times at the same time, or at least through a shared reference.

And the reason for this will become clear once we look at what closures are and what they do.

But let's first think specifically about what this means for function items and function pointers.

So a function pointer, and in fact, a function item, has no state, right?

They are standalone just chunks of code, and they don't reference anything in any other stack frames, right?

They don't reference any memory that's stored outside of themselves.

There's no lifetime associated with a function pointer or a function item is the other way to think about this.

And what that means is that they don't really care about self, right?

Like for them, all self contains is, well, nothing really for a function item.

And for a function pointer, self is just the pointer to the function.

But there's no state to really mutate there.

There's nothing to move.

And therefore, function items coerce to function pointers, and function pointers implement all three of these traits.

So if you have a function pointer, you can pass it to something that takes an fn once, you can pass it to something that takes an fn mute, and you can pass it to something that takes an fn.

The one way to think about these fn traits is that they are sort of a hierarchy where anything you can you can almost think of it as here, implement fn once or implement FN for F where F implements FN mute.

We wouldn't actually ever write this, but you can think about it this way.

And the reason you can think about it this way, and I'll show you in a second.

It takes some arguments.

I am lying, it should be this.

So, if we go back here to...

Yeah, so as you see in the documentation too, since both fn and fn mute are sub-traits of n1s, any instance of fn or fn mute can be used where fn1s is expected.

So in other words, fn implements fn1s.

So let's start from there and then we can...

And the reason for this is if it requires, if we're given an owned version of self, that is...

that is...

So far, we've only talked about function pointers, but if we're given an old owned reference to self, or an owned self rather, then we could trivially produce a reference to self.

So I can trivially then do the sort of Fn call of self.

Like I can easily translate one to the other.

Because if I have something that implements Fn and I'm given a self, I can just take a reference to it and pass that to the Fn.

And similarly, I can do the same thing for something like FnMute.

If I have an FnMute, I'm given a self, so I'm given ownership of it.

I can trivially just generate an exclusive reference to it.

And similarly...

Fn mute, I can easily implement for Fn, right?

So I'm given a mutable reference to self.

Well, I can just turn that into a shared reference and then call the FN version, right?

Because these are sort of strictly more powerful.

Does that make sense?

Like why these end up forming a hierarchy?

So anything that implements FN also implements FN mute and FN once.

Anything that implements FN mute implements FN once as well, but not FN.

Because you can't, if you're given a shared reference, you cannot produce an exclusive reference.

And anything that implements FN ones only implements FN ones.

All right.

Does that make sense so far?

Yeah, I don't know what's going on.

I think it's my internet that's being weird.

So we have this hierarchy, and I think it'll make a little bit more sense why this hierarchy is needed once we start looking at closures.

Because again, a function pointer implements all of these, because it doesn't actually care about the ownership of the function pointer, because it's just a pointer to code.

You can think of it as a function pointer implements fn, and therefore it also implements fn mute and fn once, by virtue of the same rules we just talked about.

um And so that's why here, when we require an F that implements FN, we can easily pass in a bar U32 because it courses to a function pointer, which implements FN.

And similarly, if I made this FN mute, this would still work.

And if I made an FN once, this would still work because function pointers implement all of them.

In terms of actually calling them though, you'll see that here, let's say that I wanted to actually call the F that I was given.

If I have an fnonce, then I need to take it by ownership.

If I did this, for example, reference to something that implements fnonce, then the compiler won't let me call it because fnonce requires ownership of the f.

It requires that I be able to move the f into the call, which it's telling me it can't do because it's behind an exclusive reference, right?

And now, similarly, if I get an FN, if I say that it's an FN mute, then now all I require is an exclusive reference, which I have, and therefore I can call it.

But if I got a shared reference, I couldn't call an FN mute because it can't be borrowed as mutable, which is required in order to call an FN mute.

But I could if it was FN, right?

And so then we've ended up going full circle here.

All right, so let's now go back to why are these different, like we already mentioned how function pointers implement all of these by virtue of implementing fn, so why do we need this distinction?

The distinction comes up once you start talking about closures.

So fundamentally a closure is written this way.

So you've almost certainly used closures.

If you haven't, this may not be the stream for you.

But basically it takes arguments which are passed in between these, and it has some body that returns the contents of those.

Let's say here we can annotate these with, it takes an I32 and an I32.

So this is a closure.

This happens to be a non-capturing closure.

So in general, closures are named closures because they close over their environment.

That is, they're able to capture things from their environment and generate a unique function, if you will, that specifically absorbs or uses or references data from the surrounding environment at the time when they're created.

In this case, it doesn't capture anything from the environment.

It only references its own arguments.

And therefore this is a non-capturing closure.

And what's interesting about non-capturing closures is that they are coercible to function pointers.

So I can actually call baz with f if it had the right signature, which we can make it do.

So see here, Baz takes a function pointer and closures that do not capture over their environment can be coerced to function pointers.

They can also be, they also implement the standard sort of function traits.

So they implement FN, for example, and because they implement FN, they also implement FN mute and FN once, and all the way up.

But if I have a closure that does capture from the environment, so let's say that I declare a z, which is a non-copy value.

So it's going to be a string new.

If it's copy, I'm going to hand wave a little bit and say that we don't want it to be copy because it makes this reasoning more complicated than it needs to be.

So here, I'm going to declare this closure as just consuming z.

And you'll notice that now it no longer can be passed to Baz.

And if we look at the compiler errors, you see that it says closures can only be coerced to FN types if they do not capture any variables.

And in this case, it does capture a variable, right?

It captures the Z variable from its own stack.

Now, let's say that all the closure did was like print line Z.

So it doesn't actually move z, it just takes a reference to z, but nonetheless it captures over the environment, and that means that you cannot represent this closure as just a function pointer.

Because in order to, like, this closure, think of it as the compiler generates a sort of anonymous struct for the closure that contains a...

fields for everything it captures from its environment.

Because when this closure runs, it needs access to that information.

So you can sort of think of this as it generates like a F closure struct.

that has a Z field, which is a string.

And crucially, right, this is an A or if you will, scope, which is a reference to the surrounding scope.

And then you can think of it as it implements FN for F closure.

And it can do that because if it's given a shared reference to self, then it can still call that closure in the sense of this implementation is gonna be sort of copy paste from closure definition, right?

It'll just turn that into self.z.

And that works just fine because this is just a shared reference anyway.

So you can't actually write this code, but you can think of this as what the compiler generates.

And therefore you see why it needs access to the Z.

The function pointer, which would just be a pointer to like the start of this code block, would not be sufficient.

It needs the additional state of this Z.

Does that make sense so far?

Okay, so now let's look at what happens if I make this mutable and I say that this is gonna z.clear.

So clear is a method on string that takes an exclusive reference to self and it clears the string.

It doesn't deallocate anything, but it clears the string.

So now the F closure that gets generated, the sort of state for the closure, has an exclusive reference to a string.

because that's what it needs to capture in order to call clear.

It can't capture a shared reference because this requires an exclusive reference.

And at this point, if we now again imagine that it sort of copy paste the body, this clear won't work, self.z.clear.

self.z.clear.

And the reason this isn't going to work is because the Z here requires an exclusive reference, but all we have is a shared reference to self, to the sort of closure state, if you will.

And so we can't actually get an exclusive reference through that.

Even though we have one here, this ends up being a...

shared reference to an exclusive reference to a string, which is only usable as a shared reference to a string.

And so this won't compile.

Or in other words, when you have a closure like this, it cannot implement fn.

It can implement fn mute.

Right?

Because here, we now have an exclusive reference to the closure state, which has an exclusive reference to the Z, and therefore we end up having an exclusive reference to the string, and this works just fine.

And again, anything that implements fn mute implements fn once, and so therefore this would also implement fn once.

And so this closure is an example of something that implements, let me comment this out just to avoid the compiler errors for a sec.

So you'll see that this doesn't course to a function pointer, right?

Anything that captures cannot be.

We try to call quacks with it, but it says the closure is FN mute because it mutates the variable Z here, right?

It expected a closure that implements the FN trait, but this closure only implements FN mute.

And that is indeed exactly what's going on as we just discussed.

It cannot implement FN because it needs to mutably borrow for a MITS environment.

Right?

If I made this take an FN mute, then I would need to take at least this or I could just change this to be owned.

Right?

Then now this.

And I don't pass in a shared reference to it.

So now F is just fine to pass to quacks because all it requires is an FN mute.

And this closure is FN mute.

So what then you might ask is, okay, what about this fn once?

Well, let's imagine that what we wanted to do in here was actually drop z.

So to drop z, we need to have ownership of z, which means that we need to move into the closure.

Right, so Z gets moved into the closure.

And that means that you just like obviously cannot call this closure more than once.

Because to call the closure, you move Z.

But if you tried to call the closure again, you would have to move Z again, but you can't move Z again because it's already been moved.

Or if we expressed it this way, like we've been talking about so far, this no longer has sort of a lifetime here.

It owns the Z string, right?

or you can think of it as it still has scope, but there's just no use for that scope.

If we try to implement fn mute and this tried to call drop self, it's not possible for us to drop self here because calling drop requires ownership of self, but all we have it as an exclusive reference, therefore this won't work.

We can however implement fn once because fn once is given ownership of self and therefore we can drop self.z.

Sorry, this should have been self.z all along.

So here we are allowed to because we get ownership of the closure state.

And so this implements fn once, but it cannot implement fn mute, and it cannot implement fn.

And you can see this also by, if I comment this out again, because you can't actually manually implement the fn traits at the moment, you'll see here that it says that we can't, this call to quacks is not valid because it expected a closure that implements the fn mute trait, but this only implements fn once, and it implements fn once because it moves the variable z out of its environment here.

So that's sort of the path that we go around here.

And you might've seen move closures.

So you can write the move keyword before the definition of a closure.

And what that means is, So closure capturing is actually a little complicated because here the compiler sort of knows that what the closure needs is an owned version of Z.

It needs to own Z and therefore it determines that it should move Z into the closure.

When we called Z.clear,

the compiler sort of automagically figures out that all we need is an exclusive reference to Z.

And therefore it only needs to...

move an exclusive reference into the closure and not all of z itself.

And similarly, when we had println of z, it realized that all you need here is a shared reference, and therefore it only moves a shared reference into the closure rather than all of z.

And that logic generally does what you want, but it's not perfect.

And there are some cases where you want to move a value into the closure, even though you don't technically need it.

An example of this could be if you want the closure to be the thing that drops the value.

That is, when the closure exits, you want the value to be dropped.

So currently the way this is organized is that Z, the string, will not be dropped until the end of this scope down here.

Right?

At the end of main.

Because it's not dropped in the closure.

If I write move here, what I'm telling the compiler is move Z into the closure.

And so now Z is dropped here.

We still only needed a shared reference, but we're telling the compiler, I want you to move into it here.

And now, because we're moving Z into it, we end up in the same state of, we actually need to own the Z, and so we can only implement FN once, we cannot implement FN mute or FN.

Does that make sense?

Actually, I don't know if, why does this compile?

Ooh, maybe I lied to you.

I think I lied to you.

I think the move, it'll still apply this heuristic.

Yeah, you're right.

The reasoning for this is actually slightly different, which is, well, it is true that you can make a drop in here, but it's also because if I do this, the lifetime of the closure is tied to the stack here.

So maybe if I give a slightly different example.

So makefn is gonna return an imple fn once.

Sure, why not?

And it does z equals string new.

And then it's gonna return a closure that is gonna print line z.

Right, so here is an example of I want to return a function, right?

I'm not going to tell the caller whether it's a closure or not, but I'm going to return something that's callable, and it's only callable once.

And, or in fact, let's say fn.

I want to return something that's callable, and I want it to be callable multiple times, which this one is, right?

It's just printing z over and over.

The problem is that right now, this borrows z.

And so the closure that's returned sort of has a lifetime that's associated with the sort of self of this function.

This you can sort of read as Z is like a reference to Z that's moved into here.

And this reference has some lifetime, which means that this closure type really has that same lifetime.

but if you don't specify a lifetime, it assumes that it's static.

And so here we're promising to give back something static, but the closure that we return isn't static because it references this Z.

And so there you can apply the move to say, move Z into the closure.

And now this closure is FN because every call to the closure does just references the string that's stored in the closure self contents.

And that's fine, right?

You can have multiple calls that all get a shared reference to the closure state, which gives a shared reference to the string for printing.

If it would not work, if this tried to say drop Z, because it wouldn't implement FN.

Because once you've called it once, you've now moved out of self, and so you cannot call it again.

And so this is where move is useful.

Now, one downside of move is that move means move everything.

There's no move, like let's say we had x as well.

this will move both X and Z into the closure.

And that is oftentimes what you want, right?

And here we wanted to return something that was static, but that's not always what you want.

But the way that you work around this is you either don't use move, right?

If you can get away with it, or you use move, and if there's something you specifically want only to be borrowed, you do like let X two equals, reference X and use X2 in here.

Now this won't work in this case because this makes it no longer static, but that's the way that you can move some things by reference and some things by ownership.

And there are many ways to express this, like sometimes use shadowing if you don't care about the old value.

You could introduce a new scope so that you don't have to deal with this later.

Regardless of how you end up doing it, this is the way that you would move some things but borrow other things into the closure.

Does that code create a new string in static every time makefn is called?

Yes.

So the way we had this, if I go back here and remove z and make this pub, I guess, this every time you call makefn, it's going to allocate a new string and then return a closure that owns that string that was allocated.

You can't choose per variable which to move or not move.

Move is all or nothing.

So that's why you end up with a pattern like this, for example, to specifically say the thing that I want to move in here, the X is actually a reference to the real X.

So I want to move the reference.

And of course, if you do move something into a closure, then now the closure, as we saw up here, right?

The closures...

collectively, or the closure owns the string.

And so it's only when then any call to that closure uses that same string.

And it's when that closure is eventually dropped, like the actual variable that holds the closure or the allocation that holds the closure, when that gets dropped, the state structure for that closure gets dropped and therefore the string also gets dropped.

All right, so we've now actually talked most of what's important to grasp about these function types.

But there are a couple of more things I want to touch on.

The first of these is around dinfn.

So...

Tin FN.

Like so.

So just like with other traits, you can also use the fn traits through din to get dynamic dispatch.

And you specify the full function signature and you just put din in front of it.

And this works just fine.

In fact, here I can just call f.

And similarly, if this was a din fn mute, I can call it as long as I take this as mute.

Right?

So that works fine.

And if it's an FN once, I can also still call it.

So, so far, so good.

This was not always the case, interestingly.

It used to be that there was this special FN box trait.

And the reason for this was that...

This is a little bit of an anecdote, but it is an interesting one, I think.

It used to be that box didn't FN once.

And in fact, box didn't FN anything, did not implement FN anything.

So basically, it used to be that box didn't FN, did not implement FN.

And similarly for FN mute and FN once.

And the reason for this actually is kind of interesting.

So let's imagine that we were the people trying to implement this, because I do think this gives a sort of interesting insight.

Let's imagine that we are the standard library and we want to implement, let's say, FN for a box din FN.

So we have to implement call, we take no arguments, we return nothing.

And now the question is, what do we put in here?

right?

So self is a box.

But we have this challenge here of Like, let's say I do self.0.

Let's say that's how I get at what's inside the box.

And I want to do call with no arguments.

So it seems like this is all I should really need to write.

Sort of dereference the box and then call the thing that's inside.

The problem is, what is the type of self.0 here?

If we were to write this out as a variable, right?

And let's take fn once just because it's more clear what's going on.

So I move out of self.0 to get at the thing that's inside the box.

And then I do x.call.

What's the type of x?

The type of x here is din fn once.

But this type is not sized.

So how much space does x take up on the stack here?

Remember, din in general is unsized, right?

It's not sized.

And that's why in general for box, you always need either a reference or an exclusive reference or something like a box around it.

You can't have a freestanding din because it's not sized.

So the compiler wouldn't know how to range the stack frame for this method call.

And so it's kind of interesting if you look back sort of historically, and this is going to be bright for a second.

I'm sorry about that.

Um, so here in the release announcements for Rust 1.35, you see it was like a big thing that now these traits are implemented for the appropriate box types.

Um, and previously there was this fnbox that got to do special compiler magic.

Um, and as you see here this sentence, this was ultimately due to a limitation on the compiler's ability to reason about such implementations, which has since been fixed with the introduction of unsized locals.

So someone said, why couldn't you take a reference to the box contents?

Well, this is why I chose FN1s, because here, to call it, you need an owned self.

So you can't take a reference to this, because if you had a this, you couldn't call it, because FN1s requires ownership of self.

And slightly similar issues arise with fnMute.

And so there is actually an RFC specifically for unsized R values, which is required for this implementation to exist.

And this RFC, The RFC has landed, but there's lots of implementation questions.

And basically, the compiler gets to take advantage of this particular feature, but it's unstable.

So you can opt into it on nightly, but you can't generally use this on your own code on stable.

But it is required for this particular thing to work, which I thought is an interesting tidbit.

And there's all sorts of cool implications of what we could do if we got unsized R values, but that's neither here nor there.

So when you have boxed in and then some FN trait, that just works now.

You don't really have to treat it specially, like in general, din just works, but you do have to keep in mind that if you get something like a, how am I gonna demonstrate this best?

Um, so this implements FN.

So let's say I here gave a din F.

Uh, uh, uh, uh, uh, uh, uh, uh, uh, uh, uh, uh, uh, uh, So this works just fine.

I turn f into a dynamically dispatched fn.

This is sort of like a function pointer, except it's allowed to So it has a self.

Basically it constructs a cell for this closure and then uses that as the data pointer of the dynamic dispatch.

And the V table just has the call method.

So that's so far as fine.

The challenge here, right, is let's say that I tried to make this FN mute.

So now I have a din FN mute, but all I have is a shared reference to it.

And therefore I can't actually call it because this doesn't implement FN mute.

This only implements FN because all you have is a shared reference.

So when you use...

dynamically dispatched function traits, you need to make sure that the sort of the wrapper, the indirection type, the wide pointer type that you use allows the kind of access that you actually need in order to call that function.

Right?

So in order to call an FN mute, you need to stick it behind a shared reference.

In order to call an FN, all you need is a shared reference.

In order to call an FN once, you actually need a wide pointer type that allows you to take ownership.

Right?

So I mean, I would have to box new.

And of course, again, the same hierarchy applies.

So if you have a wide pointer type that allows you to take ownership, then it will also work with any of the sort of weaker function or less restrictive function traits.

So I can have a box, din, fn, mute.

Because if I can take ownership, then I can certainly get an exclusive reference.

If I can take ownership, then I can definitely get a shared reference.

So these are both fine.

And we'll implement the appropriate trait outside of the wide pointer.

And if you think of something like Arc, right, so standard sync Arc, standard sync Arc will allow you to...

to stick unsized things into it, but an arc by necessity will only give you shared access to the thing that's inside, and therefore arc din of fn implements fn, but arc din fn mute still only implements fn, or in fact, then can't implement the trait rather.

So here, if I say this takes fn, ooh.

Maybe they haven't implemented this for Arc.

Maybe they've only implemented it for Box.

Interesting.

So I'm guessing then that this implementation doesn't actually exist for arc yet.

If we go back to look at fn, for example, you see there's an implementation of fn for box of f where f implements fn, but there is no implementation of arc.

I wonder why that is.

That implementation should exist.

Interesting.

And we can use the intuition we've built up so far for why that should be the case.

An arc supports unsized values.

Basically, arc can support being a wide pointer.

And therefore, it can hold the DIN FN.

And if it can hold the DIN FN and it's able to give you a shared reference to the closure state, then it should be able to implement FN because all that requires is being able to get a shared reference to the closure state.

So this suggests that there's sort of an implementation missing here.

And it might be because of this issue we were looking at about unsized R values.

Maybe it's like specialized to box somehow.

But that would be an interesting implementation to add.

That's pretty good.

Okay, so about an hour.

I think I guessed about right.

The last thing I wanted to touch on was const fn.

So what's a good example of this?

Let's try to erase some of this stuff.

Okay, so let's say I define a closure that takes that just returns zero.

Right, so this closure is a constant closure.

You could evaluate this closure at compile time, right?

This is sort of equivalent to const fn, like make zero that returns a use size, or I guess I32 is the default.

Right?

These are both the same.

in the sense that they're both callable at compile time.

They're basically both const.

But the question becomes, let's say that I want a const fn, I don't know, foo, and I wanted to take an f, that's let's say fn once, and I want my const fn to be able to call f.

Well, that's not currently okay, because the compiler doesn't know that f is callable as a const.

Right?

Because we've just said this is any type that implements FN1s, and we don't know that the implementation of FN1s is actually const.

So there's nothing in stable that you can do about this today.

But there's a sort of interesting pre-RFC discussion that's going on and has been going on for a while about, is there a way to say, I want to be generic over any type that implements this trait, but only using things that are const.

It's evaluatable.

And the syntax I've come up with is this.

And let's see if I can opt into this easily.

I forget what the actual thing is called.

Ooh.

It might not be named anywhere that I can easily get at.

What happens if I run cargo r?

Let me just pull this up real quick to see what the...

But what is the name?

FeatureDeconstrateImpl.

So we're going to add this here.

And then we're going to be rust up override set nightly.

And we're going to cargo R and see what happens.

Ooh.

Yeah, there's here.

There's the second one you need to add, which is this one.

So again, as you can see, this is very experimental.

Okay, so what this signifier means, this tilde const, is that it doesn't actually mean this f and ones or this f must be const.

It doesn't mean that I will only take types that have a constant implementation of f and ones.

That's what this would mean, right?

This would mean f must have a constant implementation of fn ones.

It's also not question mark, like you might think of like question mark sized.

It's not really question mark const because it's not saying may or may not be const, which is what question mark sized means.

Rather, what the tilde here signifies is foo will be const if f is const.

If f is not const, then fn is not const.

And we haven't talked too much about const on stream, but the basic premise here is if you have a const fn, it's callable at compile time or at runtime.

Like you can call it either of them, which sort of means that a const fn is also an fn.

And what this is saying here is that this const fn is only const, so it's only callable at compile time if its generic parameter is also const in its implementation of fn once.

And so that's why this is like new sigil here, because it doesn't really mean the same as the other sigils that we have.

So that's why, I mean, this is not a full RFC yet.

It hasn't been merged.

So this is very, very experimental, as you can see with like the two features we need to opt into.

But it is an interesting thing to keep in mind and something you will see going forward as we start seeing more and more constification of Rust code.

Um, uh, food calling X.

Oh, sorry.

Yeah.

So if food calls X here, um, what am I missing?

Uh, expected zero arguments.

I think this is just Rust Analyzer being confused because it doesn't know about these features.

Right, so this runs just fine.

Ooh, why?

Oh, because string new is const.

If I said string from, foo, this is not const.

Oh, a string from const2.

What is not const?

vec 123?

What is not const?

Oh, sorry.

It's fine.

Okay, so, right.

So this is what I was, yeah.

This is what I was getting at.

Let's do const fn test foo.

That's what I need to do.

The problem here, or the reason why I have to do this, is because main isn't const.

So there's no requirement that foo is const.

So I think this can go back to a string from foo now.

String new is const.

So in main, I'm allowed to call foo because I don't require foo to be const.

I can call it even if it's not const.

And the closure here, so the implementation of fnones is not const, and therefore foo sort of genericized over this closure is not const, but that's fine for main.

So if I comment out this for a second, you'll see that this runs just fine.

But in a const fn, In a constfn, you can only call things that are themselves const.

So here, if I try to run it, you'll see that it complains.

Expected an FN1's closure found this closure.

And here you'll see the errors aren't very good, and that's because the error reporting doesn't know about this const flag yet.

But the actual complaint here is that...

constfns require that everything they call is also const, so this requires that foo, when generic over this closure, is const, but this closure's implementation of fn once is not const, therefore foo is not const, and therefore this call is illegal.

If, on the other hand, I use string new here, which is const, oh, it's maybe not const.

Oh, interesting.

That's interesting.

Does it work if I do this?

Oh, weird.

Yeah, I think this is evidence of this being a very experimental feature.

But the intention is that if the closure is itself constant, or constant evaluatable, which string new is because I believe string new is a const fn, then foo should also be const fn, and therefore should be callable from const context.

constfn are stabilized, but this kind of bound is not stabilized.

And so error reporting doesn't know about this kind of bound.

And so it doesn't really talk about it in the error message you get.

It does know about constfn more generally.

All right, I think that's all I wanted to cover about functions and closures and the traits and types that are involved.

Are there questions about any of the stuff that we've talked about so far?

Like any of the bits that you want me to go over again?

And I'm sad about the audio issues, but hopefully at least in the video on demand, I'll chop it up a little.

No?

People seem to be happy?

You've generally followed?

Any questions about anything else?

Now that we ended up with sort of a relatively short stream, which was kind of as expected, anything that I can answer outside of this particular topic?

Sometimes you end up with complicated lifetime bounds with 4r.

So let's get rid of the const of n stuff for a second.

Yeah, okay.

So an example of this is if I write a f and it takes an f.

And I want to say that f is a function that, let's imagine that it's a map function.

So it's going to be given a sort of, I don't know, it's going to be given a reference to a stir and it has to return a stir.

So let's imagine that I want to write a function like this, which maps a string.

And I want to try to call quacks with a closure that takes X and returns X.

So far so good.

This is not a problem.

This works just as expected.

But there are cases where this gets a little bit more complicated.

In particular, remember in bounds, you usually, although not always, but you usually have to specify lifetimes.

What are the lifetimes here?

Whenever something returns, you can often omit the lifetimes, but if we were to try to specify what the lifetimes are here, what are they?

Because there's no lifetime here.

If we tried to fill in the lifetime for this trait bound, what actually is it?

There's no tick A because we can't assign a tick A here.

What we're really saying is we want F to the lifetime.

sort of reuse the lifetime that it gets in in its output.

We want to say that it's allowed to reference the same thing that was referenced in its input.

And this is where you get this special for syntax.

And what this syntax means is the actual desugaring of that syntax.

This you can read as for any lifetime tick A, F is implementation of a function from a str reference with a lifetime of A to another str reference with the same lifetime of A.

So it's not actually that complicated.

It's a way to say that.

This needs to hold for any lifetime.

This is what the bound should be.

It's very, very rare that you actually need to give a for bound like this.

It does sometimes happen if you have trait bounds that have lifetimes but are not FNs.

The compiler is pretty good about inferring it for anything that is of Fn type or Fn mute or Fn once.

It can usually figure this out.

But once you start having other traits that aren't Fn in here, you sometimes need to reach for four.

But it should be very, very rare.

Will you be doing another Q&A session sometime?

I will, but I don't quite know yet.

I really want to get back to the hazard pointers library, so I'm trying to find time for that too.

How much will I enjoy Rust for Rustations?

Over 9,000.

How do you broadcast both video and your screen at the same time?

What software are you using?

I'm using OBS. Very happy with OBS.

OBS. Very happy with OBS.

If I want to pass a closure to an async function, the closure needs to be static, right?

How does this kind of closure capture its environment?

If you want to pass a closure to an async fn, you can just do so.

There's nothing preventing you from taking any fn.

It doesn't need to be static.

It's more that usually with, usually with futures, especially if you want to do something like Tokyo spawn, of the future you get back, then Tokyo spawn, just like thread spawn, requires that the argument is like static.

And if F here is not static, then the return future will also not be static, right?

If we sort of think of the de-sugaring of this, right?

It's FN this to imple future.

That's really the de-sugaring of, of this.

And imple trait, just like asyncfn, automatically captures the lifetime of its inputs.

So if this input is tied to some lifetime, then the output type will also be tied to that same lifetime, which means it will not be static unless the input is static.

And so that's why you often end up having to add static to generic types that you pass into async functions.

It's not because they're required.

Like if I...

If I just directly awaited here, then there's no need for the function to be static, the future that's returned to be static.

It only comes up if you try to do something like spawning where the future needs to live longer than the current stack frame.

You often need to pin it.

I mean, you should very rarely need to pin things manually.

In general, await syntax should take care of it.

All right, in that case, I think we're going to end things there.

Thanks, everyone, for watching.

I hope you learned something.

Go teach someone else what you learned, and I will see you all in a few weeks.

I really want to do more hazard pointers, but I just need to actually find the time to do six hours of coding.

All right, bye, everyone.

Hope you enjoyed it.

Loading...

Loading video analysis...