# Syntax Proposal: Replace \`thin\` with \`{}\`

**URL:** <https://forum.modular.com/t/syntax-proposal-replace-thin-with/3383>\
**Category:** Mojo\
**Tags:** discussion\
**Created:** [August 10, 2026, 9:09pm UTC](https://forum.modular.com/t/syntax-proposal-replace-thin-with/3383 "2026-08-10T21:09:05Z")\
**Posts on this page:** 15\
**Page:** 1

<div class="post-metadata">

**Author:** ![yinon](https://avatars.discourse-cdn.com/v4/letter/y/bcef8e/32.png) [@yinon](https://forum.modular.com/u/yinon)\
**Post date:** [August 10, 2026, 9:09pm UTC](https://forum.modular.com/t/syntax-proposal-replace-thin-with/3383/1 "2026-08-10T21:09:05Z")

</div>

# Syntax Proposal: Replace `thin` with `{}`

Currently, a non-capturing function pointer type is written as `def(Int) thin -> Int`. We propose replacing the `thin` keyword with an empty capture list `{}` (e.g. `def(Int) {} -> Int`).

- `f: def()`: Generic function type (allows both closures and function pointers).
- `f: def() {}`: Explicitly disallows captures (strictly a function pointer).

This change:

1. Matches the syntax of a closure definition that captures nothing (analogous to a function pointer, as non-capturing closures implicitly coerce into them).
2. Removes the need for a separate `thin` keyword.

```mojo
def main():
    def add_1(n: Int) {} -> Int:
        return n + 1
    print(take_fn_ptr(add_1))

# current
def take_fn_ptr(f: def(Int) thin -> Int) -> Int:
    return f(42)

# proposed
def take_fn_ptr(f: def(Int) {} -> Int) -> Int:
    return f(42)

def complex_example():
    # current
    def return_fn_ptr(f: def(Int) thin -> Int) {} -> def(Int) thin -> Int:
            return f

    # proposed
    def return_fn_ptr(f: def(Int) {} -> Int) {} -> def(Int) {} -> Int:
        return f

```

**Open Questions:**

- Is there a practical use case for explicitly requiring a closure and rejecting a thin function pointer (e.g. using a syntax like `f: def() {_}` or `f: def() {...}`)?
- Currently, it is bit confusing that `def(Int) thin -> Int` acts as a direct function type, while `def(Int) -> Int` acts as a meta function type requiring explicit parameterization (or `Some[]`). Instead of this split, could `def(Int) -> Int` auto-parameterize on the closure type?

```mojo
# current: thin function type
def take_fn(f: def(Int) thin -> Int) -> Int:
    return f(42)

# current: generic - explicit parameterization required
def take_fn[F: def(Int) -> Int](f: F) -> Int:
    return f(42)

# current: Some[] alternative
def take_fn(f: Some[def(Int) -> Int]) -> Int:
    return f(42)

# proposed: explicitly strict function pointer
def take_fn(f: def(Int) {} -> Int) -> Int:
    return f(42)

# proposed: explicit parameterizes on the closure type
def take_fn(f: def(Int) {_} -> Int) -> Int:
    return f(42)

# proposed: auto-parameterizes on the closure type
def take_fn(f: def(Int) -> Int) -> Int:
    return f(42)

```

---

<div class="post-metadata">

**Author:** ![nate](https://sea1.discourse-cdn.com/flex001/user_avatar/forum.modular.com/nate/32/740_2.png) [@nate](https://forum.modular.com/u/nate)\
**Post date:** [August 10, 2026, 9:27pm UTC](https://forum.modular.com/t/syntax-proposal-replace-thin-with/3383/2 "2026-08-10T21:27:24Z")

</div>

> [@yinon](#):
>
> - Is there a practical use case for explicitly requiring a closure and rejecting a thin function pointer (e.g. using a syntax like `f: def() {_}` or `f: def() {...}`)?

I don’t believe so - or I would be **very** surprised if someone had a real motivating example where they want a closure but want to reject a thin function pointer.

---

<div class="post-metadata">

**Author:** ![owenhilyard](https://sea1.discourse-cdn.com/flex001/user_avatar/forum.modular.com/owenhilyard/32/41_2.png) [@owenhilyard](https://forum.modular.com/u/owenhilyard)\
**Post date:** [August 11, 2026, 2:43am UTC](https://forum.modular.com/t/syntax-proposal-replace-thin-with/3383/3 "2026-08-11T02:43:16Z")

</div>

> I don’t believe so - or I would be **very** surprised if someone had a real motivating example where they want a closure but want to reject a thin function pointer.

I have use-cases for this functionality.

---

<div class="post-metadata">

**Author:** ![denis](https://sea1.discourse-cdn.com/flex001/user_avatar/forum.modular.com/denis/32/399_2.png) [@denis](https://forum.modular.com/u/denis)\
**Post date:** [August 11, 2026, 3:17am UTC](https://forum.modular.com/t/syntax-proposal-replace-thin-with/3383/4 "2026-08-11T03:17:57Z")

</div>

Owen, can you elaborate?

---

<div class="post-metadata">

**Author:** ![denis](https://sea1.discourse-cdn.com/flex001/user_avatar/forum.modular.com/denis/32/399_2.png) [@denis](https://forum.modular.com/u/denis)\
**Post date:** [August 11, 2026, 3:48am UTC](https://forum.modular.com/t/syntax-proposal-replace-thin-with/3383/5 "2026-08-11T03:48:35Z")

</div>

> [@yinon](#):
>
> - Instead of this split, could `def(Int) -> Int` auto-parameterize on the closure type?

Can you spell out the benefit, why this would be desirable? Do you see a practical application where this would add a benefit for the users?

(Personally I find it a bit alarming that a trait `def(Int)->Int` and a type `def (Int) thin ->Int` are so similar in spelling)

---

<div class="post-metadata">

**Author:** ![yinon](https://avatars.discourse-cdn.com/v4/letter/y/bcef8e/32.png) [@yinon](https://forum.modular.com/u/yinon)\
**Post date:** [August 11, 2026, 10:06am UTC](https://forum.modular.com/t/syntax-proposal-replace-thin-with/3383/6 "2026-08-11T10:06:37Z")

</div>

> [@denis](#):
>
> Personally I find it a bit alarming that a trait `def(Int)->Int` and a type `def (Int) thin ->Int` are so similar in spelling

Yes, I completely agree! They _do_ look very similar, which is exactly why it’s confusing that one acts as a trait and the other acts as a type. It would be more consistent if they both acted as types.

> [@denis](#):
>
> Can you spell out the benefit, why this would be desirable?

One way to look at it is:  
Using C++ notation to avoid confusion with current Mojo syntax, a closure can be thought of as a parameterized struct containing capture data and a function pointer:

```cpp
template<typename CDATA>
struct MyIntToIntClosure {
    CDATA capture_data;
    int (*fn_ptr)(CDATA, int);

    // Assuming we had a dunder call in C++
    int __call__ (int x) {
        return this->fn_ptr(this->capture_data, x);
    }
};

```

In Mojo, since typenames act like parameters, `MyIntToIntClosure` can be specialized on the `CDATA` type (the user capture data).

For a non-capturing function (what we currently call `thin`), the capture data is just `NoneType`. Because `NoneType` has zero size, it is optimized away, leaving a thin function pointer:

```mojo
# Explicitly specialized with NoneType
def take_fn(f: MyIntToIntClosure[NoneType]) -> Int:
    return f(42)

# Currently: represented as a special 'thin' type
def take_fn(f: def(Int) thin -> Int) -> Int:
    return f(42)

# Proposed: empty capture list {} acts as sugar for NoneType
def take_fn(f: def(Int) {} -> Int) -> Int:
    return f(42)

```

When we _do_ have closure captures, the type is parameterized on the specific capture data. Currently, Mojo uses a trait to accept generic closures:

```mojo
# Explicitly parameterized on an unknown closure capture type
def take_fn(f: MyIntToIntClosure[_]) -> Int:
    return f(42)

# Currently: requires falling back to a trait
def take_fn[F: def(Int) -> Int](f: F) -> Int:
    return f(42)

# Proposed: explicitly indicates a capture type parameter
def take_fn(f: def(Int) {_} -> Int) -> Int:
    return f(42)

```

Of course there are optimizations like when the `CDATA` type contains all register passable types there is an optimization to pass it in registers, but these optimizations come later and don’t change the `MyIntToIntClosure` original type:

```mojo
struct CDATA(TrivalRegisterPassable):
    ref a: Int
    ref b: Int

```

Now because Mojo has auto-parameterization for types, if we treat the base function signature as a parameterized type rather than a trait, we get:

```mojo
# Auto-parameterized on the closure capture type
def take_fn(f: MyIntToIntClosure) -> Int:
    return f(42)

# Currently: requires boilerplate trait syntax
def take_fn[F: def(Int) -> Int](f: F) -> Int:
    return f(42)

# Proposed: `def(Int) -> Int` acts as an auto-parameterized type
def take_fn(f: def(Int) -> Int) -> Int:
    return f(42)

```

**The practical benefits for the user are:**

1. **Consistency** : `def(Int) -> Int` and `def(Int) {} -> Int` are both types, making the syntax more uniform.
2. **Ergonomics** : Users can just write `(f: def(Int) -> Int)` without explicitly using trait parameterization syntax `[F: def(Int) -> Int](f: F)`, exactly like other generic types in Mojo.
3. **Unified Mental Model** : Closures can be reasoned about as normal parameterized structs rather than requiring a separate trait system.

* * *

_Footnote: This `{}` syntax also nicely opens the door to restricting side-effects in closures. For example, there might be a use case for explicitly requiring an immutable (`imm`) closure that doesn’t have side effects, ensuring it can be used idempotently multiple times:_

```mojo
def take_fn(f: def(Int) {imm} -> Int) -> Int:
    var one = f(42)
    var two = f(42)
    assert one == two

```

---

<div class="post-metadata">

**Author:** ![nate](https://sea1.discourse-cdn.com/flex001/user_avatar/forum.modular.com/nate/32/740_2.png) [@nate](https://forum.modular.com/u/nate)\
**Post date:** [August 11, 2026, 3:05pm UTC](https://forum.modular.com/t/syntax-proposal-replace-thin-with/3383/7 "2026-08-11T15:05:44Z")

</div>

While I agree the current syntax is confusing, I’m not sure this proposal actually adds much clarity and arguably just moves the confusion elsewhere.

I think an important distinction is that `def(Int) -> Int` needs to be expressible as a **trait**. Otherwise, how would things like trait composition work?

```auto
def foo[F: def(Int) -> Int & OtherTrait](f: F):
    ...

```

Similarly, you need some way to generically store a closure without knowing its concrete type:

```auto
struct Foo[F: def(Int) -> Int]:
    var closure: Self.F

```

So while I agree the current spellings of `def(Int) -> Int` and `def(Int) thin -> Int` are confusingly similar, I think the better direction is to make the closure trait look more obviously like a trait.

For example, something along the lines of:

```auto
def foo(f: Some[FnOnce[(Int) -> Int]]):
    ...

```

(or whatever the eventual spelling is).

---

<div class="post-metadata">

**Author:** ![yinon](https://avatars.discourse-cdn.com/v4/letter/y/bcef8e/32.png) [@yinon](https://forum.modular.com/u/yinon)\
**Post date:** [August 11, 2026, 6:08pm UTC](https://forum.modular.com/t/syntax-proposal-replace-thin-with/3383/8 "2026-08-11T18:08:55Z")

</div>

I think we need the inverse of `Some[]`—perhaps something like `Type[]` or `Trait[]`—to extract the meta-type or trait of a given type. Alternatively, if we prefer to restrict this specifically to functions and closures, we could adopt a Python-like `Callable[def(Int) -> Int]` to retrieve the trait of a function type.

This approach preserves the consistency and ergonomics of treating both `def(Int) -> Int` and `def(Int) thin -> Int` as types, while providing a clear mechanism to access the trait.

> [@nate](#):
>
> I think an important distinction is that `def(Int) -> Int` needs to be expressible as a **trait**. Otherwise, how would things like trait composition work?
> 
> ```auto
> def foo[F: def(Int) -> Int & OtherTrait](f: F):
> ...
> 
> ```

Using this syntax, trait composition could look like one of the following:

```mojo
def foo[F: Type[def(Int) -> Int] & OtherTrait](f: F):
    ...

def foo[F: Trait[def(Int) -> Int] & OtherTrait](f: F):
    ...

def foo[F: Callable[def(Int) -> Int] & OtherTrait](f: F):
    ...

```

---

<div class="post-metadata">

**Author:** ![Rolf](https://avatars.discourse-cdn.com/v4/letter/r/94ad74/32.png) [@Rolf](https://forum.modular.com/u/Rolf)\
**Post date:** [August 12, 2026, 12:30pm UTC](https://forum.modular.com/t/syntax-proposal-replace-thin-with/3383/9 "2026-08-12T12:30:05Z")

</div>

I agree that the type/trait syntax not being consistent with signatures is … unfortunate. In types,`thin` also appears to be in the place of effects, which is not what we are trying to denote.

I am in favour of deprecating `thin` in types and just writing `def(Int) {} -> Int` for the type of thin `Int` to `Int` functions. Whether we also should support `def(Int) {_} -> Int`(or `def(Int) {...} -> Int`) as the _trait_ of capturing `Int` to `Int` function types is something we can punt until we have use cases (@owenhilyard , we are all ears).

Finally, while I am not a fan of `def(Int) -> Int` being the syntax of the _trait_, rather than thin function type, it does read well when it comes to writing that you need some type that conforms to the trait. E.g., `def f(g: Some[def(Int) -> Int], i: Int) -> Int` and `def f(g: FnOnce[def(Int) -> Int]) -> Int`. Presumably that would later on extend to `def f(g: Dyn[def(int) -> Int]) -> Int`where argument`g` uses indirection so that values from all the types which are callable can be passed.

(@nate , should`FnOnce` maybe be called `CallOnce`instead?)

---

<div class="post-metadata">

**Author:** ![Rolf](https://avatars.discourse-cdn.com/v4/letter/r/94ad74/32.png) [@Rolf](https://forum.modular.com/u/Rolf)\
**Post date:** [August 12, 2026, 12:33pm UTC](https://forum.modular.com/t/syntax-proposal-replace-thin-with/3383/10 "2026-08-12T12:33:33Z")

</div>

@Steffi do you have arguments for or against the proposal?

---

<div class="post-metadata">

**Author:** ![owenhilyard](https://sea1.discourse-cdn.com/flex001/user_avatar/forum.modular.com/owenhilyard/32/41_2.png) [@owenhilyard](https://forum.modular.com/u/owenhilyard)\
**Post date:** [August 13, 2026, 12:46am UTC](https://forum.modular.com/t/syntax-proposal-replace-thin-with/3383/11 "2026-08-13T00:46:59Z")

</div>

The use-case is currently internal-only, I’ll ping people on the slack thread.

---

<div class="post-metadata">

**Author:** ![Peiming](https://avatars.discourse-cdn.com/v4/letter/p/49beb7/32.png) [@Peiming](https://forum.modular.com/u/Peiming)\
**Post date:** [August 13, 2026, 2:48am UTC](https://forum.modular.com/t/syntax-proposal-replace-thin-with/3383/12 "2026-08-13T02:48:34Z")

</div>

The issue is that `thin` means a function type, without `thin`, it means a trait.

IMO, we should make them looks more different instead of making them more similar to avoid confusion.

---

<div class="post-metadata">

**Author:** ![yinon](https://avatars.discourse-cdn.com/v4/letter/y/bcef8e/32.png) [@yinon](https://forum.modular.com/u/yinon)\
**Post date:** [August 13, 2026, 7:17am UTC](https://forum.modular.com/t/syntax-proposal-replace-thin-with/3383/13 "2026-08-13T07:17:38Z")

</div>

Alternatively, both could be traits, where a `thin` function trait can accept any zero-sized callable struct. For example:

```mojo
struct AddN[N: Int]:
    # Zero-sized with no runtime state
    def __init__ (out self):
        pass

    def __call__ (self, x: Int) -> Int:
        return x + Self.N

# Currently (as a type):
def take_fn_ptr(f: def(Int) thin -> Int) -> Int:
    return f(42)

# Proposed (as a trait):
def take_fn_ptr(f: Some[def(Int) {} -> Int]) -> Int:
    return f(42)

def main():
    var f = AddN[10]()
    print(take_fn_ptr(f)) # Fails currently

```

---

<div class="post-metadata">

**Author:** ![Peiming](https://avatars.discourse-cdn.com/v4/letter/p/49beb7/32.png) [@Peiming](https://forum.modular.com/u/Peiming)\
**Post date:** [August 13, 2026, 3:42pm UTC](https://forum.modular.com/t/syntax-proposal-replace-thin-with/3383/14 "2026-08-13T15:42:07Z")

</div>

You do need a bare function pointer-like type for FFI staff though?

---

<div class="post-metadata">

**Author:** ![yinon](https://avatars.discourse-cdn.com/v4/letter/y/bcef8e/32.png) [@yinon](https://forum.modular.com/u/yinon)\
**Post date:** [August 13, 2026, 5:26pm UTC](https://forum.modular.com/t/syntax-proposal-replace-thin-with/3383/15 "2026-08-13T17:26:11Z")

</div>

For FFI, we need to specify the `abi` explicitly, for example:

```mojo
def take_ffi_ptr(ffi_ptr: def(UInt32) {} abi("C") -> UInt32):
    ...

```

So maybe it could behave like a type when it has the `abi` effect?

Or perhaps it could still use `Some[]`:

```mojo
def take_ffi_ptr(ffi_ptr: Some[def(UInt32) {} abi("C") -> UInt32]):
    ...

```

But since the `{}` (and `abi`) already reduces it down to a single possible type, `Some[]` would introduce a degenerate parameter that could just be optimized out?
