Hagia
log in
morj / local-alloc-example
overview
files
history
wiki
Viewing at
use std::alloc::Allocator;

// I lied, there are a lot of problems. The first one: it makes a lot of sense
// to make an immutable string as Box<str>, right? Well look here:
// https://github.com/rust-lang/rust/issues/18283
// "There is no way to create Box<str>" - yea the issue was closed with
// adding String::into_boxes_str. But we can't use strings! So this is no help.
//
// Another stupid problem is that Box<[T]> doesn't implement a lot of methods
// that Box<T> does, like for example into_raw and from_raw. Thankfully there
// are some methods from casting vecs to boxes, so we'll go through them.

// This is our (immutable) string type
pub struct String<A: Allocator>(Box<[u8], A>);

// You can use it as `str`:
impl<A: Allocator> AsRef<str> for String<A> {
fn as_ref(&self) -> &str {
self.as_str()
}
}
// You can use it in Debug and Display formatting too
impl<A: Allocator> std::fmt::Debug for String<A> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Debug::fmt(self.as_str(), f)
}
}
impl<A: Allocator> std::fmt::Display for String<A> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Display::fmt(self.as_str(), f)
}
}

// And here's how you create one. Regular strings can be created from `to_string`
// or from `format!`; we can't add the first one, so we're going for the second
macro_rules! format_in {
($alloc:expr, $($args:expr),*) => {
$crate::string::write_string($alloc, format_args!($($args),*))
}
}

// A trick I found online: format_args! converts from freeform format string and
// expressions into an `Arguments` structure that you then feed to a regular
// function. This allows us to do as little as possible in a macro
#[doc(hidden)]
pub fn write_string<A: Allocator>(
alloc: A,
args: std::fmt::Arguments,
) -> Result<String<A>, std::fmt::Error> {
// Create a writer (defined below)
let mut w = StringWriter(Vec::new_in(alloc));
// Using `write` fills the internal Vec-buffer with our result
std::fmt::write(&mut w, args)?;

// And then we extract the result from a vector into a box
let b = w.0.into_boxed_slice();
Ok(String(b))
}

// Nothing fancy, just a vector of bytes to implement a trait
struct StringWriter<A: Allocator>(Vec<u8, A>);

// This is the trait that `format` and `println` and friends use under the hood
impl<A: Allocator> std::fmt::Write for StringWriter<A> {
fn write_str(&mut self, s: &str) -> std::fmt::Result {
// We just concat bytes from new string to our vector. To make this
// efficient, we're going to later extend our arena to be able to
// reallocate the top item
self.0.extend_from_slice(s.as_bytes());
Ok(())
}
}

impl<'a, A: Allocator> String<A> where A: 'a {
// And now, for the only unsafe part of this module. Since the only way to
// construct our String is to append str-s, what we have in our buffer is a
// valid utf8 bytes. That means we can convert them unchecked.
pub fn as_str(&self) -> &str {
unsafe { std::str::from_utf8_unchecked(self.0.as_ref()) }
}

// And this is what makes us truly better than std strings
pub fn new_in(s: &str, alloc: A) -> Self {
let mut r = Vec::with_capacity_in(s.as_bytes().len(), alloc);
r.extend_from_slice(s.as_bytes());
String(r.into_boxed_slice())
}

pub fn leak_str(self) -> &'a str {
let bytes = Box::leak(self.0);
unsafe { std::str::from_utf8_unchecked(&*bytes) }
}
}

impl<'a> String<&'a crate::Arena> {
pub fn into_str(self) -> &'a str {
self.leak_str()
}
}