placing

Crates.ioplacing
lib.rsplacing
version1.0.0
created_at2025-05-19 15:24:35.680454+00
updated_at2025-05-19 15:24:35.680454+00
descriptionA prototype notation for referentially stable constructors
homepage
repositoryhttps://github.com/yoshuawuyts/placing
max_upload_size
id1679864
size70,188
Yosh (yoshuawuyts)

documentation

https://docs.rs/placing

README

placing

A prototype notation for referentially stable constructors

Crates.io version Download docs.rs docs

API Docs | Releases | Contributing

Installation

$ cargo add placing

Example

This crate enables address-sensitive types to be constructed. That is: types whose address in memory can't change over time. As well as types that might OOM if they are constructed on the stacke before being copied to the heap. To start we create a new type with the placing attribute macro. This sets up the right internal type hierarchy for us.

#[placing::placing]
struct Cat {
    age: u8,
}

We can then define our constructors and other methods. Constructors need to end with a struct expression as the last statement in the block, and need to be annotated with the #[placing] attribute. This allows us to transform it to be constructed directly in the caller's stack frame. Getters and other methods have no restrictions and can be freely used.

#[placing::placing]
impl Cat {
    /// Construct a new instance of `Cat` in-place
    #[placing]
    fn new(age: u8) -> Self {
        Self { age }
    }

    /// Returns the age of the cat
    fn age(&self) -> &u8 {
        &self.age
    }
}

Finally it's time to create an instance of our type. This is the most scary-looking part of this crate, but once you understand what's going on it's fairly straightforward. Earlier we defined our Cat::new constructor. The placing crate has broken this up into two parts: new_uninit which creates the "place". And new_init which instantiates the type in the place. All you have to do is call both of these right after each other, and voila - in-place construction!

fn main() {
    // Create the place for `cat`
    let mut cat = unsafe { Cat::new_uninit() };
    // Instantiate the fields on `cat`
    unsafe { cat.new_init(12) };
    // Type can now be used as normal
    assert_eq!(cat.age(), &12);
}

To define a constructor which places a type directly on the heap rather than on the stack, just write -> Box<_> and Box::new and placing will take care of the rest.

#[placing::placing]
impl Cat {
    /// Construct a new instance of `Cat` in-place on the heap
    #[placing]
    fn new(age: u8) -> Box<Self> {
        Box::new(Self { age })
    }
}

The language feature

This crate uses proc macros to prototype a new language feature. Proc macros are less powerful than what a compiler can do, as it can only provide limited syntactic transforms and does not have access to semantic analysis. Because of this a language feature should become a lot more streamlined. Assuming we'd have some first-class placing notation, the compiler would allow us to write our earlier example as follows:

struct Cat {
    age: u8,
}

impl Cat {
    placing fn new(age: u8) -> Self {
        Self { age }
    }

    fn age(&self) -> &u8 {
        &self.age
    }
}

fn main() {
    let cat = Cat::new(12);
    assert_eq!(cat.age(), &12);
}

That's just a single extra annotation on the constructor. Everything else continues working exactly the same, which is what makes this feature so appealing.

Safety

This crate prototypes a new language feature and liberally makes use of unsafe.

Contributing

Want to join us? Check out our "Contributing" guide and take a look at some of these issues:

See Also

References

License

Licensed under either of Apache License, Version 2.0 or MIT license at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this crate by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.
Commit count: 30

cargo fmt