Rust-for-Linux-tech
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Owners
        • Signed-in users
        • Everyone
        Owners Signed-in users Everyone
      • Write
        • Owners
        • Signed-in users
        • Everyone
        Owners Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights New
    • Engagement control
    • Transfer ownership
    • Delete this note
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Note Insights Versions and GitHub Sync Sharing URL Help
Menu
Options
Engagement control Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Owners
  • Owners
  • Signed-in users
  • Everyone
Owners Signed-in users Everyone
Write
Owners
  • Owners
  • Signed-in users
  • Everyone
Owners Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       Owned this note    Owned this note      
    Published Linked with GitHub
    1
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    # Thoughts on "out"-pointer, Nov 12 2025 _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 from the [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)._ _This document is supposed to cover some contents from [another dated Sep 18, 2025](https://hackmd.io/AmPxDXSFS9m3rAAaoGbz8w?edit). Contact wieDasDing@ if you find deficiency._ # Table of contents [toc] # Problem statement This section sets the theme for motivation for first-class language support for in-place initialisation. To illustrate the need for in-place initialisation, we show that there are at least two main use cases that make the concept of uninitialised place useful. ## Precise control over allocation and avoidance of moves It has been observed that constructing big values in a Rust program can be wasteful and stack-overflow inviting. ```rust pub struct BigArray([u8; 65536]); impl BigArray { pub fn new() -> Self { Self([0; 65536]) // BOMB x1 } } let array = BigArray::new(); // BOMB x2 ``` In this example, under the strictest interpretation of the current language reference, `[0; 65536]` may be first initialised on the stack and `memcpy`ed into the return value `BigArray`. Similarly, the huge object `BigArray` will be copied yet again `memcpy`ed into `array`. On compute platforms with limited memory budget, this will most probably cause stack-overflow. The question now is whether there is a way to allow initialisation of `BigArray` in a *given* memory allocation, which begins its lifetime as uninitialised? If we could handoff the memory allocation to a subroutine for initialisation, how could we make the subsequent access to the memory allocation, through direct access or borrows, safe in Rust? ```rust let array; // What is the syntax for referring to the uninitialised array? BigArray::init($<some pointer to array>); // How can we tell borrow checker that now array is safe for use? array.0[0] = 1; // The wish is that this statement should just work™. ``` ## Safe construction of self-referential values Construction of self-referential values necessarily requires working with partially initialised data. The examples of self-referential values include doubly linked lists, coroutines that necessarily contain borrows into the internal data across yield points and many others. The construction of such values require the constructor to obtain a stable memory allocation, for the self-referencing pointer value to remain valid throughout the life span of the value in question. Today the only way to achieve this is through unsafe raw pointers, because the borrow checker today categorically rejects the concept of a borrow to an uninitialised memory as a valid Rust value. If written in C, it would be seemingly easy. ```c struct list_node_t { list_node_t *prev, *next; }; struct pin_list_t { list_node_t *head; list_node_t *tail; } list_on_stack; // Assuming infallible allocation list_on_stack.head = malloc(sizeof(list_node_t)); list_on_stack.tail = list_on_stack.head; ``` ```rust let mut list_on_stack: MaybeUninit<PinList<'_>> = MaybeUninit::uninit(); let list: *mut PinList<'a> = &mut list_on_stack as *mut _; impl<'a> PinList<'a> { pub fn init(self: *mut Self) -> &'a Self { unsafe { let node = Box::new(ListNode::new()); ptr!(self.head).write(Box::leak(node)); // transfer ownership ptr!(self.tail).write(unsafe { ptr!(self.head) as &mut _ }); self as _ } } } let good_pin_list = PinList::init(list); // The returned value `good_pin_list` is now in a valid state. ``` Our research question now is, whether the function `init` can be written without `unsafe` by introducing necessary mechanism and checks. These checks would then ideally make misuse impossible. ```rust let list_on_stack; match PinList::try_init($<some borrow of list_on_stack>) { Ok(_) => { // list_on_stack should be in a consistent state // here in this branch ... } Err(_) => { // list_on_stack may be inconsistent // so access to it should be rejected &list_on_stack; // This should error } } ``` # `uninit`-pointer, formerly known as out-pointer ## Design constraints To begin with, we would like to set out a list of constraints that a solution for `uninit`-pointer should meet. - **First class type support**: one should be free to pass around `uninit`-pointers across function boundary through arguments. - **Statically determinted initialised state**: the mechanism should allow the compiler to statically determine whether, at any given program point, the sub-places behind an `uninit`-pointer are initialised. ## Sneak peek Here we have a few worked examples. ### Kernel use-case In this exercise, we demonstrate how `kernel::sync::lock::Mutex` will be constructed. This type contains a circular doubly-linked waiter queue that the kernel will use to wake up waiters on the lock. Since it is circular, initialising the lock must be emplacing. We start with `Opaque` in-place initialisation in the `kernel` crate. ```rust // First, bindings from the C-side mod bindings { pub type mutex; pub unsafe fn __mutex_init(ptr: *mut mutex); } ``` `Opaque` pointer is kernel's solution to provide safety guarantees when interacting with the underlying C-side data. Notably `Opaque` would be equipped with functions to perform in-place initialisation that we envision to introduce. ```rust #[repr(transparent)] pub struct Opaque<T> { value: UnsafeCell<MaybeUninit<T>>, _pin: PhantomPinned, // should be replaced with `UnsafePinned` } impl<T> Opaque<T> { /// In-place initialisation, infallible variant pub fn ffi_init(&uninit self, init_func: FnOnce(*mut T)) -> &own Self { init_func(self as *mut self); assume_init(self) } /// In-place initialisation, fallible variant pub fn try_ffi_init<E>( &uninit self, init_func: FnOnce(*mut T) -> Result<(), E> ) -> Result<&own Self, E> { init_func(self as *mut self)?; Ok(assume_init(self)) } } ``` For illustration purposes, we conjure `PinInit` as analogy to the `PinInit` trait from `pin_init` crate, except its exact interface is not part of our proposal yet. However, a compatible design of this trait should generally take an `uninit`-borrow and return an `own`-borrow with matching lifetime for notarisation, which will be explained in the later part of this text. We also add support for `UnsafeCell` with this new trait. ```rust // pub trait PinInit { type Target; type Error; fn try_init(self, dest: &uninit Self::Target) -> Result<&own Self::Target, Self::Error>; } impl<T> UnsafeCell<T> { pub fn pin_init<PI: PinInit>(&uninit self, value: PI) -> Result<&own Self, PI::Error> { self.value <- value.try_init(&uninit self.value)?; Ok(self) } } ``` So given that we define the `Mutex` as such. ```rust #[pin_data] pub struct Mutex<T> { #[pin] mutex: Opaque<bindings::mutex>, #[pin] value: UnsafeCell<T>, } ``` We shall initialise any `Mutex<_>` as the following. ```rust impl<T> Mutex<T> { pub fn new<PI: PinInit<Target = T>>(&uninit self, value: PI) -> Result<&own Self, PI::Error> where E: From<Infallible>, { self.mutex <- Opaque::ffi_init( &uninit self.mutex, |ptr| unsafe { bindings::__mutex_init(ptr); } ); self.value <- value.try_init(&uninit self.value)?; Ok(self) } } ``` ### The monster struct Here is an abridged version of [Asahi's monster struct](https://github.com/AsahiLinux/linux/blob/5ce014f7fa18021227bf46bd155b4ccf375a9dd3/drivers/gpu/drm/asahi/initdata.rs#L236-L478). Note that this is not the "scariest" of all. In practice bigger `struct`s in the wild are known to exist. ```rust let output; output <- raw::HwDataA::ver { unk_d20 <- f32!(65536.0), unk_e10_0 <- { let filter_a = f32!(1.0) / pwr.se_filter_time_constant.into(); raw::HwDataA130Extra { unk_58: 0x1, gpu_se_filter_a_neg: f32!(1.0) - filter_a, // ... } }, // Taylor: it would be cool to be able to promise to LLVM // that particular memory has already been zeroed (e.g. via // a zero page mapped into virtual memory or via calloc). ..Zeroable::zeroable() }; ``` #### Notable features We [notarise](#notarisation) a place behind an `&uninit T` into `&own T` with a `<-` statement. The type of the statement shall be `()`. ```rust output <- init_output(&uninit output); // or let owned: &own T = init_output(&uninit output); output <- owned; ``` In case the right hand side is `struct` literal, the right hand side is an "init" expression or an `&uninit _`, analogous to the notarisation statement. ```rust output <- MyStruct { field <- $init_expr }; ``` We have packed initialisation, demanding the source expression to have a type `I: Init` or `TI: TryInit<T, E>`. ```rust output <- MyStruct { ..Zeroable::zeroable() }; ``` ### Interaction with place pattern and place expression We propose to extend notarisation to [place pattern or place expression as well](https://doc.rust-lang.org/reference/expressions.html#r-expr.place-value), so that the following works, too. ```rust fn init_two_of_them<'a, 'b>(a: &'a uninit A, b: &'b uninit B) -> (&'a own A, &'b own B); (a, b) <- init_two_of_them(&uninit a, &uninit b); ``` #### {Optional} Extension to `let` The `let` statements at all possible places can be used for in-place initialisation, too. ```rust let (a, b): $ascription <- init_two_of_them(&uninit a, &uninit b); while let (a, b) <- init_two_of_them(&uninit a, &uninit b) { // a, b are in-place initialised } let MyStruct { a: a, b: b } <- init_two_of_them(&uninit a, &uninit b); // .. or abridged as let MyStruct { a, b } <- init_two_of_them(&uninit a, &uninit b); ``` ## Summary of proposed syntax Here is a worked example featuring all the proposed syntatx under this proposal. ```rust impl Point { fn init(&uninit self) -> &own Self { self.x <- init_i32(&uninit self.x); self.y <- init_i32(&uninit self.y); self } fn init(&uninit self) -> &own Self { (self.x, self.y) <- init_i32_tuple(&uninit self.x, &uninit self.y); self } fn init(&uninit self) -> &own Self { self <- Self { x <- init_i32(&uninit x), y <- init_i32(&uninit y), }; self } fn init(&uninit self) -> &own Self { // you could also do self.x <- init_i32(&uninit self.x); self.y <- init_i32(&uninit self.y); self } } fn main() { let x <- Struct { inner: Default::default(), ..Default::default(), }; } impl Point { fn init(&uninit self) -> &own Self { (self.x, self.y) <- weird(&uninit self.x, &uninit self.x, &uninit self.y); self } } ``` ## Type consideration ### Notations #### Subtyping on regions 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**. #### Subtyping on types The notation is extended to types as well, in the way that the types must match modulo region, and the regions in the type signatures are compared, respecting the ambient variance. As an example, `T<'a> <: U<'b>` implies `'a :> 'b` given that `'?0` in `T<'?0>` is in a **contravariant** position. ### Type `&'a uninit T` An `uninit`-pointer is a reference qualified with `uninit`. ```rust uninit_pointer: &'a uninit Type ``` The type of referent of an out-pointer must be `Sized` and one of ADT or tuples. ```rust struct Type { field: u8, } let uninit_place: Type; let uninit_pointer: &uninit Type = &uninit 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 ``` Here are the basic facts about `&'a uninit T`. - `T: 'a`; - it implements all auto traits through the constituent type `T` but it is **never `Copy` nor `Clone`**; - it can be safely coerced into `*mut T`, and `unsafe`ly reconstructed from `*mut T`; - it has **no destructor**, so that dropping it invokes no drop implementation. ##### Variance environment of `&'a uninit T` The `'a` in `&'a uninit T` is in an invariant position. `&'a uninit T <: &'b uninit U` necessarily implies `'a <:> 'b`. #### Lifetime narrowing through reborrowing Like a plain-old mutable borrow `&mut T`, a `uninit` borrow `out: &'a uninit T` can have its lifetime `'a` narrowed by reborrowing `&uninit *out`. The attached lifetime `'b` satisfies `'b :> 'a`. ### Initialisation certificate, `&own T` When we lend out a field out of a `&uninit` 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 `&uninit` 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 uninit T` instance, the type has to encode information so that the caller can re-constitute this connection. Our proposal here is to include the lifetime `'a` of the the out-pointer. Let us denote the type as `&'a own T`. When a `&'a uninit T1` is discharged with its corresponding certificate `&own<'b> T2`, `'a <:> 'b` and `T1 <:> T2` must hold. This certifying type must be able encode its unique relationship with exactly one of the `&uninit` inputs into the function. Here are the basic facts about this type `&'a own<'b> T`: - it is inhabited; - `T: 'b`; `'b: 'a`; - it is transparently a raw pointer pointing at the place behind a corresponding `&'a uninit T` pointer; - it implements `auto` traits through the constituent type `T` but never `Clone` or `Copy`, so that it is non-fungible; - the place behind `&own T` can be mutably borrowed, so that `&mut *&own value: &mut T` for `value: T`; - it **has a destructor** that delegates the destructor of `T` on the place behind it. In places the type signature may be written with one lifetime, `&'a own<'a> T`, which should be understood as `&'a own<'a> T`. ##### Variance environment of `&own<'a> T` The lifetime `'a` at its position in the signature is invariant. Specifically, from `&own<'a> T <: &own<'b> T` we can deduce that `'a <:> 'b` ## MIR, region and borrow checking considerations ### Region rules involved in introduction of `&uninit T` During borrow-checking, the numbered lifetime `'?a` assigned to a newly introduced `&'?a uninit T` should have the constraint `T <: '?a`, `'?a lives-at $current_program_point`, like a regular MIR `Rvalue::Ref` would do to its lifetime. This also applies to `uninit`-reborrowing, bzw `_a = &uninit (*_b)`, with or without projections, with the subregion relations populated in the same way as the plain-old references. #### Initialisation state of the place behind the `uninit`-borrow The initialisation state of the place behind the `uninit`-borrow should be statically determined to be **uninitialised** as known to the borrow checking. Depending on the state of the `uninit`-borrow, we have these scenarios. - If it is known to the borrow checker as always being initialised, the in-place drop should be invoked on thie place behind before the `uninit`-borrow is generated. In particular, when the place behind is getting re-assigned or re-in-place-initialised, the drop should be invoked. ###### MIR ```mir // case 1 _uninit = &uninit _place; _own = initialise() -> [return: bb1, ..]; bb1: { _place <- _own; // `_place` is initialised .. _uninit = &uninit _place; // so a drop on `_place` should be called before } ``` - If it is known to the borrow checker that at the current statement `_another_borrow = &uninit _place` * a place `_place` is already under some borrow, be it `uninit`- or immutable- or mutable-borrow; * the borrow in question is live, like in particular a `&own T` with matching region, then an access conflict error should be emitted. ###### MIR ```mir _uninit = &uninit _place; _uninit2 = &uninit _place; // access conflict, `_uninit` is still `uninit`-borrowed // the only allowed `uninit`-borrow involving `_place` is reborrowing `_place`, // like either .. _uninit_field = &uninit (*_uninit).field; // .. or "reborrow" of whole _uninit2 = &uninit (*_uninit); ``` :construction: _Call for experiment: the conflict error message needs design through experimentation._ - If it is known to the borrow checker that at the current statement `_place <- _own` where `_own: &own<'a> T` holds necessarily * a place `_place` is already under some borrow, be it `uninit`- or immutable- or mutable-borrow; * the borrow in question is live, like in particular a `&own`, but the live range of the borrow does not match with the region value `'a`, then an access conflict error should also be emitted. :construction: _Call for experiment: the conflict error message needs design through experimentation._ The MIR builder should introduce drop flags on those borrows when the initialisation state of the place in interest cannot be statically determined. ### <a id="notarisation"/> Region rules involved in notarisation of a place **Notarisation** is an assertion in the program to declare that an uninitialised place should be considered initialised by the borrow-checker. This operation involves two MIR syntax elements, a place and a `move` `Rvalue` of type `&own<'_> T`. For notation, we would simply write `_place <- _own` where `_place` is the place expression and `_own` is the `&own<'_> T` value to be moved. The pre-condition of the notarisation is that the place should be statically determined to be uninitialised. The primary effect of the notarisation should be that `_place` becomes known to be initialised, the live span of `_own` terminates and **the drop obligation on the underlying value `T` is transferred to `_place`**. In other words, a `drop` on `_place` or its ancestors will be responsible for invoking `drop` on `T` after this statement. ###### MIR ```mir let _place: T; let _own: &own<'_> T; // _place needs to be known as uninitialised _place <- _own; // _place is now initialised, _own turns dead without invocation of // `T`'s destructor. StorageDead(_own); drop(_place); // Now it is valid and necessary to invoke `T`'s destructor ``` When we notarise a place with a region `'a` with `&own<'b> T`, we demand `'a <:> 'b`. By notarisation, we certify to the borrow checker that the place and its descendent places as fully-initialised. We need to prevent confusing the notarisation one `&uninit T` by smuggling another `&own T`. Otherwise, we would notarise the wrong place leading to potential unsoundness in the following example. ```rust fn bad<'a, T>(_: &'a uninit T, _: &'a uninit T) -> &'a own T { // flip a coin, make a wish } let ua = &uninit a; // this has a new lifetime 'x let ub = &uninit b; // this has a new lifetime 'y ua <- bad(ua, ub); // 'x and 'y shall not unify // 1) we should allow reborrowing &'_ uninit T let a: A; let a0 = &'? 0 uninit a; // if we allow ... { let b = &uninit a0.field; } // ... we should allow reborrowing the whole let a1 = &'? 1 uninit *a0; // ... and if we allow it, what is the valuation of the anonymous region attached to `a1`? // choice A: '?1 so that `'?1 <: '?0` ? Mkay. // choice B: '?0, but then a1 dominates a0 but live range of a1 is contained in that of a0, so borrowck will not be happy. // 2) discharging &'_ uninit T without initialisation should be allowed fn try_init<'a>(a: &'a uninit A) -> Result<&'a own<'a> A, ()> { if flip_a_coin() { a.field = 1; Ok(a) } else { // here we leave `a` especially `*a` uninitialised // but it is fine Err(()) } } struct Evil<'a>(&'a uninit A, &'a uninit A); fn merge<'a, 'b: 'a>(a: &'a uninit A, b: &'b uninit A) -> Evil<'a> { // so we can reborrow here right? Evil(&uninit *a, &uninit *b) } let a: A; let b: A; // this should not work because ... let Evil(a, b) = merge(&uninit a, &uninit b); // the two `&uninit`s are live for two different sets of program points // because `&uninit a` and `&uninit b` are necessarily instantiated // in two different MIR statements a <- bad(a, b); // therefore, the call to `bad` does not type-check // because `'a` and `'b` cannot be unified as one late-bound lifetime parameter // this also should not work: let x = &uninit x; let ua = &uninit x.a; let ub = &uninit x.b; ua <- bad(ua, ub); ``` ### Coercion of `&uninit T` to `&own T` A `&'a uninit T` should be transparently coerced into `&'b own T` where `'a <:> 'b`, when the borrow checker can check and assert that the borrowed place tagged with the region `'a` has be notarised or statically known to be initialised. ```rust fn init_me(data: &uninit Data) -> &own Data { if CONDITION { return data; //~ ERROR: `*data` may not be fully initialised // therefore, the coercion is rejected } data.inner = new_inner(); data // coercion happens because `*data` is fully initialised // Expectation: the return value of type `&own Data` certifies // the initialisation already, // so no drop is invoked on `*data`. } ``` At coercion site, the place behind an `&uninit T`, such as `*data` in the example, will be temporarily switched back to the uninitialised state from the borrow checker's perspective: the resultant `&own T` witnesses the initialisation. ### Asserted notarisation An `unsafe` intrinsic, as an MIR `Rvalue`, should be available for asserting the notarisation of a `&'a uninit T`, returning `&'b own T`, with `'a <:> 'b`. This is useful for interoperability when the in-place initialisation is typically handed-off to a foreign function or process. The proposed intrinsic `assume_init` is the following. ```rust let x: T; x <- unsafe { call_ffi(&uninit x as *mut T); // Safety: `x` is not statically known to be initialised // after the `call_ffi` returns, // so it is safe to `uninit`-borrow `x` again // without invoking the `T` destructor. assume_init(&uninit x) }; ``` ### :construction: Extension: Pattern-matching We also intend to support pattern-matching on `&uninit T`s. From the surface syntax, we would like to support the following pattern-matching semantics. #### `let` statement `let` statement shall possibly de-initialise the place under the `uninit`-borrow mode and, transition the place into partially moved state and set discriminant on that place. ```rust let Struct::Variant { ref uninit a, b } = &uninit place; // .. de-initialises `place` and set the discriminant to that of `Variant` // while projecting out `a: &uninit A` and `b: &uninit B` // **from two MIR statements**. ``` ## Library consideration ### Supporting trait `Init<T>` We would like to introduce supporting type with syntax support. ```rust #[lang_item = "init_trait"] pub trait Init<T> { fn init(self, dest: &uninit T) -> &own T } ``` ### Auxiliary trait `TryInit<T>` In addition to `Init`, we propose to further introduce an auxiliary traits `TryInit<T>`. ```rust #![feature(try_trait_v2)] #[lang_item = "try_init_trait"] pub trait TryInit<T> { type Output: Try<Output = &'a own<'a> T> where Self: 'a, T: 'a; fn try_init<'a>(self, dest: &'a uninit T) -> Self::Output<'a>; } pub trait TryInitToResult<T>: TryInit<T> where for<'a> Result<&'a own<'a> T, Self::Error>: FromResidual<Self::Output<'a>>, { type Error; } ``` ## Syntax consideration ### Basics of emplacement statement `<-` and the try-variant `<-?` There are two modus operandi of `<-`. First is the notarisaion of a place expression or place pattern by emplacing an ADT literal, tuple, repeat or array expression. #### Emplacement context The right-hand side **source** `expr` of an emplacement statement `place <- expr;` is considered inside an emplacement context in a **target** place `place`. The HIR type checking of the statement would perform a sequence of trials to determine the emplacement action. 1. When the **source** expression has a type known to implement `Init<T>`, the emplacement action would be that the **target** place would be coerced into `T` with no adjustments permitted, followed by an `uninit`-borrow as the argument to the invocation of `Init<T>::init` call, and finally notarisation of the **target** place with the returned value. ```rust fn ctor() -> impl Init<Data>; let data: Data; // because the target is a `Data` and the source produces an `impl Init<Data>` ... data <- ctor(); // the emplacement action is invocation of `Init::init` ``` 2. When the **source** expression in the emplacement statement has a concrete type matching the concrete type of the **target** place, the emplacement action is direct assignment of the evaluation result into the target place. ```rust fn make_data() -> Data; let data: Data; // because the target is a `Data` and the source produces a `Data` ... data <- make_data(); // the emplacement action is a direct assignment ``` #### Convenient fallible emplacement through `<-?` and `TryInit` Emplacement can fail during the initialisation process. Therefore, earlier we proposed `TryInit<T>` and we would like to propose the counter-part of `<-` for the fallible case. ```rust struct Field {} struct Root { data: Field, } fn ctor() -> impl TryInitIntoResult<Field, Output = Error>; fn maybe_init(a: &uninit Root) -> Result<&own Root, Error> { a.data <-? ctor(); Ok(a) } ``` ##### Extension: convenient adaptor for use of `&uninit` constructor calls as `Init` It is observed that use of `&uninit T` and `&own T` often involves immediate notarisation upon completion of an emplacing constructor call, with the drawback of naming the one and only initialising place twice, like the following example. ```rust fn ctor(_: &uninit A, b: B) -> &own A; let a: A; a <- ctor(&uninit a, b); // `a` is named twice ``` To reduce boilerplate and improve readability for single `&uninit`/`&own` pair case, we would like to propose a builtin macro `emplace!` that generates a `Init<T>` object along with necessary captures for emplacement action. Conceptually the macro would be prototyped as follows. The macro `emplace` will replace a syntatical sigil in the input function call argument, the `@` symbol as we propose, with the `&uninit` slot. So that the example can be translated as follows. ```rust a <- emplace! { ctor(@, b) }; // or let ctor = emplace! { ctor(@, b) }; a <- ctor; // which is equivalent to .. struct Ctor(B); impl Init<A> for Ctor { fn init(self, out: &uninit A) -> &own A { ctor(out, self.0) } } a <- Ctor(b); ``` ##### :construction: Future consideration: emplacing closure There has been interest in writing objects implementing `TryInit`, `TryInitIntoResult` conveniently with a closure syntax. We would like to delay the syntax consideration to a future point of time. However, the `try_init!` macro from `pin_init` crate provides sufficient inspiration on the syntax construction. #### Tuples, repeats, arrays and primitives literals Tuples are conceptual anonymous structs while arrays and repeats . Therefore, these notation on the right-hand side of a `<-` statement receive special treatment, so that the elements are initialised in the emplacement context. ```rust // `data1, `data2` and `data3` are evaluated in the emplacement context place <- (data1, data2, data3); place <- [data1, data2, data3]; place <- [data1; N]; ``` Primitives literals subject to const promotion like `0u8`, `"str"` and constant expression such as `const { .. }` can also be in-place initialised, when they do not fall under the tuples, repeats or arrays case. ```rust a <- "hello world"; b <- T::AssociatedConst; c <- 1 + 1; ``` Due to code-generation consideration, we have found this kind of use unadvisable. Our recommendation is elaborated in the code-generation section. #### ADT literal We propose the following syntax for ADT literals. ```rust let data: Data = ..; let init_data: impl Init<Data> = .. ; // ADT literal place <- Struct::Variant { data, // `data` is moved into the destination data_field <- init_data, // `init_data.init()` in-place construction is invoked ..Default::default_init() }; ``` Here are the remarkable features of handling ADT literals. ##### Direct assignment Field specification `a: b` works like direct assignment. ```rust place <- Struct { a: b, }; // is equivalent to place.a = b; ``` ##### Short-hand Like plain-old ADT literals, we would support single occurrence of identifier `a` when the field name `a` and the emplacement context expression as a local `a` coincides. It is equivalent to `a <- a`. ```rust place <- Struct::Variant { a, }; // is equivalent to place <- Struct::Variant { a <- a }; ``` ##### Aggregated emplacement We would like to support a pack of fields in the target ADT data, without the mandatory exhaustiveness in ADT fields and with an optional default expression after the `..` sigil. ```rust place <- Struct::Variant { field1, ..Default::default_init() } // This `field2` can be initialised // outside the aggregated emplacement: place.field2 <- field2(); ``` Here `Default::default_init()` shall implement `Init<Struct>`. Its evaluation comes at the first before all the other fields in the aggregate. ##### Lexical scoping In an aggregate emplacement, it is possible that fields may use previously initialised fields. We would allow this by exposing the initialisation state of previous fields to the next fields. ```rust place <- Struct::Variant { field1, // `place.field1` is considered initialised field2 <- make_ref(&place.field1), }; ``` #### Others We still support other kinds of expression on the right-hand side, when it fails to fit in the previous categories due to possibly unfulfilled trait relationship, and interpret the statement as direct assignment. ## Code generation consideration It is observed that in some cases, specifically when unwinding on panics is concerned, a `&own T` pointer may be superfluous because its only purpose is to notarise initialisation, while its content as an address is left unread. One may note that this is a typical candidate of dead value elimination. We would advise the implementation to enable such elimination as much as possible, probably by favouring inlining of functions returning `&own T` as data. ### :construction: {Optional} Lint against superfluous in-place initialisation We advise against in-place initialisation on primitive types other than references and slices; or tuples or ADTs containing only those primitive types so that the size of the type is below a target-dependent size. Direct assignments in this case are almost always better than in-place initialisation thanks to good code generation and target architecture capability while in-place initialisation has to be necessarily materialised into memory writes and can be inferior in performance and hurt readability. We propose a lint to recognise the notarisation statements `<-` from user program, not including macro expansions, where the type of the target place falls in this category. ## FAQs ### Why `&own T`? This is the first time that Rust would need to "communicate" initialisation state across subroutine boundary. There is no type-level signal that today's Rust can use to manipulate the initialisation state of places in the caller context. In various designs, the need to manipulate the init state will eventually manifest itself in one form or another. However, we choose `&own T` because of these two reasons. - It is guardrail for ensuring proper destruction, in case it is left unused for notarising the source init place. - It is basically a reference to the initialised place, and we have intention and future extension to utilise the fact that it is a reference. ### Can we get "smarter" about notarising the initialisation state, from the return values containing `&own T`? ```rust fn method() -> T { let a: T; match get_something_init(&out a) { Ok(cert) => { return a } } } ``` ```rust fn method() -> T { let a: T; match get_something_init(&out a) { Ok(cert) => { return a } } } ``` # Important consideration - the MIR place evolution _[Zulip chat](https://rust-lang.zulipchat.com/#narrow/channel/213817-t-lang/topic/Field.20projections.20and.20places/with/544815635)_ # Initializing through types ## Types that contain values ```rust // Allows coercing a `&own Uninit<Me<T>>` to `&own Me<Uninit<T>>` when only fields // mentioning `T` are uninitialized. unsafe trait CoerceUninit {} ``` # Action Items - ~~`LHS <- RHS` mentions place twice, can we do better?~~ - Should we go for `<-` in struct initaliser or coercion when `:` is used? - Fn sig of constructor of a struct that delegates to other in-place initialisers? How to make transparent data like `UnsafeCell` play nice with its counterpart `&uninit UnsafeCell`?

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password

    or

    By clicking below, you agree to our terms of service.

    Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully