Closure != Function pointer

In Rust's rich landscape of features, function pointers and closures offer powerful ways to treat functions as values. While both allow you to pass functions around, they differ significantly in their capabilities. This article dives deep into the distinctions between these concepts, exploring their definitions, creation, usage, and when to choose one over the other.

Function Pointers: Pointing to Existing Code

  1. Type: Function pointers are a type that represents a pointer to a function.
  2. Syntax: They are written as fn() followed by an optional list of input parameters and a return type.
  3. Definition: A function pointer is a variable that stores the memory location of a function. It essentially acts as a reference to the function's code.
  4. Creation: Directly from existing functions by assigning the function name to a variable of the appropriate function pointer type. For example:
  5. Usage: They are commonly used when you need to pass a function as an argument to another function or store it in a data structure.

fn add(a: i32, b: i32) -> i32 {
    a + b
}

//* From non-capturing closures (closures that don't borrow from their environment).

fn main() {
   let a = 5;
   let b = 10;
   let add_ptr: fn(i32, i32) -> i32 = add; // function pointer
   println!("add({},{}) = {}",a,b,add_ptr(a,b)); // Fun called with function pointer varaible 
}        

  • Flexibility: Is Limited. They can't capture state or access variables from their surrounding scope. This means they work best with pre-defined functions with fixed logic.
  • Safety: Can be safe or unsafe depending on whether they point to safe or unsafe functions.

Use Cases of Function Pointers

  1. Callback Mechanism: Function pointers are commonly used in callback mechanisms where a function is passed as an argument to another function, such as event handling or sorting algorithms.
  2. Dynamic Dispatch: They are also useful in scenarios where you want to select a function to call at runtime based on certain conditions, enabling dynamic dispatch.
  3. Function Wrappers: Function pointers can be used to create wrappers around functions to modify or extend their behavior.


Closures: Anonymous Functions with a Twist

  • Type: Closures are anonymous functions that can capture variables from their enclosing scope.
  • Syntax: They are created using the |args| body syntax, similar to lambda expressions in other languages.
  • Creation: Defined using curly braces {} with a capture clause (if needed) to specify the variables to borrow from the environment. For example:
  • Usage: They are useful when you need to create a function on the fly or when you want to capture variables from the surrounding scope.Definition: Closures are anonymous functions that can "close over" their environment. This means they can capture variables from the scope where they are defined and access them even after the closure is defined.

fn main() {
    let add_closure = |a, b| a + b;
    let result = add_closure(3, 5);
    println!("result = {}",result);
}        

Use Cases of Closures

  1. Conciseness: Closures are useful for writing concise code, especially in situations where a small function is needed only once and defining a separate named function would be cumbersome.
  2. Capturing Variables: Closures can capture variables from their surrounding scope, making them suitable for scenarios where you need to carry state along with the function.
  3. Iterator Adapters: Closures are commonly used with iterator adapters in Rust's standard library, allowing for expressive and efficient data processing pipelines.


Both closures and function pointers can be used interchangeably:

In Rust, both closures and function pointers can be used interchangeably in situations where a function-like entity is expected. This is because closures and function pointers share some common traits:

  1. Similar Function Signatures: Both closures and functions have similar function signatures, specifying the types of their arguments and return values. This allows Rust to treat them similarly in many contexts.
  2. Callable: Both closures and functions are callable, meaning they can be invoked with appropriate arguments.
  3. Polymorphism: Rust supports polymorphism over closures and function pointers, allowing them to be used interchangeably in many situations.

Now, let's analyze how the operation function demonstrates this interchangeability:

fn add(a: i32, b: i32) -> i32 {
    a + b
}

fn sub(a: i32, b: i32) -> i32 {
    a - b
}

fn mul(a: i32, b: i32) -> i32 {
    a * b
}

fn operation(fptr: fn(i32, i32) -> i32, a: i32, b: i32) -> i32 {
    fptr(a, b)
}

fn main() {
    let a = 5;
    let b = 10;

    // Using function pointers
    println!("with function pointer add({}, {}) = {}", a, b, operation(add, a, b));
    println!("with function pointer sub({}, {}) = {}", a, b, operation(sub, a, b));
    println!("with function pointer mul({}, {}) = {}", a, b, operation(mul, a, b));

    println!("***************************************");

    // Using closures
    println!("with closure add({}, {}) = {}", a, b, operation(|a, b| a + b, a, b));
    println!("with closure sub({}, {}) = {}", a, b, operation(|a, b| a - b, a, b));
    println!("with closure mul({}, {}) = {}", a, b, operation(|a, b| a * b, a, b));
}        


Differences between function pointer and closure:

  1. Capture: Closures can capture variables from their enclosing scope, while function pointers cannot.
  2. Size: Closures are typically larger in size because they capture variables from their environment, whereas function pointers are just pointers.
  3. Flexibility: Closures are more flexible in terms of usage because they can capture variables, whereas function pointers are more rigid.
  4. Type Inference: Closures' types are inferred by the compiler, while function pointers' types are explicit.

Things to note down w.r.t Memory:

Function Pointers:

Function pointers in Rust are essentially pointers to functions. They hold the memory address of the function they point to. When you create a function pointer, it occupies a fixed size of memory, typically the size of a pointer on the platform (e.g., 8 bytes on a 64-bit system). This memory holds the address of the function in the program's memory space.

For example, if we have a function pointer add_ptr pointing to the add function:

let add_ptr: fn(i32, i32) -> i32 = add;        

at the memory level, add_ptr would store the memory address of the add function.

Closures:

Memory Allocation in Closures in Rust is a bit more complex.

Closures in Rust can be handled in two ways depending on whether they capture variables from their environment:

  • Non-capturing closures: These closures behave similarly to function pointers. They don't capture any variables and simply hold the function's code address. Their memory allocation is identical to function pointers - stored on the stack.
  • Capturing closures: These closures capture variables from their surrounding scope. The captured variables are allocated on the heap (a managed memory region) along with the closure itself. This ensures the captured variables are still accessible even after the closure's defining scope goes out of scope.

For example, if we have a closure capturing variables x and y:

let closure = |x, y| x + y;        

At the memory level, closure would consist of a struct containing a function pointer to the code implementing the closure's behavior and space for the captured variables x and y. This struct would be allocated on the stack at the point where the closure is created.

Key Points:

  • Function pointers and non-capturing closures have similar memory allocation on the stack.
  • Capturing closures require additional memory allocation on the heap to store captured variables.
  • Both function pointers and closures ultimately call the function's code residing in the program's code segment.

In essence, function pointers and closures provide different ways to achieve a similar goal - passing functions as arguments. However, their memory management strategies differ based on whether they capture variables.


Nice read: Function Pointers in Rust: A Comprehensive Guide – Murat Genc ( gencmurat.com )


要查看或添加评论,请登录

社区洞察

其他会员也浏览了