> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sensecode.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Functions

> def/-> — declarations, typed parameters, closures, and docstrings.

## Declare one

```sns theme={null}
def square(x):
    return x * x

def add(a: Int, b: Int) -> Int:
    return a + b
```

Parameters and the return type are both optional and independently
typed. Same shape as Python: `def` leading, `->` for the return type.

<Info>
  A plain function has no permission check and no audit entry — it's not
  the right tool the moment a call needs one. See
  [Which keyword do I want?](/reference/keywords#which-keyword-do-i-want)
</Info>

## Inline bodies

Short bodies can skip the indented block:

```sns theme={null}
def classify(n) -> String:
    if n < 0: return "negative"
    if n == 0: return "zero"
    return "positive"
```

## Closures

A function closes over the scope it was declared in:

```sns theme={null}
base = 10
def add_base(x: Int) -> Int:
    return x + base
print(add_base(5))   # 15
```

Assignment follows [the one assignment rule](/language/variables-and-scope):
a function body can mutate a variable from its enclosing scope directly,
no `nonlocal` needed:

```sns theme={null}
total = 0
def accumulate(x: Int):
    total = total + x   # mutates the outer total
accumulate(3)
accumulate(4)
print(total)   # 7
```

## Docstrings

A bare string as the first statement of a function's body is captured as
its `.doc`, same as Python:

```sns theme={null}
def square(x: Int) -> Int:
    """
    Returns the square of x.
    """
    return x * x
```

`"""..."""` and `"..."` both work — plain `"..."` already tolerates an
embedded newline. The text is dedented and trimmed for you. No docstring?
`.doc` is `nil`, never an error. `tool`, `action`, and `agent` bodies work
the same way; see them in [`sense inspect`](/reference/cli)'s panels.

## Type checking

Checked at the call site and at `return`:

```sns theme={null}
def f(a: Int) -> Int:
    return a
f("nope")   # SenseTypeError at the call site
```

```sns theme={null}
def g() -> Int:
    return "not an int"   # SenseTypeError when the function returns
```

Real, but shallow (`matches_type()` — see [Values & Types](/language/values-and-types)),
not full static inference.

## Arity is strict

Every parameter is required, exactly once, no defaults:

```sns theme={null}
def add(a: Int, b: Int) -> Int:
    return a + b
add(1)          # error: 'add' expects 2 argument(s), got 1
add(1, 2, 3)    # error: 'add' expects 2 argument(s), got 3
```

(Some *builtins* — `print`, `range` — are variadic. User-defined
functions aren't, yet.)

## Continue

<CardGroup cols={2}>
  <Card title="Control flow" icon="route" href="/language/control-flow">
    `if`/`else`, `while`, `for`, `break`, `continue`.
  </Card>

  <Card title="Actions" icon="shield-check" href="/safety/actions">
    Looks like a function, behaves completely differently — for anything
    that changes the outside world.
  </Card>
</CardGroup>
