# RFC: Structural Typing for Closures

- Status: Inactive
- Authors: Larry Garfield crell@php.net, Nicolas Grekas nicolasgrekas@php.net
- Date: 2023-04-13
- Version: 1.0
- Implementation: TBD
- Original: https://wiki.php.net/rfc/structural-typing-for-closures
- Page: https://externals.io/rfc/structural-typing-for-closures
- More for agents: search with https://externals.io/search.md?q={query} · RFCs at https://externals.io/rfc.md · JSON API at https://externals.io/api · documentation at https://externals.io/llms.txt

## Discussions on the mailing list

- [\[Discussion\] Callable types via Interfaces](https://externals.io/message/120083.md) — started on 2023-04-20, 17 messages
- [\[Discussion\] Revisiting typed closures](https://externals.io/message/131657.md) — started on 2026-07-01, 20 messages

---

## Introduction

This RFC proposes to introduce structural typing for closures when used with interfaces containing only an `__invoke()` method. This feature aims to simplify the interaction between closures and single-method interfaces, making it easier to work with them in a more consistent and expressive manner.

## Proposal

Given an interface with only an `__invoke()` method, if a parameter, return, property, or instanceof operation is typed against that interface, and a closure is passed or returned as a value, the engine checks the signature of the closure to ensure it satisfies the contract defined by the interface. This check would also happen when using `is_subclass_of()` and `is_a()`.

This would encourage developers to define more explicit and expressive type declarations, which can lead to better code maintainability and fewer runtime errors.

### Example

```php
interface TwoInts
{
    public function __invoke(int $a, int $b): int;
}
 
function takeTwo(TwoInts $c)
{
    // This is a TwoInts anon class object at this point
    $c(1, 2);
}
 
// This works
takeTwo(fn(int $x, int $y): int => $x + $y);
 
// This throws a TypeError
bad(fn(int $x, float $y, string $z) => whatever);
```

## Backward Incompatible Changes

The proposed change does not introduce any backward incompatibilities. Existing code will continue to work as it does currently. The structural typing of closures would be an optional feature that developers can choose to use when it makes sense for their specific use case.

## Open Questions

- What would be the performance impact of adding this feature to the language, considering the engine would need to verify the closure signatures at runtime? Could this be cached internally?
- Should reflection/`class_implements()` know something about this? We think not at this point because those are defined in the context of nominal typing only.
- In order the achieve the required check, the engine would need to autoload the interface/type at call time or when using `instanceof`. The engine might not be prepared for that.

## Future Scope

- Auto-cast closures to interfaces that have a single method that is not necessarily named `__invoke()`
- Allow closures to declare the interface they implement as in e.g. `function () implements FooInterface {}`. See <https://externals.io/rfc/allow-closures-to-declare-interfaces-they-implement>

## Proposed Voting Choices

This RFC proposes a straight Yes/No vote. A two-thirds majority is required for acceptance.
