I came up with the following approach. Its unsafe, but I still think its interesting because it is as powerful as it possibly can be - it even works with HashMap
. Unfortunately, I don't see any way to get an API as nice as this one without unsafe.
use std::ops::Deref;
use std::borrow::{ToOwned, Borrow};
use std::marker::PhantomData;
use std::ptr::NonNull;
use std::fmt;
#[derive(Copy, Clone)]
struct VersionRefData {
ptr: NonNull<u8>,
len: usize,
extra: usize,
}
#[repr(transparent)]
pub struct VersionRef {
data: VersionRefData,
}
#[derive(Copy, Clone)]
pub struct VersionSlice<'a> {
data: VersionRefData,
lifetime: PhantomData<&'a str>,
}
pub struct Version {
data: VersionRefData,
capacity: usize,
}
impl Version {
pub fn new(data: String, extra: usize) -> Self {
let mut data: Vec<u8> = data.into();
let len = data.len();
let capacity = data.capacity();
let ptr = data.as_mut_ptr();
std::mem::forget(data);
Self {
data: VersionRefData {
ptr: unsafe { NonNull::new_unchecked(ptr) },
len,
extra,
},
capacity,
}
}
unsafe fn make_string(&self) -> String {
String::from_raw_parts(
self.data.ptr.as_ptr(),
self.data.len,
self.capacity,
)
}
pub fn into_string(self) -> String {
unsafe {
let s = self.make_string();
std::mem::forget(self);
s
}
}
pub fn as_slice(&self) -> VersionSlice<'_> {
VersionSlice::new(self.as_str(), self.data.extra)
}
}
impl<'a> VersionSlice<'a> {
pub fn new(data: &'a str, extra: usize) -> Self {
Self {
data: VersionRefData {
ptr: unsafe { NonNull::new_unchecked(data.as_ptr() as *mut u8) },
len: data.len(),
extra,
},
lifetime: PhantomData,
}
}
pub fn as_str(&self) -> &'a str {
unsafe {
let slice = std::slice::from_raw_parts(self.data.ptr.as_ptr(), self.data.len);
std::str::from_utf8_unchecked(slice)
}
}
}
impl VersionRef {
fn create(data: &VersionRefData) -> &VersionRef {
unsafe {
&*(data as *const VersionRefData as *const VersionRef)
}
}
pub fn as_str(&self) -> &str {
unsafe {
let slice = std::slice::from_raw_parts(self.data.ptr.as_ptr(), self.data.len);
std::str::from_utf8_unchecked(slice)
}
}
pub fn extra(&self) -> usize {
self.data.extra
}
pub fn to_version(&self) -> Version {
Version::new(self.as_str().to_string(), self.extra())
}
}
impl Clone for Version {
fn clone(&self) -> Version {
self.to_version()
}
}
impl Deref for Version {
type Target = VersionRef;
fn deref(&self) -> &VersionRef {
VersionRef::create(&self.data)
}
}
impl AsRef<VersionRef> for Version {
fn as_ref(&self) -> &VersionRef {
VersionRef::create(&self.data)
}
}
impl Borrow<VersionRef> for Version {
fn borrow(&self) -> &VersionRef {
VersionRef::create(&self.data)
}
}
impl Deref for VersionSlice<'_> {
type Target = VersionRef;
fn deref(&self) -> &VersionRef {
VersionRef::create(&self.data)
}
}
impl AsRef<VersionRef> for VersionSlice<'_> {
fn as_ref(&self) -> &VersionRef {
VersionRef::create(&self.data)
}
}
impl Borrow<VersionRef> for VersionSlice<'_> {
fn borrow(&self) -> &VersionRef {
VersionRef::create(&self.data)
}
}
impl ToOwned for VersionRef {
type Owned = Version;
fn to_owned(&self) -> Version {
self.to_version()
}
}
impl Drop for Version {
fn drop(&mut self) {
unsafe {
drop(self.make_string());
}
}
}
impl fmt::Debug for Version {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Version")
.field("string", &self.as_str())
.field("extra", &self.data.extra)
.finish()
}
}
impl fmt::Debug for VersionSlice<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("VersionSlice")
.field("string", &self.as_str())
.field("extra", &self.data.extra)
.finish()
}
}
impl fmt::Debug for VersionRef {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("VersionRef")
.field("string", &self.as_str())
.field("extra", &self.data.extra)
.finish()
}
}
playground