# Var<T> vs Expr<T> in the Luisa Compute DSL: Storage and Expression Types Explained

> Understand Var<T> vs Expr<T> in Luisa Compute. Learn how Var<T> defines mutable storage and Expr<T> represents read-only values to optimize your kernel IR.

- Repository: [LuisaGroup/luisacompute](https://github.com/luisagroup/luisacompute)
- Tags: deep-dive
- Published: 2026-03-06

---

**`Var<T>` declares mutable storage locations in kernel IR while `Expr<T>` represents read-only values and computation results, with `Var` implicitly converting to `Expr` but never the reverse.**

In the `luisagroup/luisacompute` shading language, distinguishing between mutable storage and read-only expressions is fundamental to writing correct GPU kernels. The DSL provides two primary template types—**`Var<T>`** and **`Expr<T>`**—that separate variable declaration from value computation within the abstract syntax tree. Understanding when to use each type ensures you allocate device memory only when necessary while maintaining type-safe expression trees.

## Core Concepts of Var<T> and Expr<T>

### What is Var<T>?

In [`include/luisa/dsl/var.h`](https://github.com/luisagroup/luisacompute/blob/main/include/luisa/dsl/var.h), the **`Var<T>`** template class creates a named storage location in the generated kernel intermediate representation (IR). Each `Var` instance generates a declaration statement and supports assignment operators like `operator=` and `operator+=`. This type is essential when you need values to persist across statements or require mutation within loops and conditionals.

### What is Expr<T>?

Defined primarily in [`include/luisa/dsl/struct.h`](https://github.com/luisagroup/luisacompute/blob/main/include/luisa/dsl/struct.h), **`Expr<T>`** wraps existing values without allocating new storage. It represents literals, variable references, or temporary computation results. The DSL treats `Expr` as an immutable view that composes freely into expression trees but cannot serve as an assignment target.

## When to Use Var<T> in the Luisa Compute DSL

Use **`Var<T>`** when you require:

- **Named local variables** inside kernels that persist across multiple statements, such as loop accumulators or temporary buffers
- **Assignment capabilities** including mutation operators like `+=`, `-=`, or direct reassignment
- **Kernel parameters passed by reference** where the underlying storage must be extracted and potentially modified
- **Mutable struct fields** when defining DSL structs, as each field stores data as `Var<member_type>`

```cpp
device.compile<1>([](Var<float3> pos, Var<float> time) noexcept {
    Var<float3> shifted = pos + make_float3(0.f, 1.f, 0.f);
    Var<float> intensity = dot(shifted, make_float3(1.f, 0.f, 0.f));
    shifted = make_float3(0.f, 0.f, 0.f);  // Reassignment allowed
});

```

## When to Use Expr<T> in the Luisa Compute DSL

Use **`Expr<T>`** when you need:

- **Read-only values** sourced from variables, literals, or function returns without declaring new storage
- **Pure expressions** inside return statements, function arguments, or conditional checks where mutation is unnecessary
- **Temporary computations** passed to callables without explicit variable declaration overhead
- **Struct field accessors** that expose read-only views of underlying `Var` storage members

```cpp
device.compile<1>([](Var<float3> pos) noexcept {
    Expr<float3> direction = normalize(pos);  // Read-only view
    Expr<float> length = dot(pos, direction);  // Computation result
    return length * 0.5f;  // Used directly in expressions
});

```

## Relationship Between Var<T> and Expr<T>

The types maintain a strict one-way conversion relationship enforced by the DSL type system.

### Implicit Conversion from Var to Expr

In [`include/luisa/dsl/var.h`](https://github.com/luisagroup/luisacompute/blob/main/include/luisa/dsl/var.h), the `Var` class defines `operator Expr<T>() const noexcept`, allowing seamless use of variables within expressions:

```cpp
Var<int> counter = def<int>(0);
Expr<int> expr_view = counter;  // Implicit conversion

```

### No Reverse Conversion

Converting **`Expr<T>` to `Var<T>`** is prohibited because expressions may represent temporary values without unique storage locations. Only `Var` can serve as the assignment target.

## Practical Implementation Examples

### Accumulating Values Inside Kernels

When reducing buffer data, declare the accumulator as `Var` while reading buffer elements as `Expr`:

```cpp
auto sum_kernel = device.compile<1>([](BufferVar<int> data, Var<int> out) noexcept {
    Var<int> sum = def<int>(0);  // Mutable storage
    ForRange(i, 0, data.size()) {
        sum += data.read(i);     // data.read() returns Expr<int>
    }
    out = sum;  // Assignment to Var parameter
});

```

### Read-Only Computations in Callables

For pure calculations without side effects, chain `Expr` values through function calls:

```cpp
auto bright_pass = device.compile<1>([](Var<float3> color) noexcept {
    Expr<float> lum = 0.2126f * color.x + 0.7152f * color.y + 0.0722f * color.z;
    return color * (lum + 0.1f);  // Returns Expr<float3>
});

```

### DSL Structs with Automatic Type Handling

The struct macro in [`include/luisa/dsl/struct.h`](https://github.com/luisagroup/luisacompute/blob/main/include/luisa/dsl/struct.h) generates `Var` members for storage and `Expr` accessors for read-only field access:

```cpp
struct Light {
    Var<float3> position;   // Storage
    Var<float>  intensity;  // Storage
};

device.compile<1>([](Var<Light> l) noexcept {
    Expr<float3> pos = l.position;   // Read-only accessor
    Expr<float>  i   = l.intensity;
    Expr<float3> dir = normalize(pos - make_float3(0.f));
    return i * max(dot(dir, make_float3(0.f, 1.f, 0.f)), 0.f);
});

```

## Summary

- **`Var<T>`** creates mutable storage locations in the kernel IR and supports assignment; defined in [`include/luisa/dsl/var.h`](https://github.com/luisagroup/luisacompute/blob/main/include/luisa/dsl/var.h)
- **`Expr<T>`** provides read-only views of values and computation results without allocating storage; defined in [`include/luisa/dsl/struct.h`](https://github.com/luisagroup/luisacompute/blob/main/include/luisa/dsl/struct.h)
- **Var converts to Expr implicitly** via `operator Expr<T>() const noexcept`, but Expr cannot convert to Var
- Use **Var** for local variables, accumulators, and mutable struct fields
- Use **Expr** for literals, temporary calculations, and read-only struct accessors

## Frequently Asked Questions

### Can I assign a value to an Expr<T>?

No. **`Expr<T>`** is strictly read-only and lacks assignment operators. If you need to modify a value, declare it as **`Var<T>`** instead. The expression type represents values that may not have unique storage backing them, making assignment semantically invalid in the DSL.

### How do I convert a Var<T> to Expr<T>?

Conversion happens automatically through the implicit conversion operator defined in [`include/luisa/dsl/var.h`](https://github.com/luisagroup/luisacompute/blob/main/include/luisa/dsl/var.h). Simply assign a `Var` to an `Expr` variable or pass it to functions expecting `Expr` parameters: `Expr<float> e = my_var;`.

### Why does the DSL struct macro use both Var and Expr types?

The macro generates **`Var<member_type>`** for actual data storage within the struct layout, ensuring each field occupies device memory. It simultaneously exposes **`Expr<member_type>`** accessors to prevent accidental mutation of struct fields when accessed through instances, maintaining the DSL's distinction between storage and expression contexts.

### Where are Var<T> and Expr<T> defined in the luisacompute source?

**`Var<T>`** is defined in [`include/luisa/dsl/var.h`](https://github.com/luisagroup/luisacompute/blob/main/include/luisa/dsl/var.h) with declaration helpers and assignment operators. **`Expr<T>`** is defined in [`include/luisa/dsl/struct.h`](https://github.com/luisagroup/luisacompute/blob/main/include/luisa/dsl/struct.h) alongside the struct definition machinery. Additional statement builders that consume these types reside in [`include/luisa/dsl/stmt.h`](https://github.com/luisagroup/luisacompute/blob/main/include/luisa/dsl/stmt.h).