Functions, Closures, and Their Traits

Introduction to Functions and Closures

Section Overview: In this section, the speaker introduces the topic of functions and closures in Rust. They explain that there are many different types of function-like things in Rust, and it can be challenging to distinguish between them. The speaker also mentions their book “Rust for Rustaceans,” which covers intermediate-level topics related to Rust.

Types of Functions in Rust

  • The speaker explains that being generic over functions is common in Rust, especially when working with callback functions or writing code in a functional style.
  • Function pointers are a versatile primitive in Rust that come up frequently.
  • The type of fn main is a function item, which is subtly different from a function pointer. A function item is a zero-sized value that references the unique function at compile time.

Understanding Function Items

  • When declaring a variable like x = bar, where bar is a function, the resulting type of x is not actually a function pointer but rather a function item.
  • A function item is carried around at compile time and references the unique function it represents. If the referenced function was generic, then using its identifier alone would not uniquely identify it.
  • While two different instantiations of the same generic function may have identical signatures if thought of as a function pointer, they are not interchangeable when used as arguments because they reference different unique functions.

Function Items, Function Pointers, and Closures

Section Overview: This section covers the differences between function items and function pointers, how they can be coerced into each other, and introduces closures.

Function Items vs. Function Pointers

  • A function item uniquely identifies a particular instance of a function.
  • A function pointer is a pointer to a function with a given signature.
  • Function items are coercible into a function pointer.
  • Passing in different types to the same named function generates different chunks of code that share the same name but have distinct bodies.

Coercion of Function Items into Function Pointers

  • The compiler coerces the function item type into a function pointer type so that this function can be called.
  • When calling baz with bar, the compiler coerces the bar function item type into a u32 input and output signature for use as a parameter in baz.
  • If you never call an instantiated named function like bar, then it’s possible that its code will not be generated by the compiler.

Closures

  • Closures are functions that capture their environment.

Understanding the fn Traits

Section Overview: This section explains the three different traits in Rust that are used to define functions: fn, fn mut, and fn once.

The Three Different Traits

  • fn takes a shared reference to self.
  • fn mut takes an exclusive reference to self. You can only call it once at a time, and you need a mutable reference to it in the first place.
  • fn once takes an owned reference to self. You can only call it once, and at that point, you’ve moved the value of the function that you wanted to call.

Implications of Each Trait

  • If you stick an fn mut in an RC, you wouldn’t be able to call it. Similarly, if you’re given a shared reference to something that’s fn mut, you also cannot call it.
  • On the other hand, with fn, you can call it multiple times through a shared reference.
  • Anything that implements fn also implements both f and mute and fn once. However, anything that implements only one of these two does not implement all three.

Function Pointers

  • Function pointers have no state or lifetime associated with them. They don’t care about “self” because they don’t really contain anything related to “self”.
  • Function pointers implement all three traits: fn, f and mute, and fn once.

Function Pointers and Closures

Section Overview: This section discusses the hierarchy of function pointers and closures in Rust.

Hierarchy of Function Pointers

  • A function pointer implements fn and therefore also implements f, &fn, &mut fn, and *const fn.
  • If a function requires an f that implements fn, a bar u32 can be passed in because it coerces to a function pointer which implements fn.
  • If the required function is an f and mute, this would still work. Similarly, if it was an f and once, this would still work.
  • If the given function is an fn once, it needs to be taken by ownership to call it.

Closures

  • A closure takes arguments passed between these brackets {} and has some body that returns the contents of those arguments.
  • Non-capturing closures are coercible to function pointers.
  • Closures that capture from the environment cannot be passed as a parameter to functions that require only a function pointer.
  • The compiler generates an anonymous struct for closures that capture over their environment.

Rust Closures and Trait Bounds

Section Overview: In this section, the speaker discusses how Rust closures work and their trait bounds.

Implementing fn for f closure

  • A closure can implement fn if it is given a shared reference to self.
  • The implementation will be a copy-paste from the closure definition.
  • The function pointer alone would not be sufficient; it needs access to the additional state of z.

Implementing fn mute for f closure

  • If the closure is mutable, then it cannot implement fn.
  • This is because the exclusive reference required by clear() method cannot be captured as a shared reference.
  • Instead, it can implement fn mute, which requires an exclusive reference to the closure state.

Implementing fn once for f closure

  • A mutable closure that takes ownership of its environment can implement fn once.
  • However, calling the closure more than once is not possible since you cannot move z again after moving it into the closure.

Closure Capturing

Section Overview: In this section, the speaker discusses closure capturing and how it works in Rust.

Move Keyword

  • The move keyword can be used before the definition of a closure to tell the compiler to move a value into the closure.
  • The compiler determines whether to move an owned or shared reference based on what is needed by the closure.
  • There are cases where you want to move a value into the closure even though you don’t technically need it. For example, if you want the closure to drop a value when it exits.
  • Using move tells the compiler to move a value into the closure, which means that we actually need to own that value.

Lifetime of Closures

  • If we specify move for a variable inside a function, its lifetime will be tied to that function’s stack frame.
  • If we return a closure from a function without specifying its lifetime, Rust assumes that it has static lifetime. However, this may not always be true since closures can reference variables with non-static lifetimes.
  • Using move allows us to specify that we want to move a variable into the returned closure.

Downsides of Move

  • Using move moves everything specified after it into the closure.

Moving and Borrowing in Closures

Section Overview: This section covers how to move some things but borrow other things into closures.

Moving and Borrowing

  • There are many ways to express moving and borrowing, such as shadowing or introducing a new scope.
  • When using closures, you can only choose to move all or nothing.
  • To specifically move a reference, use a pattern like x which is actually a reference to the real x.
  • When something is moved into a closure, the closure owns it collectively. The string will be dropped when the closure is eventually dropped.

Using Dynamic Dispatch with Function Traits

Section Overview: This section covers how to use dynamic dispatch with function traits.

Dynamic Dispatch

  • You can use function traits through din for dynamic dispatch by specifying the full function signature and putting in in front of it.
  • If it’s an f and once, you can still call it as long as you take this as mute.
  • Box didn’t fn anything did not implement fn because din in general is unsized.

Implementing Call for Box Din Fn

  • Previously, box din fn did not implement fn due to limitations on the compiler’s ability to reason about such implementations.
  • 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 um.

Dynamically Dispatched Function Traits

Section Overview: This section discusses dynamically dispatched function traits and the challenges that arise when trying to call an fn mute using a shared reference.

Unsized R Values

  • An RFC exists for unsized r values, which is required for this implementation to exist.
  • The RFC has landed, but there are still many implementation questions.
  • The feature is unstable and can only be used on nightly.

Dynamically Dispatched Function Traits

  • When you have Box<dyn F> and some F trait, it just works without needing special treatment.
  • However, if you get something like a dyn F + Mute, all you have is a shared reference to it and therefore cannot call it because it does not implement F + Mute.
  • 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.

Arc Din Fn Implementation

  • There is no implementation of arc din fn yet, even though the intuition built up so far suggests that it should exist.
  • An arc supports unsized values and can hold the din fn. If it’s able to give you a shared reference to the closure state, then it should be implemented as fn because all that requires is being able to get a shared reference to the closure state.
  • There might be an implementation missing due to issues with unsized r values or maybe specialized for box.

Const Generics

Section Overview: In this section, the speaker discusses const generics and how they can be used to make generic types that are only usable with const evaluatable functions.

Defining a Closure that Returns Zero

  • The speaker defines a closure that returns zero.
  • This closure is a constant closure that can be evaluated at compile time.
  • The speaker explains that this closure is equivalent to make_zero which returns a u32.

Using Const Evaluatable Functions in Generic Types

  • The speaker wants to define a generic type foo that takes an fn once.
  • However, the compiler does not know if the function is callable as a const.
  • There is currently no way to take any type that implements a trait but only using things that are const evaluatable.
  • There is an interesting pre-RFC discussion about whether there is a way to do this.
  • The speaker proposes syntax for opting into this feature.

Opting into Experimental Features

  • The speaker adds experimental features and runs cargo r to see what happens.
  • Rust analyzer gets confused because it doesn’t know about these features.

Making Foo Callable with X

  • If foo calls x, rust analyzer throws an error because main isn’t const.

Const Functions and Closures

Section Overview: In this section, the speaker discusses const functions and closures in Rust. They explain how const functions work, what can be called within them, and how closures can be used with const functions.

Const Functions

  • In a const fn of n, only things that are themselves constant can be called.
  • Const functions require that everything they call is also const.
  • If the closure is itself constant or constant evaluatable, then it should also be callable from cons context.

Error Reporting

  • The error reporting doesn’t know about the const flag yet.
  • This kind of bound is not stabilized, so error reporting doesn’t know about it.

Complicated Lifetime Bounds

  • Sometimes you end up with complicated lifetime bounds with 4r.
  • You usually have to specify lifetimes when something returns. If we were to try to specify what the lifetimes are here, what actually is it? This is where you get this special for syntax.

Rare Use of For Bound

  • for bound should hold for any lifetime. It’s very rare that you actually need to give a for bound like this.

Passing a Closure to an Async Function

Section Overview: In this section, the speaker discusses passing closures to async functions and how it relates to static closures.

Closure Capture and Environment

  • When passing a closure to an async function, the closure does not need to be static.
  • Usually with futures, especially if you want to do something like Tokio spawn of the future you get back then Tokio spawn just like thread spawn requires that the argument is like static.
  • 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.

Input Lifetime and Output Type

  • The desugaring of impul future captures lifetime inputs automatically.
  • Impul trait just like async fn 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.
  • You often need to pin things manually in general a weight syntax should take care of you.

Spawning Futures

Section Overview: In this section, the speaker discusses spawning futures and when it’s necessary for them to live longer than their current stack frame.

Pinning Futures

  • If you try to do something like spawning where the future needs to live longer than the current stack frame um you often need to pin it i mean you should very rarely need to pin things manually in general a weight syntax should take care of you.

Conclusion

Section Overview: In this section, the speaker concludes the video and thanks the audience for watching.

Final Remarks

  • The speaker thanks everyone for watching and encourages them to teach someone else what they learned.
  • The speaker expresses interest in doing more hazard pointers but needs to find time to do six hours of coding.

Generated by Video Highlight

https://videohighlight.com/video/summary/dHkzSZnYXmk