Skip to content

js2xxx/placid

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

78 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

placid - Separated ownership and in-place construction

Cargo Documentation License

placid extends Rust's ownership model with owned and uninit references with pinning variants (semantically &own T, &uninit T, and &pin own T or Pin<&own T>). It also provides macros, traits, and functions for constructing and manipulating these types, achieving safe in-place construction.

The problem

In Rust, the ownership of a value is typically bound to the value itself. In other words, there is no way to separate the ownership of a value from it.

This creates challenges in scenarios where:

  • You need to construct large values in-place to build address-aware objects ergonomically;
  • You want to transfer ownership of large objects without expensive memcpys;

Traditional Rust forces you to construct values on the stack, then move them, which can be inefficient for large types. placid solves this by introducing owned and uninit references that carry full ownership through a reference.

Features

  • Owned References (&own T): Carry exclusive ownership through a reference.
  • Pinned Owned References (&pin own T): Combine ownership with pinning guarantees for immovable types.
  • Temporary Fixation (Fix<P>): A Pin-like wrapper that keeps a value fixed to its place, but allows safe leaking and temporary fixation of plain &mut T.
  • In-place Construction: Construct complex types directly in their final location using init! and init_pin! macros.
  • Uninit References: Work safely with uninitialized memory.
  • Safe Movement: Transfer ownership out of smart pointers like Box without reallocating or moving the underlying data.

Usage

Add placid to your Cargo.toml:

[dependencies]
placid = "<the current version>"

Basic Ownership

Constructing an owned reference directly on the stack:

use placid::prelude::*;

let simple = own!(42);
assert_eq!(*simple, 42);

#[derive(Init)]
struct LargeData {
    buffer: [u8; 1024],
}

fn process_owned(data: Own<LargeData>) {
    // The function owns the data and will drop it when done.
    println!("Processing: {} bytes", std::mem::size_of_val(&*data));
} // data is dropped here.

// In a real scenario, you'd construct this in-place rather than moving.
let owned = own!(init!(LargeData { buffer: init::repeat(42) }));
process_owned(owned);

Pinned Types

For self-referential or pinned types:

use placid::prelude::*;
use std::{pin::Pin, marker::PhantomPinned};

#[derive(InitPin)]
struct SelfRef {
    data: i32,
    data_ptr: *const i32,
    marker: PhantomPinned,
}

fn process_pinned(data: POwn<SelfRef>) {
    // The function owns the data and guarantees it won't move.
    // This is safe because the data is pinned in place.
}

let pinned = pown!(
    // Provide initial value for the struct.
    init::value(SelfRef {
        data: 42,
        data_ptr: std::ptr::null(),
        marker: PhantomPinned,
    })
    .and_pin(|this| unsafe {
        // SAFETY: We are initializing the self-referential pointer.
        let this = Pin::into_inner_unchecked(this);
        this.data_ptr = &this.data as *const i32;
    })
);
process_pinned(pinned);

Moving Ownership

Moving out an owned reference from a regular smart pointer:

use placid::prelude::*;

let boxed = Box::new_str(7, "move me");

let mut left; // Box<[MaybeUninit<u8>]>
// Move out an owned reference from the Box. The original
// allocation is transferred to `left`, mutably borrowed
// by `own`.
let own = into_own!(left <- boxed);
assert_eq!(*own, "move me");

// Drops the String, but not the Box allocation.
drop(own);

// Now we can reuse the Box allocation.
let right: Own<str> = left.write("new val");
assert_eq!(&*right, "new val");

Temporary Fixation (Fix)

Fix<P> fixes the value behind a pointer P to its memory location, just like Pin<P>. The difference is that it makes one fewer promise: the value need not stay valid at its location until it is dropped. Dropping that requirement means a fixed value can be safely leaked or forgotten, and a Fix<&mut T> can be created safely from a plain &mut T.

In return, Fix only prevents the value from being moved out of its place: it never exposes a &mut T (nor unwraps its inner pointer) unless the target type is trivially movable, with MoveToUninit acting as the Unpin-like opt-out. This is what makes in-place construction sound: every initializer returns a Fix<Own<T>>, so a freshly built value is guaranteed to stay put even before its custom move/drop logic runs. Structural projection works out of the box through the munge crate.

use placid::prelude::*;

// Fixation is temporary, so a `Fix<&mut T>` is safe to create.
let mut value = 42;
let fixed = Fix::new(&mut value);
assert_eq!(*fixed, 42);

// `i32` is trivially movable, so the `&mut` can still be recovered.
let back: &mut i32 = Fix::into_inner(fixed);
*back += 1;
assert_eq!(value, 43);

// `own!(@fix ..)` builds a value on the stack as a `Fix<Own<T>>` directly,
// keeping it fixed but still relocatable through its move constructor, without
// ever exposing a bare `Own` or a `&mut`.
let fixed: Fix<Own<String>> = own!(@fix String::from("fixed"));
let uninit = uninit!(String);
let moved = fixed.move_to(uninit);
assert_eq!(&*moved, "fixed");

License

This project is licensed under either of:

at your option.

References

About

Separated ownership and in-place construction in Rust

Resources

License

Apache-2.0, MIT licenses found

Licenses found

Apache-2.0
LICENSE-APACHE
MIT
LICENSE-MIT

Stars

1 star

Watchers

1 watching

Forks

Contributors

Languages