Fix E0772: Function pointer called with wrong number of arguments

compilation-errors beginner Linux macOS Windows

1. Symptoms

The Rust compiler emits error E0772 when attempting to invoke a function pointer with a mismatched argument count. This error occurs during the type-checking phase of compilation.

error[E0772]: this function takes 2 argument(s) but 3 argument(s) were supplied –> src/main.rs:5:17 | 5 | let result = func_ptr(arg1, arg2, arg3); | ^^^^^^^^^ —————- | | | unexpected argument


The compiler clearly indicates the discrepancy between the expected argument count and the actual number provided. In this case, the function pointer expects 2 arguments, but 3 were supplied.

**Additional symptom patterns:**

```rust
// Example 1: Too few arguments
error[E0772]: this function takes 3 argument(s) but 1 argument(s) were supplied
 --> src/main.rs:10:20
  |
10 |     let result = func_ptr(arg1);
  |                 ^^^^^^^^^ -- expected 2 more argument(s)

// Example 2: Calling with zero arguments when one is expected
error[E0772]: this function takes 1 argument(s) but 0 argument(s) were supplied
 --> src/main.rs:15:5
  |
15 |     func_ptr();
  |     ^^^^^^^^

// Example 3: With closures and function pointers
error[E0772]: this function takes 2 argument(s) but 3 argument(s) were supplied
  --> src/main.rs:20:14
   |
20 |     (func_ptr)(1, 2, 3);
   |      ^^^^^^^^ ---------

The error message always follows the pattern: this function takes N argument(s) but M argument(s) were supplied, where N is the expected count and M is the actual count.


2. Root Cause

Error E0772 stems from a fundamental type mismatch between the function pointer’s signature and the invocation syntax. In Rust, function pointers have a distinct type that encodes both the return type and the parameter types.

Type System Background:

Function pointer types in Rust are written as fn(参数的Types) -> ReturnType. For example:

  • fn() represents a function taking no arguments and returning nothing
  • fn(i32, i32) -> i32 represents a function taking two i32 values and returning an i32
  • fn(&str) -> String represents a function taking a string slice and returning a String

When you attempt to call a function pointer, Rust’s type system enforces that you provide exactly the right number and types of arguments. If either quantity or type mismatches, the compiler rejects the code.

Common Scenarios Where E0772 Occurs:

  1. Dynamic function selection with mismatched signatures: When using function pointers selected at runtime through traits or enums with different function variants

  2. Refactoring without updating call sites: After changing a function’s parameter count but forgetting to update all call sites

  3. Higher-order function composition errors: When composing functions incorrectly in functional-style Rust code

  4. Callback registration mismatches: When registering callbacks with an API that expects a different function signature

  5. Generic function pointer handling: When generic code produces function pointers that don’t match expected call patterns

The root issue is always a disconnect between what the function pointer type promises (its signature) and what the calling code provides (actual arguments at the call site).


3. Step-by-Step Fix

Step 1: Identify the Function Pointer Type

First, examine the error message to determine the expected argument count. The number after “takes” indicates how many arguments the function pointer expects.

// Determine the type of your function pointer
fn add(a: i32, b: i32) -> i32 {
    a + b
}

fn main() {
    // Explicitly annotate the function pointer type
    let func_ptr: fn(i32, i32) -> i32 = add;
    
    // Now call it correctly
    let result = func_ptr(5, 10);
    println!("Result: {}", result);
}

Before:

fn process(callback: fn(i32) -> i32) {
    // ...
}

fn main() {
    fn two_args(a: i32, b: i32) -> i32 {
        a + b
    }
    process(two_args); // Problem: two_args takes 2 args, callback expects 1
}

After:

fn process(callback: fn(i32, i32) -> i32) {
    // Updated signature to match
}

fn main() {
    fn two_args(a: i32, b: i32) -> i32 {
        a + b
    }
    process(two_args); // Correct: both take 2 arguments
}

Step 2: Fix the Call Site Argument Count

If the function pointer signature is correct, adjust the call site to match.

Before:

type Operation = fn(i32, i32) -> i32;

fn calculate(op: Operation, a: i32, b: i32, extra: i32) {
    // Attempting to call with wrong number of arguments
    let result = op(a, b, extra);
}

After:

type Operation = fn(i32, i32) -> i32;

fn calculate(op: Operation, a: i32, b: i32) {
    // Correct: matching the function pointer signature
    let result = op(a, b);
    println!("Result: {}", result);
}

fn main() {
    fn add(a: i32, b: i32) -> i32 { a + b }
    calculate(add, 10, 20);
}

Step 3: Use Wrapper Functions for Signature Adaptation

When working with callbacks that have fixed signatures, create wrapper functions.

Before:

fn call_with_three<F>(func: fn(i32, i32, i32)) {
    func(1, 2, 3);
}

fn main() {
    fn takes_two(a: i32, b: i32) {
        println!("{}, {}", a, b);
    }
    call_with_three(takes_two); // E0772: takes_two takes 2 args, expected 3
}

After:

fn call_with_three<F>(func: fn(i32, i32, i32)) {
    func(1, 2, 3);
}

fn main() {
    // Wrapper that adapts the 2-arg function to the 3-arg requirement
    fn takes_two(a: i32, b: i32) {
        println!("{}, {}", a, b);
    }
    
    fn adapter(a: i32, b: i32, c: i32) {
        takes_two(a, b);
        println!("Third arg ignored: {}", c);
    }
    
    call_with_three(adapter); // Works: adapter matches expected signature
}

Step 4: Use Closures with the Correct Signature

Closures can be converted to function pointers when they don’t capture their environment.

Before:

fn execute_callback(callback: fn(i32, String) -> bool) -> bool {
    callback(42, "hello".to_string())
}

fn main() {
    // This closure captures `extra` from the environment
    let extra = 100;
    let my_callback = |x: i32, s: String| -> bool {
        println!("{} {}", x + extra, s);
        true
    };
    execute_callback(my_callback); // Won't work: closure captures environment
}

After:

fn execute_callback(callback: fn(i32, String) -> bool) -> bool {
    callback(42, "hello".to_string())
}

fn main() {
    // Use a named function instead of a capturing closure
    fn my_callback(x: i32, s: String) -> bool {
        println!("{} {}", x, s);
        true
    }
    execute_callback(my_callback); // Correct: function pointer, not closure
}

Step 5: Handle Variadic Function Pointers with Generics

For more complex scenarios, use generics or trait objects.

// Using Fn traits when function pointer flexibility is needed
fn execute<F>(func: F) -> i32
where
    F: Fn(i32, i32) -> i32,
{
    func(10, 20)
}

fn main() {
    fn multiply(a: i32, b: i32) -> i32 { a * b }
    fn add(a: i32, b: i32) -> i32 { a + b }
    
    println!("Multiply: {}", execute(multiply));
    println!("Add: {}", execute(add));
}

4. Verification

After applying the fix, verify that the code compiles successfully.

Compile the code:

cargo build

A successful build produces no errors:

   Compiling my_project v0.1.0
    Finished dev [unoptimized + debuginfo] target(s) in 0.52s

Run tests to ensure functionality:

cargo test
running 1 test
test tests::test_function_pointer_call ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

Create a minimal test case to confirm the fix:

fn main() {
    // Verify the exact scenario that caused E0772
    
    // Test case 1: Correct argument count
    fn add(a: i32, b: i32) -> i32 { a + b }
    let result = add(5, 10);
    assert_eq!(result, 15);
    
    // Test case 2: Function pointer assignment and call
    type BinaryOp = fn(i32, i32) -> i32;
    let op: BinaryOp = |x, y| x * y;
    let product = op(3, 4);
    assert_eq!(product, 12);
    
    // Test case 3: Passing function pointer to another function
    fn apply(op: fn(i32, i32) -> i32, a: i32, b: i32) -> i32 {
        op(a, b)
    }
    assert_eq!(apply(add, 100, 200), 300);
    
    println!("All tests passed!");
}

Execute the verification:

cargo run
All tests passed!

Check for any related warnings:

cargo clippy

Clippy may provide additional suggestions for improving function pointer usage patterns.


5. Common Pitfalls

Pitfall 1: Assuming Closures and Function Pointers are Interchangeable

Rust closures are not the same as function pointers. Closures can capture their environment, while function pointers cannot. This distinction matters when working with callback APIs.

// WRONG: Trying to use a capturing closure where fn is expected
fn call_fn(func: fn()) {
    func();
}

fn main() {
    let x = 10;
    let closure = || println!("{}", x);
    call_fn(closure); // Won't compile
}

// CORRECT: Either don't capture or use Fn trait
fn call_fn<F: Fn()>(func: F) {
    func();
}

fn main() {
    let x = 10;
    let closure = || println!("{}", x);
    call_fn(closure); // Works with Fn trait
}

Pitfall 2: Confusing Method Receivers in Function Pointers

Methods with receivers (&self, &mut self, self) cannot be used as function pointers directly.

// WRONG
struct Calculator {
    value: i32,
}

impl Calculator {
    fn add(&self, other: i32) -> i32 {
        self.value + other
    }
}

fn main() {
    let calc = Calculator { value: 10 };
    let func: fn(&Calculator, i32) -> i32 = Calculator::add; // Won't work
}

Pitfall 3: Forgetting That Unit () is an Argument

Functions returning () still take arguments if they have parameters.

// WRONG
fn print_sum(a: i32, b: i32) {
    println!("{}", a + b);
}

fn main() {
    let func: fn() = print_sum; // Wrong: this fn takes two i32 args
    func(); // E0772
}

// CORRECT
fn main() {
    let func: fn(i32, i32) = print_sum;
    func(3, 4); // Prints: 7
}

Pitfall 4: Generic Functions Accepting Multiple Callable Types

When using generics, be aware that Fn traits accept various callable types, but function pointers have a stricter type.

// This accepts closures AND function pointers
fn execute<F: Fn(i32) -> i32>(f: F, x: i32) -> i32 {
    f(x)
}

// This ONLY accepts function pointers
fn execute_exact(f: fn(i32) -> i32, x: i32) -> i32 {
    f(x)
}

fn main() {
    fn double(x: i32) -> i32 { x * 2 }
    
    // Both work with fn
    execute(double, 5);
    execute_exact(double, 5);
    
    // Only execute() works with closures
    execute(|x| x * 2, 5);
    // execute_exact(|x| x * 2, 5); // Won't compile
}

Pitfall 5: Platform-Specific Function Signatures

Some platform-specific functions have varying signatures. Ensure your function pointer type matches the actual platform API.


Error CodeDescriptionRelationship
E0061This function takes argumentsBoth relate to function calls with wrong argument handling
E0050Method call has an incompatible number of argumentsSimilar issue but for methods vs function pointers
E0308Mismatched typesBroader type error that E0772 falls under
E0615Attempt to call a value that is not a function or function pointerRelated to calling non-callable values
E0520Return type cannot have an unboxed inner pointerUnrelated but common in FFI contexts

E0061 vs E0772:

E0061 occurs when calling regular functions with wrong arguments, while E0772 specifically targets function pointers:

// E0061: Regular function call
fn greet(name: &str) {
    println!("Hello, {}", name);
}
greet(); // E0061: this function takes 1 argument

// E0772: Function pointer call
fn main() {
    let func: fn(&str) = greet;
    func(); // E0772: this function takes 1 argument
}

Both errors indicate the same fundamental problem but target different code patterns in Rust’s type system.