---
# System prepended metadata

title: 'Thoughts on In-place init, Sep 18 2025'
tags: [rust, in-place-init]

---

# Thoughts on In-place init, Sep 18 2025

# out-pointer

_None of the following syntax proposals is binding so that a syntax proposal can be delayed as much as possible. This is a reconstruction of the conversation during Kangrejos 2025._

_[More discussion on Zulip chat](https://rust-lang.zulipchat.com/#narrow/channel/528918-t-lang.2Fin-place-init/topic/out-pointer.20type.20and.20MIR.20semantics.20consideration/with/541880132)_

So we have confirmed that dataflow analysis can be adapted to partially support the [partial initialisation in borrow checker](https://github.com/rust-lang/rust/issues/54987).

We would like to extend the idea to out-places and out-pointers.

## Notation

An out-place is a user local without an initializer expression.

```rust
let uninit_place: Type;
```

An out-pointer is a reference qualified with `out`.

```rust
uninit_pointer: &'a out Type
```

### Basic types

The type of an out-place and referent of an out-pointer must be `Sized` and one of ADT or tuples, *without `#![non_exhaustive]` unless the type is local in the current crate and visible to the current body*.

```rust
#![non_exhaustive]
struct Type {
    field: u8,
}
let out uninit_place: Type;
let uninit_pointer: &out Type = &out uninit_place;
// Then `Type: Sized` and is either an ADT or a tuple
// and additionally `Type` is local in the current crate
// and its fields are visible to the current body
```

### Region relationship

The following notation is adopted to describe region relations.

- When we want to say that live range of `'a` contains that of `'b`, we write as a predicate `'a: 'b` as per Rust language `where` clause syntax, or `'a :> 'b`;
- Conversely, when live range of `'b` contains that of `'a`, we write `'b: 'a` or `'a <: 'b`;
- When both of the forementioned condition hold, it is written as `'a <:> 'b` for short;
- When neither of the forementioned condition holds, it is written as `'a </> 'b`, or **"incomparable" to each other**.

## Well-formedness and auto traits
For `&'a out T` to be well-formed, it is necessary that `T: 'a`.

`&out T` is `Send` if `T: Send`. `&out T` is `Sync` if `T: Sync`.

`&out T` is `Unpin`.

## Initialisation certificate
When we lend out a field out of an `out` variable or `&out` reference to a sub-procedure like functions, closures and coroutines, we need a mechanism for the sub-procedure to certify that the entirety of the lent out place behind an `&out` is fully initialised. This will enable the caller of the sub-procedure to **discharge** the uninitialisation state of the lent out place.

In favour of expressiveness, we propose that the "certificate" has a type representation in Rust type system. The reason being is that the ability to write function pointer signature like `fn() -> ?` and trait predicates like `FnMut() -> ?` where `?` is the certificate type is very important. This makes out-pointer a more composable abstraction and, thus, a more useful one.

In order to establish a relationship between an initialisation certificate and a corresponding specific `&'a out T` instance, the type has to encode information so that the caller can re-constitute this tie. Our proposal here is to include the lifetime `'a` of the the out-pointer. Let us denote the type as `init<'a>`. When a `&'a out T` is discharged with its corresponding certificate `init<'b>`, `'a <:> 'b` must hold.

This certifying type must be able encode its unique relationship with exactly one of the `&out` inputs into the function.

Here are the basic facts about this type `init<'_>`:
- it is inhabited;
- it is 1-ZST;
- it implements `Send + Sync` but never `Clone` or `Copy`, so that it is non-fungible;
- a user inherent or trait implementation is never allowed;
- it has a trivial destructor.


#### Region rules involved in certification

When the arguments of a function signature contains two out-pointers, `&'a out X` and `&'b out Y`, `'a </> 'b` must hold in order for the function signature to be considered well-formed. To check this, the relations `'a :> 'b` and `'b :> 'a` are tested and the well-formedness is checked if both relations yield no solution. This rule is required so that we can safely establish a unique map between a given `&'a out T` passed in as an input and an `init<'a>` value with no ambiguity.

#### Further consideration

It is possible that `init<'a>` might be used as data in other types.

```rust
type XandY<'a, 'b> = (init<'a>, init<'b>);
// XandY<'a, 'b> is well-formed only if the generics 'a </> 'b

fn initialize<'a, 'b>(x: &'a out X, y: &'b out Y)
    -> Result<XandY<'a, 'b>, Error>
// XandY<'a, 'b> is well-formed only if 'a </> 'b in the typing environment are
{
    ..
}
```

This might imply that we demand a new sets of requirements for types and ADTs to be well-formed. If a data type contains a field with `init<'a>`, where `'a` is a generic lifetime, it is a **rigid** lifetime generic parameter. The property of a rigid lifetime generic parameter is that within a same data type, when two rigid lifetime generics are instantiated with two region values, these two region values must be incomparable to each other.

```rust
struct SomeType<'a>(init<'a>);
//              ^~ this generic parameter is rigid
struct Invalid<'a, 'b: 'a>(init<'a>, init<'b>);
//                 ^~~~~~            ^~~~~~~~ this is invalid because ...
//                 |_ 'b :> 'a is provable, but 'a and 'b are rigid
```

The same rule applies to function types, `fn(&'a out T) -> init<'a>` and `Fn*(&'a out T) -> init<'a>`. As an example, in checking `for<'a, 'b> fn(&'a out T, &'b out T, &'c out T) -> (init<'a>, init<'b>, init<'c>)`, since `'a` and `'b` will be assigned a universal placeholder value and, therefore, automatically incomparable to each other; `'c` lives in an outer universe and cannot name neither `'a` nor `'b` while neither `'a: 'c` nor `'b: 'c` holds. Therefore, this function pointer type is well-formed.


##### Unresolved question
What would this do to the other types? How would it work if `init<'a>` is captured by a closure, a coroutine or a coroutine closure which generates a coroutine of lifetime `'env`?

# Proposed MIR semantics

## Init states
The introduction of out-place would entail a relaxation of init states. Current MIR demands that projections out of a place are dominated by initialisation of the place as a whole. Our proposal requires this rule to be relaxed, so that assignments into possibly nested fields are allowed.

```rust
struct Type {
    a: u32,
    b: u32,
}
let out foo: Type;
foo.a = 0;
// use_me(&foo.b): ERROR, foo.b is uninit
// use_me(&foo): ERROR: foo.b is uninit
foo.b = 1;
// both a and b are now path-initialised
use_me(&foo.b); // OK
use_me(&foo); // OK
```

This code is still lowered as if `out foo` is "valid" for per-field assignments.

```mir
StorageLive(_foo)
_foo = const Type { 0: const 1u32, 1: const 2u32 };
_foo.0 = const 1u32;
_foo.1 = const 2u32;
_foo_b = &foo.b;
call use_me(move _foo_b) [return: .., unwind: ..]
```

## Function returning

At function returns, all `&out` and `out` variables must be discharged. It can be discharged either implicitly by inspecting initialisation state of the descendants of the `out` places and paths rooted in `&out` variables.

## MIR statements

We propose the following changes to MIR statements and their operational semantics.

### Statements

#### `Assign`

We assume the following local declarations with renumbered regions.
- `let _x: &'a out X;`
- `let _y: &'b out Y;`

```mir
_y = &out (*_x).y;
```
The pre-condition of constructing the `&out (*_x).y` is that the place `(*_x).y` must be uninitialised. This emits region relation `'b :> 'a` **as per current MIR semantics**.

```mir
*y = Y { .. };
```
The primary effect here is the place `*y` is set to be initialised. However, `(*_x).y` is considered to be still uninitialised. To discharge it, we need to use `_init = AssertInit(y)` and `SetInit((*_x).y, move _init)`

:warning: **Unresolved question**: What is the precondition of the assignment here? I suppose we need to assert uninitialised state.

#### :sparkles: AssertInit(`<place expr to a &out>`) intrinsic

```mir
let _init: init<'c>;
_init = AssertInit(_x);
```
This emits region relation `'a <:> 'c`. The borrow checker must assert that `*_x` is fully initialised as a pre-condition.


#### :sparkles: AssumeInit(`<place expr to a &out>`) intrinsic

This is an unsafe intrinsic that is supposedly only emitted from an unsafe function call.

```mir
_init = AssumeInit(_x);
```
The primary effect is that the place `_x` is considered initialised, along with the judgement `'a <:> 'c`.

#### :sparkles: SetInit(`<place expr to a &out>`, `<init>`)

```mir
SetInit(_x, move _init);
// mind that it is _x, **not** the uninit place *_x behind the out-pointer
```
The borrow checker must assert that `'a <:> 'c` as a pre-condition. The primary effect is that the place `*_x` and all of its descendent places is considered initialised and `_init` uninitialised.


```rust
init MyStruct {
    field: init_expression,
}

init MyStruct {
    field: ...,
    field2: {
        do_something_with(field)
    }
}
```


### `struct` to refer to the current data under construction, `super` for level traversal

```rust
Foo {
    field: 0,
    field2: struct.field + 2,
    bar: Bar {}
}
```

